HTML5 Rocks

HTML5 Rocks

What's the CSS :scope pseudo-class for?

By Eric Bidelman at

:scope is defined in CSS Selectors 4 as:

A pseudo-class which represents any element that is in the contextual reference element set. This is is a (potentially empty) explicitly-specified set of elements, such as that specified by the querySelector(), or the parent element of a <style scoped> element, which is used to "scope" a selector so that it only matches within a subtree.

An example of using this guy is within a <style scoped> (more info):

<style>
  li {
    color: blue;
  }
</style>

<ul>
  <style scoped>
    li {
      color: red;
    } 
    :scope {
      border: 1px solid red;
    }
  </style>
  <li>abc</li>
  <li>def</li>
  <li>efg</li>
</ul>

<ul>
  <li>hij</li>
  <li>klm</li>
  <li>nop</li>
</ul>

Note: <style scoped> can be enabled in Chrome using the "Enable experimental WebKit features" flag in about:flags.

This colors the li elements in the first ul red and, because of the :scope rule, puts a border around the ul. That's because in the context of this <style scoped>, the ul matches :scope. It's the local context. If we were to add a :scope rule in the outer <style> it would match the entire document. Essentially, equivalent to :root.

Contextual elements

You're probably aware of the Element version of querySelector() and querySelectorAll(). Instead of querying the entire document, you can restrict the result set to a contextual element:

<ul>
 <li id="scope"><a>abc</a></li>
 <li>def</li>
 <li><a>efg</a></li>
</ul>
<script>
  document.querySelectorAll('ul a').length; // 2

  var scope = document.querySelector('#scope');
  scope.querySelectorAll('a').length; // 1
</script>

When these are called, the browser returns a NodeList that's filtered to only include the set of nodes that a.) match the selector and b.) which are also descendants of the context element. So in the the second example, the browser finds all a elements, then filters out the ones not in the scope element. This works, but it can lead to some bizarre behavior if you're not careful. Read on.

When querySelector goes wrong

There's a really important point in the Selectors spec that people often overlook. Even when querySelector[All]() is invoked on an element, selectors still evaluate in the context of the entire document. This means unanticipated things can happen:

scope.querySelectorAll('ul a').length); // 1
scope.querySelectorAll('body ul a').length); // 1

WTF! In the first example, ul is my element, yet I'm still able to use it and matches nodes. In the second, body isn't even a descendant of my element, but "body ul a" still matches. Both of these are confusing and not what you'd expect.

It's worth making the comparison to jQuery here, which takes the right approach and does what you'd expect:

$(scope).find('ul a').length // 0
$(scope).find('body ul a').length // 0

...enter :scope to solve these semantic shenanigans.

Fixing querySelector with :scope

WebKit recently landed support for using the :scope pseudo-class in querySelector[All](). You can test it in Chrome Canary 27.

You can use it restrict selectors to a context element. Let's see an example. In the following, :scope is used to "scope" the selector to the scope element's subtree. That's right, I said scope three times!

scope.querySelectorAll(':scope ul a').length); // 0
scope.querySelectorAll(':scope body ul a').length); // 0
scope.querySelectorAll(':scope a').length); // 1

Using :scope makes the semantics of the querySelector() methods a little more predictable and inline with what others like jQuery are already doing.

Performance win?

