Archive for the ‘Web Technologies’ Category

WebGL on a high-DPI display

Dealing with WebGL on a high-DPI display isn’t too difficult, but it does require an understanding of device pixels vs CSS pixels. Elements on a page automatically upscale on a high-DPI display, as dimensions are typically defined with CSS, and therefore defined in units of CSS pixels. The <canvas> element is no exception. However, upscaling a DOM element doesn’t mean that the content within the element will be upscaled or rendered nicely – this is why non-vector content can appear blurry on higher resolutions. With WebGL content, not only will it appear blurry, but the viewport will likely be clipped as well, due to the viewport being incorrectly calculated using CSS pixel dimensions.

With WebGL everything is assumed to be in units of device pixels and there is no automatic conversion from CSS pixels to device pixels. To specify the device pixel dimensions of the <canvas>, we need to set the width and height attributes of the element:

CSS Pixels vs Device Pixels on canvas

I like to compute and set the attributes automatically using window.devicePixelRatio.
In glfx I do the following (passing in _canvasWidthCSSPx, _canvasHeightCSSPx):

// Get devicePixelRatio
glfx.devicePixelRatio = window.devicePixelRatio || 1;

// Set the width,height attributes of the canvas element (in device pixels)
var _canvasWidthDevicePx = _canvasWidthCSSPx * glfx.devicePixelRatio;
var _canvasHeightDevicePx = _canvasHeightCSSPx * glfx.devicePixelRatio;    
_canvas.setAttribute(
"width", _canvasWidthDevicePx);
_canvas.setAttribute(
"height", _canvasHeightDevicePx);

// Set viewport width,height based on dimensions of canvas element        
glfx.gl.viewportWidth = _canvasWidthDevicePx;
glfx.gl.viewportHeight = _canvasHeightDevicePx;        

Reference: HandlingHighDPI

Double-tap interactions

While click events are seamlessly supported on touch devices, double-click events are not and there is no equivalent double-tap event available. However, double-tap interactions can be captured by listening for multiple, subsequent touchend events. The code below shows how to do this, with the basis for a double-tap being:

  • Two touchend events, on a certain element, both occurring within a certain time interval (300ms)
  • Two touchend events, on a certain element, both occurring within a certain distance from each other (24px)

I’ve come across a lot of code that addresses the first point (see double tap on mobile safari), but the second point is just as important because almost all touchscreens are multitouch and a two-handed posture with a phone or tablet is not uncommon. With a two-handed posture, and an element large enough to span the screen, it’s easy to hit two different areas of the element (on opposite ends of the screen) in rapid succession – a double-tap would be detected, even though two distant points on the element were touched, unless the distance between the points is taken into account.

Ideally, both the interval and distance should be user-defined settings at the device/operating-system level, similar to the way setting the double-click speed is, but lacking such support, hacking it in at the application-level is the only viable option.


// Handler for when a double-tap (on a touchscreen) or double-click (with a mouse) is detected
var dblTapHandler = function (x, y) {

// Do stuff...

}

$(document).ready(
function() {


// Listen for double-click events for desktop/mouse-based interactions
$('.ia-dbltap-area').on('dblclick', function (e) {
// Call handler
dblTapHandler(e.pageX, e.pageY);
});


// Listen for touchend events for touch-based interactions
$('.ia-dbltap-area').on('touchend', function(e) {

var dblTapRadius = 24; // radius (in pixels) of the area in which we expect the 2 taps for a double-tap
var dblTapSpeed = 300; // interval (in milliseconds) in which we expect the 2 taps for a double-tap

if(e.originalEvent.changedTouches.length <= 0) {
return false; // we have nothing to work with
}

var dblTapDetected = false; // flag specifying if we detected a double-tap
var areaElem = $(this); // element in which this touchend event has occured

// Position of the touch
var x = e.originalEvent.changedTouches[0].pageX;
var y = e.originalEvent.changedTouches[0].pageY;

var now = new Date().getTime();

// Check if we have stored data for a previous touch (indicating we should test for a double-tap)
if(areaElem.data('last-touch-time')) {

lastTouchTime = areaElem.data(
'last-touch-time');

// Compute time since the previous touch
var timeSinceLastTouch = now - lastTouchTime;

// Get the position of the last touch on the element
var lastX = areaElem.data('last-touch-x');
var lastY = areaElem.data('last-touch-y');

// Compute the distance from the last touch on the element
var distFromLastTouch = Math.sqrt( Math.pow(x-lastX,2) + Math.pow(y-lastY,2) );

// Check if:
// 1. If the time since the last touch is within the specified double-tap interval (dblTapSpeed)
// 2. The distance from the last touch is within the specified double-tap radius (tapRadius)
if(timeSinceLastTouch <= dblTapSpeed && distFromLastTouch <= dblTapRadius) {

// Flag that we detected a double tap
dblTapDetected = true;

// Call handler
dblTapHandler(x, y);

// Remove last touch info from element
areaElem.data('last-touch-time', '');
areaElem.data(
'last-touch-x', '');
areaElem.data(
'last-touch-y', '');
}

}


if(!dblTapDetected) { // A double-tap wasn't detected

// Store time and position of this touch on the element
// (Next touch may be a double-tap, we can use this info to determine if it is)
areaElem.data('last-touch-time', now);
areaElem.data(
'last-touch-x', x);
areaElem.data(
'last-touch-y', y);
}

});

});

