HTML5 Rocks

HTML5 Rocks

The amazing powers of CSS

By Ilmari Heikkinen at

Yesterday at the office, we were coming up with strange and magical CSS tricks. Take this one for instance, it makes empty links very visible:


a[href = ""] {
  background: red;
  color: white;
  font-size: x-large;
}

Check out the live example at jsFiddle

You can also style absolute links differently from relative links:


a[href ^= http] {
  display: inline-block;
  color: red;
  transform: rotate(180deg);
}

Check out the live example at jsFiddle

If you want to have a different style for links pointing out of your domain, you can use the :not() selector. This is actually how we do the little arrows next to external links at HTML5Rocks.


a[href ^= 'http']:not([href *= 'html5rocks.']) {
  background: transparent url(arrow.png) no-repeat center right;
  padding-right: 16px;
}

Check out the live example at jsFiddle

Just to remind you that you’re not limited to styling links, here’s how to make all PNG images inverted:


img[src $= .png] {
  filter: invert(100%);
}

Moving on from attribute selectors, did you know that you can make the document head visible, along with the other elements there?


head {
  display: block;
  border-bottom: 5px solid red;
}
script, style, link {
  display: block;
  white-space: pre;
  font-family: monospace;
}

Or that you can use the awesome power of CSS attr-function to fill in the :after and :before content?


script:before { content: “<script src=\”“ attr(src) “\” type=\”“ attr(type) “\”>”;
}
script:after { content: “</script>”;
}

style:before { content: “<style type=\”“ attr(type) “\”>”;
}
style:after { content: “< /style>”;
}

/* And for a finish, */
link:before { content: “<link rel=\”“ attr(rel) “\” type=\”“ attr(type) “\” href=\”“ attr(href) “\” />”;
}


Check out the live example at jsFiddle

Note that attr() reads in the attribute values of the matching element, so if you use it for #foo:before, it reads the attributes from #foo.

Processing XHR2 file uploads in PHP

By Eric Bidelman at

My article "New Tricks in XMLHttpRequest2" has many fine examples, but what it doesn't have is any server code to illustrate how to handle files. If you're curious how to process a file upload using xhr.send(FormData), here's a quick example of an image upload in PHP.

This server is trivial but it demonstrates two things. The first is sending a file and extra payload at the same time. The second is how to grab the file (and other data) in PHP. Lastly, the image is encoded into a data: URL and included in a JSON response sent back to the client.

Transferable Objects: Lightning Fast!

By Eric Bidelman at

Chrome 13 introduced sending ArrayBuffers to/from a Web Worker using an algorithm called structured cloning. This allowed the postMessage() API to accept messages that were not just strings, but complex types like File, Blob, ArrayBuffer, and JSON objects. Structured cloning is also supported in later versions of Firefox.

Faster is better

Structured cloning is great, but it's still a copy operation. The overhead of passing a 32MB ArrayBuffer to a Worker can be hundreds of milliseconds. New versions of Chrome contain a huge performance improvement for message passing, called Transferable Objects.

With transferable objects, data is transferred from one context to another. It is zero-copy, which vastly improves the performance of sending data to a Worker. Think of it as pass-by-reference if you're from the C/C++ world. However, unlike pass-by-reference, the 'version' from the calling context is no longer available once transferred to the new context. For example, when transferring an ArrayBuffer from your main app to Worker, the original ArrayBuffer is cleared and no longer usable. Its contents are (quiet literally) transferred to the Worker context.

To play with transferables, there's a new version of postMessage() in Chrome/V8 that supports transferable objects:

worker.webkitPostMessage(arrayBuffer, [arrayBuffer]);
window.webkitPostMessage(arrayBuffer, targetOrigin, [arrayBuffer]);

For the worker case, the first argument is the ArrayBuffer message. The second argument is a list of items that should be transferred.

Benchmark demo

To see the performance gains of transferrables, I've put together a demo.

The demo sends a 32MB ArrayBuffer to a worker and back using webkitPostMessage(). If your browser doesn't support transferables, the sample falls back to structured cloning. Averaging 5 runs in different browsers, here's what I got:

