Archive for the '(x)HTML(5)' Category

getUserMedia in Opera 12

Thursday, June 14th, 2012

Opera 12 wins - the first stable desktop browser to ship getUserMedia(). I believe they had shipped it already in a mobile version of the browser.

(I'll need to fix my example from last night because Chrome Canary uses webkitGetUserMedia and also uses a stream URL to assign to video element's src)

You can start playing with it right now in Opera, no need to install nightly versions and enable secret flags. Here's how:

1. Install Opera
2. Load any page
3. Press Command + Alt + I (on a Mac) to bring the Dragonfly console (like Firebug)
4. Type:

document.body.innerHTML = '<video autoplay>';

and then

navigator.getUserMedia(
  {video:true}, 
  function(stream){document.getElementsByTagName('video')[0].src = stream}
)

This is it, enjoy :)

Ever so slightly disappointed that {audio: true} gives a "not supported" error

 

Canvas pixels #3: getUserMedia

Wednesday, June 13th, 2012

getUserMedia() is a proposal for one of the most desired device APIs that can give HTML and JS access to the user's camera and microphone. It's already available in Chrome Canary in the form of navigator.webkitGetUserMedia(). It's also available in Opera without a prefix.

In part #1 of this miniseries I talked about manipulating pixels in canvas, let's do the same but this time using video data from your own webcam instead of a static image.

Demo

I've only tested in Chrome so if you want to see the demo, you need to:
1. install Canary
2. go to chrome://flags and Enable PeerConnection

If you don't want to go through the trouble, here's a snapshot of what you're missing: a little video element showing your video camera stream and 4 canvas elements where the image data is manipulated in some way.

And the demo

Hooking up the cam

Getting a video stream is pretty straightforward:

navigator.webkitGetUserMedia(
  {video: true},
  iCanHazStream,
  miserableFailure
);

You declare what type of media you want (video in this case) and provide success and failure callbacks. The browser then prompts the user to allow access:

If the user allows, your success callback is called.

Here's mine:

function iCanHazStream(stream) {
  var url = webkitURL.createObjectURL(stream);
  $('video').src = url;
  webkitRequestAnimationFrame(paintOnCanvas);
}

I have a <video id="video"> element on the page and I set its src to be the stream URL. (In Opera you assign the stream directly, not some made up URL. In webkit the URL ends up being something like http://www.phpied.com/files/canvas/blob:http%3A//www.phpied.com/c0d155b9-f4f8-4c4f-b2bc-694de68d74f2. Anyway, not terribly important)

So this is all you need to do in order to display the camera stream in a VIDEO element. Easy right?

Then I have another function paintOnCanvas() which I schedule with the new requestAnimationFrame hotness instead of old school setInterval()

Setting up the canvas

For the image manipulation I'm using the same CanvasImage() constructor from part #1.

During page load I initialize 4 canvas elements with a placeholder image.

var transformadores = [
  new CanvasImage($('canvas1'), 'color-bars.png'),
  new CanvasImage($('canvas2'), 'color-bars.png'),
  new CanvasImage($('canvas3'), 'color-bars.png'),
  new CanvasImage($('canvas4'), 'color-bars.png')
];

And I have 4 simple pixel manipulators like you saw already:

var manipuladors = [
  {
    name: 'negative',
    cb: function(r, g, b) {
      return [255 - r, 255 - g, 255 - b, 255];
    }
  },
  {
    name: 'max blue',
    cb: function(r, g, b) {
      return [r, g, 255, 255];
    }
  },
  {
    name: 'max red',
    cb: function(r, g, b) {
      return [255, g, b, 255];
    }
  },
  {
    name: 'noise',
    cb: function(r, g, b) {
      var rand =  (0.5 - Math.random()) * 50;
      return [r + rand, g + rand, b + rand, 255];
    },
    factor: '(0 - 500+)'
  }
];

Painting on the canvas

Finally, the paintOnCanvas() function. Here's what happens:

function paintOnCanvas() {
  var transformador = transformadores[0];
  transformador.context.drawImage(
    $('video'), 0, 0, 
    transformador.image.width, transformador.image.height
  );
  var data = transformador.getData();
  for (var i = 0; i < 4; i++) {
    transformador = transformadores[i];
    transformador.original = data;
    transformador.transform(manipuladors[i].cb);
  }
  webkitRequestAnimationFrame(paintOnCanvas);
}

First we need to take the image data from the video element and draw it on a canvas. Then read the image data from the canvas, play with it and paint it back. This seems like a hassle, there might be an easier way to get image data from the video or the stream without going stream-video-canvas, but I don't know it. In any event I only do it once for the first canvas, then remember this data and use it for all the 4 canvases.

It's surprisingly easy to draw a video data in canvas, just using context.drawImage(video_dom_element, ...). From there I read the image data into data and loop through the 4 canvas instances, transforming the image using one of the manipulators I have set up.

Once again, for your entertainment, the demo is right here.

 

Canvas pixels #2: convolution matrix

Monday, June 11th, 2012

In the previous post I talked about manipulating and changing pixels in an image (using JavaScript and canvas) one at a time. We took a single pixel and messed around with its R, G, B or A values.

This time let's look into taking account not only the single pixel but the pixels around it. This allows you to do all kinds of effects, the most popular being emboss, edge detection, blur and sharpen.

The demo page is here

Theory

The type of manipulation we'll consider is called image convolution using a 3x3 matrix. You take 9 pixels from the image: the current pixel you're changing and the 8 immediately around it.

In other words you want to change the RGB values for the pixel in the middle based on its own value and those around it.

Let's say we have some sample values (given in red for R, blue for B and green for G in this figure):

Remember this manipulation was called convolution matrix. So you need a matrix. Below is an example of one such matrix (used in the blur effect)

1,2,1,2,4,2,1,2,

Now you take one of the channels, say R for example. You take each of the 9 R values you have and multiply it by the corresponding number in the matrix. Then sum the nine numbers.

1,2,1,2,4,2,1,2,

1*1 + 2*2 + 5*1 + 11*2 + 10*4 + 20*2 + 1*1 + 10*2 + 1*1 =
 1  +  4  + 5   +   22 +  40  +  40  +  1  +  20  +  1  =
                      134 

In addition to the matrix we also have a divisor and an offset, both optional. If there's no divisor (meaning it's 1, not 0), the result for Red we're looking for is 134. As you can see 134 is pretty far off from the original value of 10. But the blur effect has a divisor of 16. So the new value for red is 8.375