The demo below shows the code in actions along with a bit of SVG to render where the user double-clicked or double-touched div.ia-dbltap-area:

Moving the caret to the end of text in an <input> element

Very simple, and the following will work in all modern browsers.

HTML

<input name="url" type="text" value="http://" />

Javascript

var inputElem = document.getElementsByName("url")[0];
                
var valLen = inputElem.value.length;

inputElem.selectionStart = valLen;
inputElem.selectionEnd = valLen;

inputElem.focus();

The same technique will work for <textarea> elements as well.

Manipulating text relative to the caret in a contenteditable div

I wanted to play around a bit with dynamically modifying text as you type. The following is a simple auto-correct demo that makes use of the Selection and Range interfaces to replace text (read: text preceding the caret) within a contenteditable div.

$(document).on('keydown', '.ia-txt', function (e) {
            
    
// check if space bar was hit
    
if(e.keyCode == 32) {
                    
        
// we'll check for the string "hwat"; incorrect form of "what"
        
var incorrectTxt = "hwat";
    
        
// Get selection and range based on position of caret
        // (we assume nothing is selected, and range points to the position of the caret)
        
var sel = window.getSelection();
        
var range = sel.getRangeAt(0);
                            
        
// check that we have at least incorrectTxt.length characters in our container
        
if(range.startOffset - incorrectTxt.length >= 0) {
        
            
// clone the range, so we can alter the start and end
            
var clone = range.cloneRange();
            
            
// alter start and end of cloned ranged, so it selects incorrectTxt.length characters
            
clone.setStart(range.startContainer, range.startOffset - incorrectTxt.length);
            clone.setEnd(range.startContainer, range.startOffset);

            
// get contents of cloned range
            
var contents = clone.toString();                    
                                    
            
// check if the contents of the cloned range is equal to our incorrectTxt string
            
if(contents == incorrectTxt) {
                                        
                
// delete the contents of the range ("hwat")
                
clone.deleteContents();    
                
                
// create a text node with the corrected text ("what") and insert it where we deleted the incorrect text
                
var txtNode = document.createTextNode("what");
                range.insertNode(txtNode);
                
                
// set the start of the range after the inserted node, so we have the caret after the inserted text
                
range.setStartAfter(txtNode);
                                    
                
// Chrome fix
                
sel.removeAllRanges();
                sel.addRange(range);             

            }                
        }
    }
    

});

You can see the code in action in the frame below. Every time you press the space-bar and the string “hwat” is detected, preceding the position of the caret, it is removed and replaced with the string “what”:

This is an incredibly trivial example (note that it doesn’t even check that the string “hwat” is surrounded by whitespace on both sides), but it does serve as a template for more advanced functionality. That said, be very aware of minor differences in the behavior of Range methods when working across browsers, I’ve stumbled across a few:

  • The code above breaks under certain conditions in Internet Explorer. If you move the caret to a position between 2 words, type “hwat” + space (the string is auto-corrected to “what”), then type “hwat” + space again, the auto-correct doesn’t work. The range.startOffset variable seems incorrect (too small) and subtracting incorrectTxt.length (4) yields a negative start offset.
  • Using a keyup event instead of a keydown event, and checking for the string “hwat ” instead yields different behaviors in Firefox and Chrome. Firefox preserves the space after the corrected string, and the caret is at the position after the space. However, Chrome strips the space and the caret is after the corrected string.
  • After the selection’s range is altered after auto-correcting, Chrome requires the removeAllRanges(), addRange() calls to replace the selection’s range, but Firefox does not.

Notes on web workers