On a MacBook Pro/10.6.8/2.53 GHz/Intel Core 2 Duo, FF was the fastest using structured cloning. On average, it took 302ms to send the 32MB ArrayBuffer to a worker and post it back to the main thread (RRT - Round Trip Time). Comparing that with transferables, the same test took 6.6ms. That is a huge perf boost!

Having these kinds of speeds allows massive WebGL textures/meshes to be seamlessly passed between a Worker and main app.

Feature detecting

Feature detecting is a bit tricky with this one. My recommendation is to send a small ArrayBuffer to your worker. If the buffer is transferred and not copied, its .byteLength will go to 0:

worker.postMessage = worker.webkitPostMessage || worker.postMessage;

var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
  alert('Transferables are not supported in your browser!');
} else {
  // Transferables are supported.
}

Support: Currently Chrome 17+

Updated (2011-12-13): Code snippet to show webkitPostMessage() signature is different for window and worker.

"Stream" video using the MediaSource API

By Eric Bidelman at

The MediaSource API extends the HTMLMediaElement to allow JavaScript to generate media streams for playback. Allowing JavaScript to generate streams facilitates a variety of use cases like adaptive streaming and time shifting live streams.

Here is a quick demo and example usage of API:

const NUM_CHUNKS = 5;

var video = document.querySelector('video');
video.src = video.webkitMediaSourceURL;

video.addEventListener('webkitsourceopen', function(e) {
  var chunkSize = Math.ceil(file.size / NUM_CHUNKS);

  // Slice the video into NUM_CHUNKS and append each to the media element.
  for (var i = 0; i < NUM_CHUNKS; ++i) {
    var startByte = chunkSize * i;

    // file is a video file.
    var chunk = file.slice(startByte, startByte + chunkSize);

    var reader = new FileReader();
    reader.onload = (function(idx) {
      return function(e) {
        video.webkitSourceAppend(new Uint8Array(e.target.result));
        logger.log('appending chunk:' + idx);
        if (idx == NUM_CHUNKS - 1) {
          video.webkitSourceEndOfStream(HTMLMediaElement.EOS_NO_ERROR);
        }
      };
    })(i);

    reader.readAsArrayBuffer(chunk);
  }
}, false);

DEMO

The example splits a .webm video into 5 chunks using the File APIs. The entire movie is then 'streamed' to a <video> tag by appending each chunk to the element using the MediaSource API.

If you're interested in learning more about the API, see the specification.

Support: Currently, the MediaSource API is only available in Chrome Dev Channel 17+ with the --enable-media-source flag set or enabled via about:flags.

Let Your Content Do the Talking: Fullscreen API

By Eric Bidelman at

Most browsers have the ability to enter a fullscreen or kiosk mode for a while now. Basically, the browser's chrome UI gets out of the way, and the content takes over. For apps installed from the Chrome Web Store, it's even been possible for users to manually configure an app to run fullscreen when it's opened from the New Tab Page. Manual fullscreen is good. Programmatic fullscreen is better!

The Fullscreen API allows web apps to programmatically tell any content on the page to enter the browser's fullscreen viewing mode, from JavaScript. This means WebGL and <canvas> games can finally become fully immersive, videos can feel like the silver screen, and online magazines can feel like the real deal. I love the browser, but we shouldn't be constrained by it :)

If you want to skip the details, here's a demo:

So how does the API work? If you wanted a <div>, for example, to go fullscreen, simple tell it to:

div.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
div.mozRequestFullScreen();
div.msRequestFullscreen();
div.requestFullscreen(); // standard

Then to exit fullscreen, the document exposes a method for that:

document.webkitExitFullscreen();
document.mozCancelFullscreen();
document.msExitFullscreen();
document.exitFullscreen();

Note: the exitFullscreen() is the spec version. However, both FF and WebKit still expose prefixed cancelFullScreen().

Content in fullscreen-mode is centered in the browsers viewport. It's left to the developer to style that content in the most appropriate way for viewing. Typically, you'll want your <div> to take up the entire screen real-estate. Good news is that the API includes new CSS pseudo-selectors for this:

div:-webkit-full-screen {
  width: 100% !important;
}
div:-moz-full-screen {
  width: 100% !important;
}
div:-ms-fullscreen {
  width: 100% !important;
}
div:fullscreen {
  width: 100% !important;
}