If the convolution asked for an offset, you add it to the end result.

Then you repeat the same for Green and Blue. You can do alpha if you want but for regular images it has constant 255 value so you'll do a lot of math and end up with 255.

You may have noticed that the divisor 16 is also the sum of the numbers in the matrix;

1 + 2 + 1 + 2 + 4 + 2 + 1 + 2 + 1 = 16

This way the result image is as bright as the original. If you have an unbalanced matrix you'll get a darker or a lighter image.

The offset is 0 most of the time, but not always. The emboss effect has offset 127 for example.

Demo matrices

My demo uses the most popular matrices out there. You can search the web for other matrices and play with them. None of them define a divisor because it's the sum of their elements, but the API I'll show you lets you use your custom divisor.

Without further ado, here are the matrices I used defined as an array of JavaScript objects:

var matrices = [
  {
    name: 'mean removal (sharpen)',
    data:
     [[-1, -1, -1],
      [-1,  9, -1],
      [-1, -1, -1]]
  },
  {
    name: 'sharpen',
    data:
     [[ 0, -2,  0],
      [-2, 11, -2],
      [ 0, -2,  0]]
  },
  {
    name: 'blur',
    data:
     [[ 1,  2,  1],
      [ 2,  4,  2],
      [ 1,  2,  1]]
  },
  {
    name: 'emboss',
    data:
     [[ 2,  0,  0],
      [ 0, -1,  0],
      [ 0,  0, -1]],
    offset: 127,
  },
  {
    name: 'emboss subtle',
    data:
     [[ 1,  1, -1],
      [ 1,  3, -1],
      [ 1, -1, -1]],
  },
  {
    name: 'edge detect',
    data:
     [[ 1,  1,  1],
      [ 1, -7,  1],
      [ 1,  1,  1]],
  },
  {
    name: 'edge detect 2',
    data:
     [[-5,  0,  0],
      [ 0,  0,  0],
      [ 0,  0,  5]],
  }
];

Results

Original

Blur

Sharpen

Edge detect

Edge 2

Emboss

Emboss (subtle)

Mean removal (sharpen a lot)

The API

The API is the same as in the previous post, same constructor and all, just adding a new method called convolve(). This is where the magic happens.