Recently I’ve been working on parallelizing some HTML5 canvas image processing code with web workers and hit a few stumbling blocks, mostly due to some unexpected limits and misunderstandings about workers. Below are some notes I’ve jotted down while working.

  • There’s a limit on the number of web workers being executed, with Firefox I hit a limit of 20.
  • With Firefox, there’s no warning when you hit the limit, your worker simply doesn’t run. There’s no queuing with workers either, so you can’t fire off a worker and expect the browser to schedule it to run when you have less than the maximum number of workers executing.
  • I attempted to implement my own queuing mechanism, but this was a waste of time. You can’t precisely predict when the browser will delete the worker. I tried posting a message from the worker thread to the caller (via postMessage) at the end of the worker onmessage processing function, but this only accurately signals that the processing is done, not that the worker is terminated, and you therefore can’t predict when it’s possible to spawn off another worker.
    EDIT: Experimenting a bit more, I’ve discovered this to be incorrect. To actually terminate the worker, you can use the close method from within the worker, or the terminate method from the caller, so a queuing mechanism is a feasible solution to deal with browser limits on the number of executing web workers.
  • When posting a message from worker to caller (via postMessage), the handler function within the caller seems to be executed within the worker thread.
  • You can’t pass a function to a worker. You pass data between worker and caller by copying the data (serializing/de-serializing an object by structured cloning) or giving ownership of the object to the worker (transferable objects). This allows for a great deal of thread-safety, but in a language like Javascript where functions are first-class citizens, it’s hard not to see the elegance of being able to simply spawn off a worker from a function.
  • Given an upper limit on the number of executing web workers, a spawn-and-forget (or spawn-and-wait) model for concurrency is not practical. A thread pool pattern may be appropriate. In general, each worker should be thought of as a heavy-weight processing engine.
    EDIT:This is perhaps more true not because of browser limits on the number of workers, but the fact that workers were designed with the expectation of them being long-lived, with high performance and memory costs.

All my work was in Firefox, so the points above may not necessarily be true for other browsers.

No SMIL+SVG in Internet Explorer

After noticing the SMIL animation in my previous post on SVG animations not working in Internet Explorer 10, I did a bit of digging to see what level of support was offered by IE10 and IE9 (previous versions do not support SVG). Simply put, Internet Explorer does not support SMIL animation of SVG. An entry from IEBlog regarding the an IE9 platform preview explains why:

… support for SMIL animation of SVG in the web development community is far from strong. The leader of the SVG standardization effort wrote that not supporting SMIL in its current state is probably best “since the SVG WG intends to coordinate with the CSS WG to make some changes to animation and to extend filters.” There’s already work started to reconcile CSS3 animations and SVG.

I’m not shedding any tears. That said, I’m not that enthusiastic about CSS3 animations for SVG either, as CSS3 animations bring with them the same loss of flexibility as SMIL. The current, flexible, cross-browser solution for SVG animation is Javascript, and I can’t see why that’s not a worthwhile solution for the foreseeable future as well.

SVG Animations

I decided to play around a bit with animating SVG content. There are actually multiple ways to animate SVG: CSS (transition, transform, @keyframes), Javascript, or Synchronized Multimedia Integration Language (SMIL). Where possible, I tend to prefer Javascript, as you have far more flexibility compared to markup languages; however, out of curiosity, I did try my hand at SMIL as well.

Method 1: Javascript

Here’s the SVG markup for the object being animated.

<svg version="1.1" x="0px" y="0px" width="74px" height="74px" viewBox="-1.751 -1.751 74 74" enable-background="new -1.751 -1.751 74 74" xml:space="preserve">
    <
g id="circularMarker" transform="rotate(0 35.163 35.521)">                
        <
path fill="#27AAE1" d="M46.336,7.908c2.174,0.678,4.236,1.538,6.199,2.54V3.69c-1.417-0.607-2.875-1.146-4.377-1.616
            C37.794-1.161,27.147-0.491,17.793,3.242v6.663C26.41,5.825,36.518,4.843,46.336,7.908z"/>
        <
path fill="#27AAE1" d="M62.423,46.338c-0.679,2.173-1.537,4.236-2.538,6.199h6.757c0.605-1.417,1.147-2.877,1.615-4.382
            c3.235-10.364,2.564-21.006-1.167-30.362h-6.664C64.505,26.41,65.489,36.52,62.423,46.338z"/>
        <
path fill="#27AAE1" d="M23.992,62.42c-2.171-0.678-4.236-1.536-6.199-2.538v6.759c1.418,0.604,2.877,1.146,4.381,1.616
            c10.364,3.233,21.009,2.564,30.362-1.17v-6.664C43.921,64.505,33.81,65.488,23.992,62.42z"/>
        <