/* While in fullscreen, hide any children with class 'tohide' */   
:-webkit-full-screen .tohide {
  display: none;
}
:-moz-full-screen .tohide {
  display: none;
}
:-ms-fullscreen .tohide {
  display: none;
}
:fullscreen .tohide {
  display: none;
}

CSS pseudo-selectors make it very easy to style fullscreen content any way you want.

The Fullscreen API is enabled by default in Chrome 15, FF 14, IE 11 and Opera 12.1. For more information on the rest of the API, see the spec.

Updated 2012-10-11: to be inline with spec changes. Lowercased the "S" in requestFullscreen() and changed document.webkitCancelFullScreen() to document.webkitExitFullscreen(). Updated browser compatibility comment.

Updated 2014-02-11: to include prefixes for IE, add the standard CSS syntax, and update the browser compatibility comment. Thanks @dstorey and
@patrickkettner.

Workers ♥ ArrayBuffer

By Eric Bidelman at

As of crbug.com/73313, Chrome 13 and FF5 support sending an ArrayBuffer (or Typed Array) to/from a Web Worker. For example:

worker.js

self.onmessage = function(e) {
  var uInt8Array = e.data;
  postMessage("Inside worker.js: uInt8Array.toString() = " + uInt8Array.toString());
  postMessage("Inside worker.js: uInt8Array.byteLength = " + uInt8Array.byteLength);
};

main.html

var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}

console.log('uInt8Array.toString() = ' + uInt8Array.toString());
console.log('uInt8Array.byteLength = ' + uInt8Array.byteLength);

worker.postMessage(uInt8Array);

Why is this exciting?...binary data!

Instead of the browser serializing your postMessage() data to a JSON object, it uses the structured clone algorithm to copy the ArrayBuffer to the worker's context, and vice versa. This opens up a real potential for workers that we haven't seen before. That is, being able to easily pass binary data between main app and worker thread.

Typed array I/O makes intense image manipulation, sound processing, and heavy webgl calculations much more feasible. For example, one could read a File as an array buffer or fetch a Blob using XHR2 and pass the result directly to a worker. No more base64 encoding the data :)

In my opinion this is one of those nitpicks workers should have included from the start. It just makes sense.

insertAdjacentHTML Everywhere

By Ernest Delgado at

If we want to insert content in a HTML document we have three ways to do it:

  • Using DOM methods like createNode and appendChild
  • Using Document Fragments
  • Using innerHTML

One can arguably say we also have document.write for few use cases.

innerHTML has been standarized in HTML5 and with it a brother method insertAdjacentHTML which works as innerHTML but allows us to define more specifically where we want to insert the HTML content: beforeBegin, afterBegin, beforeEnd and afterEnd.

var ul = document.getElementById("list");
ul.insertAdjacentHTML("beforeEnd", "<li>A new li on the list.</li>");

Back in 2008 John Resig wrote an article about insertAdjacentHTML with this conclusion:

Having browsers implement this method will dramatically reduce the amount of code needed to write a respectable JavaScript library. I'm looking forward to the day in which this method is more-widely available (along with querySelectorAll) so that we can really buckle down and do some serious code simplification.

Until now, the main issue with insertAdjacentHTML has been its lack of browser support. With Firefox implementing insertAdjacentHTML as of version 8, it will available in all major browsers including mobile browsers. If you want to use it now and make sure it works in Firefox versions earlier than 8 you can use this polyfill.

Saving generated files on the client-side

By Eli Grey at

Have you ever wanted to add a Save as… button to a webapp? Whether you're making an advanced WebGL-powered CAD webapp and want to save 3D object files or you just want to save plain text files in a simple Markdown text editor, saving files in the browser has always been a tricky business. Usually when you want to save a file generated with JavaScript, you have to send the data to your server and then return the data right back with a Content-disposition: attachment header. This is less than ideal for webapps that need to work offline. The W3C File API includes a FileSaver interface, which makes saving generated data as easy as saveAs(data, filename), though unfortunately it will eventually be removed from the spec. I have written a JavaScript library called FileSaver.js, which implements FileSaver in all modern browsers. Now that it's possible to generate any type of file you want right in the browser, document editors can have an instant save button that doesn't rely on an online connection. When paired with the standard HTML5 canvas.toBlob() method, FileSaver.js lets you save canvases instantly and give them filenames, which is very useful for HTML5 image editing webapps. For browsers that don't yet support canvas.toBlob(), Devin Samarin and I wrote canvas-toBlob.js. Saving a canvas is as simple as running the following code:

canvas.toBlob(function(blob) {
  saveAs(blob, filename);
});

I have created a demo of FileSaver.js in action that demonstrates saving a canvas doodle, plain text, and rich text. Please note that saving with custom filenames is only supported in browsers that either natively support FileSaver or browsers like Google Chrome 14 dev and Google Chrome Canary, that support <a>.download or web filesystems via LocalFileSystem.

View FileSaver.js demo

How to construct files for saving

First off, you want to instantiate a BlobBuilder. The BlobBuilder API isn't supported in all current browsers, so I made BlobBuilder.js which implements it. The following example illustrates how to save an XHTML document with saveAs().

var bb = new BlobBuilder();
bb.append((new XMLSerializer).serializeToString(document));
var blob = bb.getBlob("application/xhtml+xml;charset=" + document.characterSet);
saveAs(blob, "document.xhtml");

Not saving textual data? You can append binary Blobs and ArrayBuffers to a BlobBuilder too! The following is an example of setting generating some binary data and saving it.

var bb = new BlobBuilder();
var buffer = new ArrayBuffer(8); // allocates 8 bytes
var data = new DataView(buffer);

// You can write (u)int8/16/32s and float32/64s to dataviews
data.setUint8 (0, 0x01);
data.setUint16(1, 0x2345);
data.setUint32(3, 0x6789ABCD);
data.setUint8 (7, 0xEF);

bb.append(buffer);
var blob = bb.getBlob("example/binary");
saveAs(blob, "data.dat");
// The contents of data.dat are <01 23 45 67 89 AB CD EF>

If you're generating large files, you can implement an abort button that aborts the FileSaver.

var filesaver = new FileSaver(blob, "video.webm");
abort_button.addEventListener("click", function() {
  filesaver.abort();
}, false);

Seek into local files with the File System API

By Seth Ladd at

If you have a File object (say, one stored using the FileSystem API), it's possible to seek into it and read chunks without reading the entire file into memory:

var url = "filesystem:http://example.com/temporary/myfile.zip";

window.webkitResolveLocalFileSystemURL(url, function(fileEntry) {
  fileEntry.file(function(file) {
    var reader = new FileReader();

    reader.onload = function(e) {
      var ab = e.target.result; // arrayBuffer containing bytes 0-10 of file.
      var uInt8Arr = new Uint8Array(ab);
      ...
    };

    var blob = file.webkitSlice(0, 10, "application/zip");  // mimetype is optional
    reader.readAsArrayBuffer(blob);
  }, errorHandler);
}, errorHandler);

Downloading resources in HTML5: a[download]

By Eric Bidelman at

Chrome now supports the HTML spec's new download attribute to a elements. When used, this attribute signifies that the resource it points to should be downloaded by the browser rather than navigating to it.

From Downloading Resources:

The download attribute, if present, indicates that the author intends the hyperlink to be used for downloading a resource. The attribute may have a value; the value, if any, specifies the default filename that the author recommends for use in labeling the resource in a local file system. There are no restrictions on allowed values, but authors are cautioned that most file systems have limitations with regard to what punctuation is supported in file names, and user agents are likely to adjust file names accordingly.

The attribute can furthermore be given a value, to specify the filename that user agents are to use when storing the resource in a file system. This value can be overridden by the Content-Disposition HTTP header's filename parameter.

For example, clicking the following link downloads the .png as "MyGoogleLogo.png" instead of navigating to its href value: download me. The markup is simple:

<a href="http://www.google.com/.../logo2w.png" download="MyGoogleLogo">download me</a>

The real benefit of a[download] will be when working with blob: URLs and filesystem: URLs URLs. It'll give users a way to download content created/modified within your app.

Full Demo

Browser support: only the current Chrome dev channel release (14.0.835.15+) supports this attribute.