You use this method like so:

transformador.convolve([
  [1,2,1],
  [2,4,2],
  [1,2,1]
], 16, 0);

Again, 16 is optional as the method will figure it out if you omit and offset is optional too. Actually you can go to the demo and play in the console to see what happens with a different divisor, e.g.

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 10);

or

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 20);

convolve()

Some comments on how convolve() was implemented in this demo.

The big picture:

CanvasImage.prototype.convolve = function(matrix, divisor, offset) {
  // ...
};

Handle arguments: flat matrix is easier to work with and figure out the divisor if missing. How 'bout that array reduce, eh? ES5 ftw.

  var m = [].concat(matrix[0], matrix[1], matrix[2]); // flatten
  if (!divisor) {
    divisor = m.reduce(function(a, b) {return a + b;}) || 1; // sum
  }

Some vars more or less the same as the last time in the transform() method:

  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var len = newpx.length;
  var res = 0;
  var w = this.image.width;

Then a loop through all the image data, filter out every 4th element (because we ignore Alpha channel) and write the new image data to the canvas.

  for (var i = 0; i < len; i++) {
    if ((i + 1) % 4 === 0) {
      newpx[i] = oldpx[i];
      continue;
    }
 
    // 
    // magic...
    //
  }
  this.setData(newdata);

Remember that canvas image data is one long array where 0 is R for pixel #1, 1 is B, 2 is G, 3 is Alpha, 4 is R for pixel #2 and so on. This is different than more other code examples you'll in different languages where there are two loops in order to touch every pixel: one from 0 to width and an inner one from 0 to height.

And finally, the "magic" part:

    res = 0;
    var these = [
      oldpx[i - w * 4 - 4] || oldpx[i],
      oldpx[i - w * 4]     || oldpx[i],
      oldpx[i - w * 4 + 4] || oldpx[i],
      oldpx[i - 4]         || oldpx[i],
      oldpx[i],
      oldpx[i + 4]         || oldpx[i],
      oldpx[i + w * 4 - 4] || oldpx[i],
      oldpx[i + w * 4]     || oldpx[i],
      oldpx[i + w * 4 + 4] || oldpx[i]
    ];
    for (var j = 0; j < 9; j++) {
      res += these[j] * m[j];
    }
    res /= divisor;
    if (offset) {
      res += offset;
    }
    newpx[i] = res;

these are the pixels we want to inspect. oldpx[i] is the one in the middle which we're changing to newpx[i]. Also note how we default all pixels to oldpx[i]. This is to deal with the boundary pixels: tho top and bottom rows of pixels and the left and right columns. Because the pixel in position 0x0 has no pixels above it or to the left. Then we loop through these and multiply by the corresponding value in the matrix. Finally divide and offset, if required.

Thanks!

Thanks for reading, and now go play with the demo in the console. An easy template to start is:

transformador.convolve([[1,0,0],[0,0,0],[0,0,-1]], 1, 127); 

If you want to apply convolutions on top of each other, you can reset the original image data to the current.

transformador.original = transformador.getData();
 

Pixel manipulation in canvas

Sunday, June 10th, 2012

Done it before with PHP, but now that JavaScript is all-powerful, let's see how we can manipulate images in an HTML <canvas>.

The demo is here.

Pixel manipulation

The simplest way to fiddle with image data is to take each pixel and change the value of one or more of its channels: red, green, blue and alpha (transparency), also known as R, G, B and A for short. The results may vary from mostly useless to "wow, that sepia was easy, I'm totally doing it for all my thumbnails!"

A trivial example is to just swap some values. E.g. take the amount of B and assign it to G.

rgb(100, 50, 30, 255) becomes
rgb(100, 30, 50, 255)

Callbacks

For the purpose of this demo I've isolated the part that deals with the canvas API (loading an image file, reading and writing pixels) from the actual pixel manipulation. The manipulation itself is in the form of simple callback functions.

The example above (B to G and G to B) is written as

function (r, g, b) {
  return [r, b, g, 255];
}

Here we ignore alpha, because we don't need it and hardcode it to 255. Here's the result:

from the original:

Let's say you want to touch alpha and make an image partially transparent. Here's the callback:

function (r, g, b, a, factor) {
  return [r, g, b, factor];
}

Here we let the user specify a factor in other words how transparent should the image be. We use this as a value for the alpha channel.