path fill="#27AAE1" d="M7.909,23.994c0.678-2.174,1.538-4.237,2.538-6.2H3.691c-0.606,1.416-1.147,2.878-1.617,4.38
            c-3.234,10.364-2.564,21.012,1.168,30.365h6.664C5.825,43.921,4.843,33.813,7.909,23.994z"/>
        <
circle fill="#00AEEF" cx="35.163" cy="35.521" r="11.331"/>
    </
g>
</
svg>    

To animate, the transform attribute on the #circularMarker group element is updated every frame to do a simple rotation at a rate of 0.275 deg/ms. You can see the result in the iframe below.

Here’s the Javascript code that makes it happen:

<script src="jquery-1.8.2.min.js" type="text/javascript"></script>
<
script type="text/javascript">

// setup window.requestAnimationFrame
(function ()
{
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                                 window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();


// set initial frame time (in milliseconds)
var ft1 = new Date().getTime();

// set initial angle (in degrees)
var angleDeg = 0;

// function to rotate circle
function rotateCircularMarker()
{
var ft = new Date().getTime();
var ftDelta = ft - ft1;
ft1 = ft;

// rotate at a rate of 0.275 deg/ms
angleDeg += ftDelta * 0.275;

if (angleDeg >= 360) {
// full circle!, reset angleDeg
angleDeg = 0;
}

// transform the #circularMarker group
// Note: the rotation is about the center of the circle element (35.163, 35.521)
$('#circularMarker').attr('transform', 'rotate(' + angleDeg + ' 35.163 35.521)');

// call requestAnimationFrame to continue animating
requestAnimationFrame(rotateCircularMarker);
}

$(document).ready(
function ()
{

rotateCircularMarker();

});
            
</script>

Note that window.requestAnimationFrame is used, so a modern browser is required. For older browsers it is possible to use window.setInterval as a fallback.

Method 2: SMIL

With SMIL, the SVG code remains the same, with the exception of an animateTransform tag within the #circularMarker group element.

<animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 35.163 35.521" to="360 35.163 35.521" begin="0s" dur="1.336996s" repeatCount="indefinite"/>

The attributes of animateTransform describe the animation, mainly type, from, to, begin, dur, and repeatCount.

Obviously SMIL yields less code and removes all Javascript dependencies, but it does come at the cost of losing flexibility (as you can only perform transformations and timing operations supported by SMIL attributes) and having to learn yet another markup language.

jxNotify

I made a little JavaScript notification system, somewhat inspired by webOS and also by the type of notifications you see on Gmail.

jxNotify code

The central idea was to have an elegant system that could sensibly display the progress of AJAX operations; meaning notifications stays up while the operation is being done (i.e. while sending request and waiting for a reply from the server), then a success or failure message is posted upon completion, which fades away automatically.

While designed for AJAX calls, this could certainly be used in other cases as well.

jxNotify notifyPre

jxNotify notifyPostError


Initializing

// optional argument = icon, recommended size of 18x18
jxNotify.init('jx-notify.content/sdotspott-notify-icon.png');


Notify of operation in progress (notifyPre)

jxNotify.notifyPre('doing stuff...');


Notify of operation completed (notifyPost)

jxNotify.notifyPost('finished!');


Notify of operation failure (notifyPostError)

jxNotify.notifyPostError('something bad happened!');

 

Reflex Feedback 0.3

A small update to the Reflex Feedback Widget,

  • Design tweaks (border, box-shadow, border-radius on textarea; padding-top on copyright)
  • widgetPos argument added to init(), valid values => ‘left’, ‘right’

reflex feedback widget 0.3

So you can now position the widget on the right edge of the page like this:

Reflex.init($('body'), 'controller/post_feedback.php', 'right');

More info about the widget itself can be found in the original blog post.

dotspott public pages

New feature added to dotspott earlier this week: you can now create a public URL for any of your spotts, which will generate a page that looks something like the following,

dotspott public page, 63 bites

… and is accessible to anyone on the web.

Currently this can only be done from the web client:

  • Login
  • Click “Share…” on the submenu for the spott you want to make a link for
  • Click the “Make Public” button; the spott will be made public and the URL will appear

Note: All spotts are private by default; they can only be seen by you when you login or by someone with whom you’ve shared the spott info via email.

Edit: I should mention this is a proof of concept of Unity. There are actually no async calls beyond those for Google Maps.