Not yet :(

I was curious if using :scope in qS/qSA gives a performance boost. So...like a good engineer I threw together a test. My rationale: less surface area for the browser to do selector matching means speedier lookups.

In my experiment, WebKit currently takes ~1.5-2x longer than not using :scope. Drats! When crbug.com/222028 gets fixed, using it should theoretically give you a slight performance boost over not using it.

Visualizing Shadow DOM Concepts

By Eric Bidelman at

Shadow DOM is a difficult topic to wrap your head around. It's just complex. It introduces unfamiliar concepts that we're not used to on the web. Shadow boundaries, styling scoping, event retargeting, insertion points, shadow insertion points, host nodes, distributed nodes,...the lingo goes on and on.

TRY THE DEMO

One thing that's conceptually taxing about Shadow DOM is the way your final product (DOM) is rendered by the browser. Nodes from the host node are magically swizzled into a ShadowRoot's insertion points, yet logically, still remain in the host node. Weird! So at render time, they appear as part of the shadow tree and not the original host. How this rendering takes place is one of the most confusing pieces of Shadow DOM.

A few days ago, I released a tool I've been working on called Shadow DOM Visualizer to help lessen the learning curve.

It allows you to visually see how Shadow DOM renders in the browser, something DevTools lacks today. Both black code blocks on the left are editable. Try changing the <content> insertion points, removing, or adding new ones to see how the composited (rendered) tree is affected on the right.

Mouse over the nodes in the graph to highlight the relevant markup on the left. Yay for d3.js! Blue nodes are coming from the host node. Yellow nodes come from the Shadow DOM. <content> insertion points are the bridge between the two worlds. Because they're logically in the Shadow DOM, they're colored yellow. Their blue border indicates that they invite blue host nodes into the rendering party.

Shadow DOM is available in Chrome 25 and the <template> element is available in Chrome 26 (although you only need the first to try the demo).

CSS Background shorthand coming to mobile WebKit browsers

By Pete LePage at

Earlier this year, WebKit updated the behavior of the CSS background shorthand property. With this change, the background shorthand property will reset the background-size to its default value of auto auto if it’s not set in the shorthand declaration. This change brings Chrome and other WebKit browsers in compliance with the specification and matches the behavior of Firefox, Opera and Internet Explorer.

With Chrome for Android moving forward to current revisions on WebKit, this change is now rolling out in Android. Because this was a fix to webkit, sites that tested in multiple browsers are likely not affected.

Old way of doing things

// background-size is reset to auto auto by the background shorthand 
.foo {  
  background-size: 50px 40px;
  background: url(foo.png) no-repeat;
}

The background shorthand property does not contain any size property and will therefore reset the background-size to its default value of auto auto.

Correct way to specifying image size

// background-size is specified after background
.fooA { background: url(foo.png) no-repeat; background-size: 50px 40px;
}

// background-size is specified inline after position
.fooB { background: url(foo.png) 0% / 50px 40px no-repeat;
}

In fooB, specifying a background-size in the background shorthand requires the position (0%) be specified first, followed by a forward slash then the background-size (50px 40px). |

Fixes for existing code

There are several options that will provide an easy fix for this:

  • Use background-image instead of the background shorthand property.
  • Set the background-size last, with a higher specificity than any other CSS rules that set background on that element, and don’t forget to check and :hover rules.
  • Apply the !important property to background-size.
  • Move the sizing information to the background shorthand property.

Bonus: background image offsets

In addition to this change, there’s now greater flexibility in positioning the image within the background. In the past, you could only specify the image position relative from the top left corner, now you can specify an offset from the edges of the container. For example setting background-position: right 5px bottom 5px; the image will be positioned 5px from the right edge and 5px from the bottom. Check out this sample on JSBin

Canvas-driven background images

By Eric Bidelman at

Programmatically animating background images

There are two primary ways people animate background images:

  1. Use CSS sprites to update a background-position in JS .
  2. Hacks with .toDataURL() .

The first works great if you have the image ahead of time, but what if your source needs to be programmatically generated, say, by a <canvas>? The solution to #1 is to use .toDataURL() on the canvas and set the background to the generated URL:

while(1) {
  var url = canvas.toDataURL('image/jpeg');
    el.style.background = 'url(' + url + ')';
 }

There are two problems with this:

  1. data: URLs add a ~33% size overhead to the resulting image.
  2. A ton of DOM touching (el.style).

Both of these methods are inefficient: unacceptable for an always-silky-smooth 60fps web app.

Using 2d canvas as a background

DEMO

Turns out, there's a non-standard API which WebKit has had for years that can take canvas as the source for a background. Sadly however, there's no published spec for this feature.

First, instead of specifying a URL for the back:

.bg {
  background: url(bg.png) no-repeat 50% 50%;
}

use -webkit-canvas(), referencing a string identifier to a canvas context:

.canvas-bg {
  background: -webkit-canvas(animation) no-repeat 50% 50%;
}

Next, we need to create the 2d context with a special version of .getContext():

var ctx = document.getCSSCanvasContext('2d', 'animation', 300, 300);

Note: this method is on the document object and not the canvas. The second argument is the same name used in -webkit-canvas().

Further information from Dave Hyatt:

There is only one CSS canvas for a given global identifier per document. When you obtain the drawing context you also specify the size. The canvas will not be cleared as long as you repeatedly request the same size. Specifying a new size is equivalent to the kind of behavior you’d expect if you resized a <canvas> element, and so the canvas buffer will be cleared.

All objects that observe a CSS canvas of the same name are sharing that canvas. This means that (similar to how animated GIFs work), you can do animations and have them happen in lockstep on all the clients of the canvas. Drawing changes will be propagated to all clients automatically.

Animations

As seen in the demo, we can reuse requestAnimationFrame() to drive an animation. This is great, because once things are hooked up, the association between CSS and the canvas element is preserved. There's no need to fiddle with the DOM.

Demo not animated in Chrome?

The current stable channel of Chrome (version 23) has crbug.com/161699, which prevents a requestAnimationFrame() animation from updating the background properly. This has been fixed in Chrome 25 (currently Canary). The demo also should work well in the current Safari.

Performance benefits

We're talking canvas here. Hardware accelerated animations are now fully in play (at least for the browsers this feature works in). And just to reiterate, there's no need to molest the DOM from JS.

Using webgl as a background

Hold on a sec. Does this mean we can power a CSS background using webgl? Of course it does! WebGL is merely a 3d context for canvas. Just swap in "experimental-webgl" instead of "2d", and voila.

var gl = document.getCSSCanvasContext('experimental-webgl', 'animation', 300, 150);

Here's a proof of concept that contains a div with it's background drawn using vertext and fragment shaders: DEMO

Other approaches

It's worth noting that Mozilla has had -moz-element() (MDN) for quite some time. This is part of the CSS Image Values and Replaced Content Module Level 4 spec and allows you to create an image generated from arbitrary HTML: videos, canvas, DOM content,...you name it. However, there are security concerns with having full access to snapshot images of the DOM. This is primarily why other browsers have not adopted said feature.

Content Security Policy 1.0 is officially awesome.

By Mike West at

It's official! The W3C has advanced the Content Security Policy 1.0 specification from Working Draft to Candidate Recommendation, and issued a call for implementations. Cross-site scripting attacks are one step closer to being (mostly) a thing of the past.

Chrome Canary and WebKit nightlies now support the unprefixed Content-Security-Policy header, and will be using the prefixed X-WebKit-CSP header to begin experimenting with some new behavior that's being specified as part of Content Security Policy 1.1. Instead of writing:

X-WebKit-CSP: script-src 'self'; object-src 'none'

You'll write:

Content-Security-Policy: script-src 'self'; object-src 'none'

We expect other browser vendors to follow suit within the next few revisions, so it's a great idea to start sending the canonical header today.

Content Securawhat?

Content Security Policy! It helps you reduce the risk of cross-site scripting and other content injection attacks in your applications. It's a huge step forward in terms of the protection you can offer your users, and we highly recommend taking a hard look at implementing it. You can get all the details in the ever so cleverly named "An Introduction to Content Security Policy".

Stacking Changes Coming to position:fixed elements

By Tom Wiltzius at

In Chrome 22 the layout behavior of position:fixed elements is slightly different than previous versions. All position:fixed elements now form new stacking contexts. This will change the stacking order of some pages, which has the potential to break page layouts. The new behavior matches the behavior of WebKit browsers on mobile devices (iOS Safari and Chrome for Android).

Stacking Whats?

Everyone knows and loves the z-index for determining depth ordering of elements on a page. Not all z-indexes are created equal, however: an element's z-index only determines its ordering relative to other elements in the same stacking context. Most elements on a page are in a single, root stacking context, but absolutely or relatively positioned elements with non-auto z-index values form their own stacking contexts (that is, all of their children will be z-ordered within the parent and not be interleaved with content from outside the parent). As of Chrome 22, position:fixed elements will also create their own stacking contexts.

For a general overview of stacking contexts this MDN article is a great read.

Compare position:fixed to the new position:sticky attribute: for reference, position:sticky always creates a new stacking context.

Motivation

Mobile browsers (Mobile Safari, Android browser, Qt-based browsers) put position:fixed elements in their own stacking contexts and have for some time (since iOS5, Android Gingerbread, etc) because it allows for certain scrolling optimizations, making web pages much more responsive to touch. The change is being brought to desktop for three reasons:

  1. Having different rendering behavior on "mobile" and "desktop" browsers is a stumbling block for web authors; CSS should work the same everywhere when possible.
  2. With tablets it isn’t clear which of the "mobile" or "desktop" stacking context creation algorithms is more appropriate.
  3. Bringing the scrolling performance optimizations from mobile to desktop is good for both users and authors.

Specifics of the Change

Here's an example showing the different layout behaviors: http://codepen.io/paulirish/pen/CgAof

With the change, both versions will render like the right-hand version.

In this example, the green box has a z-index: 1, the pink box has a z-index: 3, and the orange box has a z-index: 2. The blue box is an ancestor of the orange box, and has position:fixed.

If the blue box gets its own stacking context, the orange box's z-index is computed relative to the blue box’s stacking context. Since the blue box has a z-index of auto, giving it a stacking level of zero in the root stacking context, this means the orange box ends up behind the green and pink boxes, which have z-indexes of 1 and 3 (respectively) in the root context.

If the blue box does not get its own stacking context, the orange box's z-index is computed relative to the root stacking context (along with the green and pink boxes). Hence, the orange box ends up interleaved with the pink and green boxes.

For more detail about criteria for stacking context creation (and how stacking contexts behave in general), again refer to this MDN article. In the example the right-hand side version always gave the blue box its own stacking context because its opacity is less than 1. The behavior change being made effectively adds another criterion for creating a separate stacking context, namely an element being position:fixed.

Testing & The Future

To test if your page is going to change, go to Chrome's about:flags and turn on/off "fixed position elements create stacking contexts". If your layout behaves the same in both cases, you're set. If not, make sure it looks acceptable to you with that flag enabled, as that will be the default in Chrome 22.

This change removes one capability - the ability to interleave content within a position:fixed subtree with non-scrolling content from outside. It's unlikely that any web developers are doing this on purpose, and the same effect can be created by giving multiple position:fixed elements the different portions of DOM. As an example, consider these two examples:

http://codepen.io/wiltzius/pen/gcjCk

This page attempts to take two child divs (overlayA and overlayB) of a position:fixed element and place one above a separate content div and one below that same separate content div. It’s now impossible to do this because the position:fixed element is its own stacking context, and it (along with all of its children) will be either entirely above or entirely below the content div. Note that this example works in Chrome 21 and earlier but no longer in Chrome 22.

To fix this, the two overlays can be broken up into their own position:fixed elements. Each is its own stacking context, one of which can go above the content div and one of which can go below the content div. See the fixed example, which works in Chrome 21 and 22:

http://codepen.io/wiltzius/pen/vhFzG

Credit for the genesis of this example goes to the inimitable hixie.

Chrome is the first desktop browser to cause position:fixed elements to create their own stacking contexts. The relevant standard is the CSS z-index specification (see e.g. http://www.w3.org/TR/CSS21/zindex.html). There's not yet consensus over what to do about the difference between mobile and desktop browsers here, but given the confusion of having two different behaviors on mobile and desktop, Chrome has chosen to move to this single behavior on both platforms for the time being.

Updated Oct 1, 2012: The original version of this article suggested that the CSS z-index specification had already been changed to reflect the new behavior of position: fixed elements. This is inaccurate; it has been discussed on the www-style list but as of yet no change has been taken into the spec.

Stick your landings! position: sticky lands in WebKit

By Eric Bidelman at

position: sticky is a new way to position elements and is conceptually similar to position: fixed. The difference is that an element with position: sticky behaves like position: relative within its parent, until a given offset threshold is met in the viewport.

Use cases

Paraphrasing from Edward O’Connor's original proposal of this feature:

Many web sites have elements that alternate between being in-flow and having position: fixed, depending on the user's scroll position. This is often done for elements in a sidebar that the page author wants to be always visible as the user scrolls, but which slot into a space on the page when scrolled to the top. Good examples are news.google.com (the "Top Stories" sidebar) and yelp.com (search results map).

Introducing sticky positioning

LAUNCH DEMO

By simply adding position: sticky (vendor prefixed), we can tell an element to be position: relative until the user scrolls the item (or its parent) to be 15px from the top:

.sticky {
  position: -webkit-sticky;
  position: -moz-sticky;
  position: -ms-sticky;
  position: -o-sticky;
  top: 15px;
}

At top: 15px, the element becomes fixed.

To illustrate this feature in a practical setting, I've put together a DEMO which sticks blog titles as you scroll.

Old approach: scroll events

Until now, to achieve the sticky effect, sites setup scroll event listeners in JS. We actually use this technique as well on html5rocks tutorials. On screens smaller than 1200px, our table of contents sidebar changes to position: fixed after a certain amount of scrolling.

Here's the (now old way) to have a header that sticks to the top of the viewport when the user scrolls down, and falls back into place when the user scrolls up:

<style>
.sticky {
  position: fixed;
  top: 0;
}
.header {
  width: 100%;
  background: #F6D565;
  padding: 25px 0;
}
</style>

<div class="header"></div>

<script>
var header = document.querySelector('.header');
var origOffsetY = header.offsetTop;

function onScroll(e) {
  window.scrollY >= origOffsetY ? header.classList.add('sticky') :
                                  header.classList.remove('sticky');
}

document.addEventListener('scroll', onScroll);
</script>

Try it: http://jsbin.com/omanut/2/

This is easy enough, but this model quickly breaks down if you want to do this for a bunch of DOM nodes, say, every <h1> title of a blog as the user scrolls.

Why JS is not ideal

In general, scroll handlers are never a good idea. Folks tend to do too much work and wonder why their UI is janky.

Something else to consider is that more and more browsers are implementing hardware accelerated scrolling to improve performance. The problem with this is that on JS scroll handlers are in play, browsers may fall back into a slower (software) mode. Now we're no longer running on the GPU. Instead, we're back on the CPU. The result? User's perceive more jank when scrolling your page.

Thus, it makes a lot of sense to have such feature be declarative in CSS.

Support

Unfortunately, there isn't a spec for this one. It was proposed on www-style back in June and just landed in WebKit. That means there's no good documentation to point to. One thing to note however, according to this bug, if both left and right are specified, left wins. Likewise, if top and bottom are used at the same time, top wins.

Support right now is Chrome 23.0.1247.0+ (current Canary) and WebKit nightly.

Integrating input[type="file"] with the Filesystem API

By Eric Bidelman at

Let's say you have a photo editing app and you'd like users to be able to drag in hundreds of photos and copy them into your app. Ok, what do you do?

Launch Demo

In a recent post, Eiji Kitamura highlighted a subtle, yet powerful new feature in the drag and drop APIs; the ability to drag in folders and retrieve them as HTML5 Filesystem API FileEntry and DirectoryEntry objects (done by accessing a new method on the DataTransferItem, .webkitGetAsEntry()).

What's remarkably cool about the .webkitGetAsEntry() extension is how elegant it makes importing files and entire folders. Once you have a FileEntry or DirectoryEntry from a drop event, it's a matter of using the Filesystem API's copyTo() to get it imported into your app.

An example of copying multiple dropped folders over to the filesystem:

var fs = null; // Cache filesystem for later.

// Not shown: setup drag and drop event listeners.
function onDrop(e) {
  e.preventDefault();
  e.stopPropagation();

  var items = e.dataTransfer.items;

  for (var i = 0, item; item = items[i]; ++i) { 
    var entry = item.webkitGetAsEntry();

    // Folder? Copy the DirectoryEntry over to our local filesystem.
    if (entry.isDirectory) {
      entry.copyTo(fs.root, null, function(copiedEntry) {
        // ...
      }, onError);
    }
  }
}

window.webkitRequestFileSystem(TEMPORARY, 1024 * 1204, function(fileSystem) {
  fs = fileSystem;
}, function(e) {
  console.log('Error', e);
});

Very nice! Again, the simplicity comes from integrating DnD with the Filesystem API calls.

Taking this one step further, we also have the ability to drag and drop a folder and/or files onto a normal <input type="file">, then access the entries as Filesystem directory or file entries. That is done through .webkitEntries:

<input type="file" multiple>

function onChange(e) {
  e.stopPropagation();
  e.preventDefault();

  var entries = e.target.webkitEntries; // Get all dropped items as FS API entries.

  [].forEach.call(entries, function(entry) {

    // Copy the entry into our local filesystem.
    entry.copyTo(fs.root, null, function(copiedEntry) {
      ...
    }, onError);

  });
}

document.querySelector('input[type="file"]').addEventListener('change', onChange);

I've put together a photo gallery demo to demonstrate these different techniques for importing files/folders.

Launch Demo

To learn more about the HTML5 Filesystem API, see Exploring the Filesystem APIs.

When milliseconds are not enough: performance.now()

By Paul Irish at

The High Resolution Timer was added by the WebPerf Working Group to allow measurement in the Web Platform that's more precise than what we've had with +new Date and the newer Date.now().

So just to compare, here are the sorts of values you'd get back:

   Date.now()         //  1337376068250
   performance.now()  //  20303.427000007
   

You'll notice the two above values are many orders of magnitude different. performance.now() is a measurement of floating point milliseconds since that particular page started to load (the performance.timing.navigationStart timeStamp to be specific). You could argue that it could have been the number of milliseconds since the unix epoch, but rarely does a web app need to know the distance between now and 1970. This number stays relative to the page because you'll be comparing two or more measurements against eachother.

Monotonic time

Another added benefit here is that you can rely on the time being monotonic. Let's let WebKit engineer Tony Gentilcore explain this one:

Perhaps less often considered is that Date, based on system time, isn't ideal for real user monitoring either. Most systems run a daemon which regularly synchronizes the time. It is common for the clock to be tweaked a few milliseconds every 15-20 minutes. At that rate about 1% of 10 second intervals measured would be inaccurate.

Use Cases

There are a few situations where you'd use this high resolution timer instead of grabbing a basic timestamp:

  • benchmarking
  • game or animation runloop code
  • calculating framerate with precision
  • cueing actions or audio to occur at specific points in an animation or other time-based sequence

Availability

The high resolution timer is currently available in Chrome (Stable) as window.performance.webkitNow(), and this value is generally equal to the new argument value passed into the requestAnimationFrame callback. Pretty soon, WebKit will drop its prefix and this will be available through performance.now(). The WebPerfWG in particular, led by Jatinder Mann of Microsoft, has been very successful in unprefixing their features quite quickly.

In summary, performance.now() is...

  • a double with microseconds in the fractional
  • relative to the navigationStart of the page rather than to the UNIX epoch
  • not skewed when the system time changes
  • available in Chrome stable, Firefox 15+, and IE10.

Arrived! xhr.send(ArrayBufferViews)

By Eric Bidelman at

And here you thought we were done improving XHR!

For a while now XHR2's overloaded send() method has supported sending a ArrayBuffer (a raw byte array).

Chrome 22 (current Canary) deprecates this feature by replacing it with sending ArrayBufferViews instead. JS Typed Arrays are just special ArrayBufferViews, so all this really means is that you can now send a typed array directly across the wire without touching its underlying buffer. This change is in match step with the recent updates to the XMLHttpRequest2 spec.

So for example, instead of sending an ArrayBuffer:

var xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) { ... };

var uInt8Array = new Uint8Array([1, 2, 3]);

xhr.send(uInt8Array.buffer);

Just send the typed array itself:

xhr.send(uInt8Array);

Eventually, sending ArrayBuffers will be removed, but for the time being you'll get console warnings when trying to send a buffer.

As always, you can keep up with these types of changes by following chromestatus.com.