The result of calling this with factor of 111:

And the most complicated form of callback is taking into account where this pixel is in the overall image. Here's a gradient:

function (r, g, b, a, factor, i) {
  var total = this.original.data.length;  
  return [r, g, b, factor + 255 * (total - i) / total];
}

And the result of calling this with factor of 111:

this refers to an object of our own design which you'll see in a bit. It keeps some info in case the callbacks need as is the case above.

The canvas part

For the canvas stuff I started by copying an old post to load an image into a canvas. This constructor was born:

function CanvasImage(canvas, src) {
  // load image in canvas
  var context = canvas.getContext('2d');
  var i = new Image();
  var that = this;
  i.onload = function(){
    canvas.width = i.width;
    canvas.height = i.height;
    context.drawImage(i, 0, 0, i.width, i.height);
 
    // remember the original pixels
    that.original = that.getData();
  };
  i.src = src;
  
  // cache these
  this.context = context;
  this.image = i;
}

You use this by passing a reference to a canvas element somewhere on the page and a URL to an image. The image has to be on the same domain for the image data part to work.

var transformador = new CanvasImage(
  $('canvas'),
  '/wp-content/uploads/2008/05/zlati-nathalie.jpg'
);

The constructor creates a new Image object and once it's loaded it draws it into the canvas. Then it remembers some stuff for later such as the canvas context, the image object and the original image data. `this` is the same `this` that pixel manipulator callbacks have access to.

Then you have three simple methods to get, set and reset the image data into the canvas:

CanvasImage.prototype.getData = function() {
  return this.context.getImageData(0, 0, this.image.width, this.image.height);
};
 
CanvasImage.prototype.setData = function(data) {
  return this.context.putImageData(data, 0, 0);
};
 
CanvasImage.prototype.reset = function() {
  this.setData(this.original);
}

The "brains" of the whole thing is the transform() method. It takes the pixel manipulator callback and a factor which is essentially a configuration setting for the manipulator. Then it loops through all pixels, passes olddata rgba channel values to the callback and uses the return values from the callback as newdata. At the end the new data is written back to canvas.

CanvasImage.prototype.transform = function(fn, factor) {
  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var res = [];
  var len = newpx.length;
  for (var i = 0; i < len; i += 4) {
   res = fn.call(this, oldpx[i], oldpx[i+1], oldpx[i+2], oldpx[i+3], factor, i);
   newpx[i]   = res[0]; // r
   newpx[i+1] = res[1]; // g
   newpx[i+2] = res[2]; // b
   newpx[i+3] = res[3]; // a
  }
  this.setData(newdata);
};

Pretty simple, right? The only confusing part could be the i += 4 increment in the loop. See, the data returned by canvas' getImageData().data is an array with 4 elements for each pixel.

Imagine an image with only two pixels - red and blue and no transparency. The data for this image is

[
  255, 0, 0, 255,
  0, 0, 255, 255
]

And this is it. You use the objects created by ImageCanvas constructor like so:

transformador.transform(function(r, g, b, a, factor, i) {
  // image magic here
  return [r, g, b, a];
}, factor);

Feel free to play in the console with this on the demo page

Callbacks (contd.)

The rest of the code for the demo is just the setting up pixel-fiddling callbacks and creating some UI to use them. Let's check out a few.

Greyscale

Grey is equal amounts of r, g, b. So you can simply average the three values to get a single value:

var agv = (r + g + b) / 3;

This is good enough, but there's a secret formula that does a better job for humans, because it takes into account our species' sensitivity to the different channels. The result:

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg, avg, avg, 255];
}

Sepia

A lazy sepia is to make a greyscale version and then add some color to it - equal amounts of rgb to each pixel. I add 100 to red and 50 to green and I like it, but you can play with different values.

function(r, g, b) {
  var avg = 0.3  * r + 0.59 * g + 0.11 * b;
  return [avg + 100, avg + 50, avg, 255];
}

There's a second sepia callback which is supposed to be better, but I don't like it as much.

Negative (invert)

Subtracting each channel from 255 gives you a negated image:

function(r, g, b) {
  return [255 - r, 255 - g, 255 - b, 255];
}

Noise

Noise is kind of fun, you take a random value between -factor and factor and add it to each channel:

function(r, g, b, a, factor) {
  var rand =  (0.5 - Math.random()) * factor;
  return [r + rand, g + rand, b + rand, 255];
}

Noise with factor of 55:

Your turn

I'm leaving some of these manipulator callbacks for your source exploration and then to your imagination. What can you think of?

Go nuts with those callbacks in the console! Your basic "template" again is:

transformador.transform(function(r, g, b, a, factor, i) {
  // image magic here...
  return [r, g, b, a];
});

Try e.g. - blue to 255. Or make it black and white (not greyscale, make each pixel either 0,0,0 or 255,255,255)

 

x-webkit-speech input and textareas

Saturday, August 6th, 2011

New hotness, speech input. (The demo, the demo)

You get an input:

<input>

You add an x-webkit-speech attribute

<input x-webkit-speech>

And voila!

Or if you don't have a recent Chrome version, here's what these who have a recent Chrome version see:

Nice.

Textareas

Speech inputs - gotta love them. But in textareas - you can't have them. So you can put an input and a textarea and copy over the content.

html:

<textarea id="txt"></textarea>
<input x-webkit-speech id="mike"/>

With some CSS you can remove the border, add pointy cursor, etc make it look like not an input.

#mike {
    font-size: 25px;
    width: 25px;
    height: 25px;
    cursor:pointer;
    border: none;
    position: absolute;
    margin-left: 5px;
    outline: none;
    background: transparent;
}
#txt {
    height: 150px;
    width: 150px;
}

Finally a little JavaScript to a/ unfocus the input and b/ carry over the content from the input to the textarea

var mike = document.getElementById('mike');
mike.onfocus = mike.blur;
mike.onwebkitspeechchange = function(e) {
    //console.log(e); // SpeechInputEvent
    document.getElementById('txt').value = mike.value;  
};

That's all there is. Click over to the demo.

Here's what you should see in Chrome:

You can see that console.log() there - it's definitelly worth exploring. The thing is a SpeechInputEvent type of object is passed to the handler of the onwebkitspeechchange event. This object has some interesting properties, such as the array results[] which is number of guesses what you've said and confidence (0 to 1 it seems) in the guess.

Enjoy and go ahead and add these all over the place! :)

 

Lazy HTML evaluation

Wednesday, June 8th, 2011

#7 This post is part of the Velocity countdown series. Stay tuned for the articles to come.

Some time ago Google talked about using a sort of lazy JavaScript evaluation which especially helps mobile devices. The idea was to comment out a chunk of JavaScript you don't need right away and serve it this way. Later, when you need it, you get the content of the commented code and eval() it. More here and here.

At the last Fronteers conference I had the pleasure of chatting with Sergey Chikuyonok, who is so great and (among other things) is responsible for coming up with zen coding and writing a bunch of deep articles on image optimization for Smashing Magazine. So he told me he experimented with similar lazy HTML evaluation and it proved to be incredibly helpful for mobile devices. Not only the overall experience is faster but the initial rendering happens sooner and we all know how important that is.

Sergey is a busy person and chances of him writing about his experiment in English seemed pretty low at the time, so I decided to do an experiment on my own and see what happens. Meanwhile he did write about it so I forgot all about my findings, but here they are now.

Long document

I took one big HTML document - The adventures of Sherlock Holmes, which is half a megabyte or about 200K gzipped. Page A is the document as-is, plus some JS for measurements.

Page B (lazy) is the same page but with about 95% of its content commented out. The remaining 5% is a whole chapter so there's plenty of time to deal with the rest while the user is reading. After onload and a 0-timeout I take the commented markup (conveniently placed in <div id="lazy-daze">) and strip the comments. Then take the "unwrapped" time after another 0-timeout to let the browser repaint the DOM and regain control.

The overall skeleton of the lazy page is like so:

<!doctype html>
 
<html>
<body>
  <h1>THE ADVENTURES OF<br/>
  SHERLOCK HOLMES</h1>
  ...
  ... to chat this little matter over with you.</p>
 
  <div id="lazy-daze">
  <!--
    <p>II.</p>
    <p>
    At three o’clock precisely ... 
    ... she has met with considerable success.</p>
  -->
  </div>
 
<script>
 
 
window.onload = function () {
 
    setTimeout(function(){
 
        var daze = document.getElementById('lazy-daze'),
            inner = daze.innerHTML;
 
        daze.innerHTML = inner.substring(4, inner.length - 4);
    
        setTimeout(function(){
            // take end time... 
        }, 0);
                
    }, 0);
};
 
</script>
</body></html>

Experiment

All the files are here:
http://www.phpied.com/files/lazyhtml/

We have the plain normal document - http://www.phpied.com/files/lazyhtml/sherlock-plain.html
And the lazy one - http://www.phpied.com/files/lazyhtml/sherlock-lazy.html

In order to run the experiment you just go to
http://www.phpied.com/files/lazyhtml/start.html
And click "Go nuts". This will load each of the two documents 20 times and take a few time measurements. "Go nuts" again and you'll get 20 more data points.

The time measurements I take are:

  • "plain" - unload to onload of the base version
  • "lazy" - unload to onload of the lazy version NOT including unwrapping it. This should be quicker than the plain version
  • "unwrapped" - unload to onload plus time to unwrap and rerender - this is expected to be bigger than "plain" because the browser has to render twice and is therefore doing more work
  • DOM loaded "plain" - unload to DOMContentLoaded instead of onload
  • DOM loaded "lazy"

Then I take the same 5 measurements but instead of starting at unload of the previous page, it starts at the top of the documents, as soon as a timestamp can be taken with JavaScript. This will exclude DNS, establishing connection, time to first byte...

Results

Here are the results from back when I did the experiment originally last year, using iPhone 2 (with iOS 3.2 or thereabouts)

I ran this experiment over Wifi and again over 3G.

First striking thing - it takes the about the same time to load the plain old page over Wifi and over 3G. For the smaller, "lazy" document, there is a difference, but there's virtually none for the plain base page. The guess here is that the rendering and its cost in terms of memory and CPU is far greater than the actual download time. In other words it takes longer to render than it does to download an HTML. At least in this class of phones. This guess is confirmed when you look at the time from the top of the documents, when the request overhead is removed:

With or without the request time - it's all pretty much the same.

The next striking thing - and how about that lazy document! It renders 3-4 times faster than the whole plain document. Not bad.

And one more surprise - lazy+unwrap time is less than the plain old document. Now that's interesting. It appears faster to split the task into two and do the whole double-rendering, which should've been slower because it's extra work. I guess that poor phone really chokes on the long document.

The same I found is true in Firefox, but almost the difference is negligible.

iPhone 4

I repeated the experiment tonight on iPhone 4 and wifi. And boy, is there a difference. What used to take 13 seconds is now under 3s.

The lazy + unwrap time is more than the plain time, which was to be expected.

Rendering that initial lazy document is still 2-3 times faster that waiting for the whole document.

The numbers:

  • 2765 plain (2014 DOM)
  • 1268 lazy
  • 2995 lazy+unwrap

Ignoring the request overhead:

  • 2200 plain (1421 DOM)
  • 715 lazy
  • 2423 lazy+unwrap

And one last run/observation - on the 3G and iPhone 4 there isn't much benefit of lazy-evaluation and empty cache. The request seems much more expensive. unload to onload 4.9s where document top to onload is 2.5. When the request overhead is out of the picture than lazy eval wins again - 1.7s compared to 2.5s

Parting words

  • Lazy HTML FTW?
  • Who the heck loads an entire book in a page?! Well it may happen. It may not be a whole book, but just a lot of markup. The entire book gzipped was 219K. A hefty document, but have you seen some of those news sites?
  • Possible use case - blog comments. Lots and lots of blog comments. Or posts.
  • If you're going to lazy-load something and get it with an ajax request, why not save yourself the request and ship with another chunk of html
  • This was a simple layout task. Just a bunch of text. I'm guessing there could be much more complicated pages and layouts to render. And rendering is what takes the time it seems.
  • Drawbacks a plenty because of the hidden content - accessibility, SEO.

Thoughts? Anyone want to run the test on Android or any other phone/device/tab/pad/whathaveyou? The guess is that the newer/powerful the device the smaller the difference. But it will be nice to know.

 

a DOCTYPE is all it takes

Friday, July 25th, 2008

At a lunch conversation about quirks vs standards mode, the question was raised: but what if you don't have those kinda important tags such as <html> or <body>? is this going to be quirks mode?

Well, the following test case shows that a DOCTYPE is all it takes in order to have standards mode rendering. Was this a Cinderella song: "We all need a little DOCTYPE, just a little helper, ooh and it will be alright" :)

» Run test example

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
 
hello
 
<script type="text/javascript">
alert("Quirks? " + document.compatMode);
</script>
 

When client-only validation is good for business

Sunday, April 13th, 2008
You should never never ever rely on client-side validation only. Client-side validation is for enhancing user experience, server-side is the validation. This is a rule, never to be broken. But here's a funny story how skipping the server-side validation actually helped.

This is a real story, but the actual names have been replaced in XXX, just not to make other people look bad :D

There is this site called xxxxxxxxx.com that charges you $XX membership access. Having just moved from Canada, last year I didn't have a US credit card to pay the fee and tried to use my Canadian visa. Problem: the input field for postal code (zip code) accepts 5 characters only, since the zip codes in US a like 90404, 90066 and so on. A Canadian postal code is like H0H-0H0 or H0H0H0, six characters. So seemed like I couldn't pay online. Or could I?

Checking the source code with Firebug gives me this:

client.png

From here it's trivial to change maxlength attribute of the input. Even with IE it's super easy just to type in the address bar something like:
javascript:document.getElementsByName('XXXXXXX')[0].maxLength = 100;

So I did change it, typed my Canadian CC#, Canadian postal code and submitted the form, crossing fingers that the developers who built the site were too pressed by deadlines to do a proper server-side validation. Lo and behold, it worked!

At the end with the help of an innocent client-side tweak I got what I needed (membership), xxxxxxxx.com got more business, and everybody's happy.

There's a lesson in this: sometimes being too strict in data validation for things that don't matter is just in your way.
And another: don't assume all your potential clients are from US.

 

The Front-end Cerberus

Thursday, October 25th, 2007

Some smart guys picture the distinction of content (HTML), presentation (CSS) and behaviour (JavaScript) as a three-legged stool. This is totally fine, but can't we draw a more heroic picture of today's Front-end developer?

frontend-cerberus.jpg

I found the image here, if anyone knows the original author, let me know so I can give proper credit.

BTW, I never knew what Cerberus meant until 15 minutes ago. The thing is that where I come from, we used this name to refer to some of the teachers that weren't very nice to us at school :)

 

HTML2DOM

Thursday, September 14th, 2006

Here's this HTML-2-DOM service - http://www.html2dom.com What it does is pretty simple - you paste some HTML code and the output is JS script code that uses DOM functions to produce the same result. Could be useful when you're working on an AJAX-style app that generates new content using JavaScript.

I build this simple script, inspired by this great book I was reading - "Build Your Own AJAX Web Applications". In the book, the author sometimes starts with writing up what is the HTML code for the result you want to achieve, and then goes ahead with giving the DOM code. Because, you know, DOM code can be quite verbose and sometimes a bit hard to follow. So I thought, why not write up a simple tool to automate this HTML to DOM transition.

The code is not complicated at all, it just takes the HTML, treats it as an XML document, then loops through all the elements of the XML doc and all the attributes for each element. The script is here, hopefully reusable and you can grab it for your own projects if you wish. You can check the source of html2dom.com's index page for an example how to use the html-2-dom class.

Some limitations of the script (that I know of) are result of the fact that I'm treating the HTML as XML document. So you might get some errors if the HTML you paste is not well-formed, with all closed tags and so on. Also you cannot use &nbsp; and other entities, because XML doesn't know about them. What XML knows is only the pre-defined 5. And last, out of the different node types, my script understands only three - element, attribute and comment. I think it's enough for the practical purposes I was aiming at, even the comment type is a bit of a stretch.

So enjoy and as always, any feedback is welcome!

 

IE script tag problem

Tuesday, October 18th, 2005

Issue:
The page not displayed in IE. "View Source" shows the full source, <title> is displayed OK, but the page is not blank. White screen of death. Nothing.

Cause:

<script type="text/javascript" src="some.js" />

Solution:

<script type="text/javascript" src="some.js"></script>

Comments:
It's probably this whole thing with the current support of XHTML. Most browsers (in their forgiveness for HTML authoring errors) interpret XHTML as HTML with some weirdly placed slashes and other noise. So for example they see <br /> as a slightly broken <br>. More about this here.
In the case above probably IE needs the end </script> tag and because it cannot find it, it treats the whole HTML document as JavaScript, apparently broken. Hence the white page with OK source and OK HTML title.

... And while on the script tag subject, "language" is not a valid XHTML Strict attribute for <script>.