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.
// 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.
// 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). |
There are several options that will provide an easy fix for this:
background-image instead of the background shorthand property.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.!important property to background-size.background shorthand property.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
There are two primary ways people animate background images:
background-position in JS .
.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:
data: URLs add a ~33% size overhead to the resulting image.
el.style).
Both of these methods are inefficient: unacceptable for an always-silky-smooth 60fps web app.
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.
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
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.
Recently, I have read news on Webmonkey that Adobe’s CSS Shaders proposal, which will bring high-quality cinematic effects to the web through some new CSS tools, has been accepted by the W3C. If you haven't seen it yet, watch the video below:
Google Chrome's latest Canary added support for CSS shaders, so I decided to experiment with them.
In this experiment, I used custom vertex shader (spherify.vs) and fragment shader (spherify.fs) to create a globe with Google Maps.
<iframe
class="globe"
src="http://maps.google.com/?ie=UTF8&ll=14.597042,-15.625&spn=158.47027,316.054688&t=h&z=2&output=embed"
scrolling="no"></iframe>
.globe {
width: 550px;
height: 550px;
border: 0;
-webkit-filter: contrast(1.4) custom(url(shaders/spherify.vs) mix(url(shaders/spherify.fs) multiply source-atop),
50 50 border-box,
amount 1,
sphereRadius 0.5,
sphereAxis -0.41 1 0.19,
sphereRotation 43.5,
ambientLight 0.15,
lightPosition 1 0.87 0.25,
lightColor 1 1 1 1,
transform perspective(500));
}
Here, we're applying a vertex shader (spherify.vs) which will operate on a mesh that has 50 lines and 50 columns (50 50 border-box). Feel free to read the source of the vertex shader: spherify.vs. It's written in GLSL but you can probably follow along.
The mix() function provides basic functionalities for color manipulation like blending and alpha compositing on a fragment shader.
We can change the shere's radius, axis, rotation right in the CSS. In this example we set the value of the sphereRadius: 0.5 and it gives original sphere size.
Below is a video of the effect. If you've got shaders enabled you can play with the real thing right below!
If you just see a flat google maps above, you can enable it with the instructions below
This is currently cutting-edge, so it's only available in the latest Google Chrome Canary and WebKit nightly. To enjoy the full experience you'll need to turn a few knobs.
about:flags in the browser's navigation barBenchmarking browser graphics in a nutshell: draw as much as you can while maintaining a smooth frame rate. Once your framerate drops, you know how much you can draw per frame. End of post. ...No? Ok, I’ll explain some more.
Example time! Here’s a small code snippet with a benchmarking tick function. The tick function calls a draw function with an increasing draw load until the draw takes consistently longer than 33 ms.
var t, previousTime;
var drawLoad = 1;
var slowCount = 0;
var maxSlow = 10;
t = previousTime = performance.webkitNow();
var tick = function() {
var maximumFrameTime = 1000/30; // 30 FPS
t = performance.webkitNow();
var elapsed = t - previousTime;
previousTime = t;
if (elapsed < maximumFrameTime || slowCount < maxSlow) {
if (elapsed < maximumFrameTime) {
drawLoad+=10;
} else {
slowCount++;
}
draw(drawLoad);
webkitRequestAnimationFrame(tick);
} else {
// found maximum sustainable load at 30 FPS
document.getElementById('res').innerHTML = ("could draw "+(drawLoad)+" in " +
maximumFrameTime + " ms");
}
};
webkitRequestAnimationFrame(tick);
You can see how the benchmark keeps drawing more and more until it hits the point where it slows down. This is a nice and simple way to figure out how much you can draw at a smooth frame rate. You can also plug in your own draw function to the example and do some custom benchmarking, yay!
So, if the above example is the nice way to do it, what are the not so nice ways? The ways that lead to you benchmarking unrelated things or that give you weird performance metrics that don’t seem to have anything to do with how fast your app is running. Glad you asked, here are the two most common ones I’ve seen around the web.
Measuring max FPS: draw a little bit every frame and measure the FPS. It doesn’t work well for measuring graphics performance on Chrome because the underlying graphics implementation is synchronized to the screen refresh rate (so you get max 60 screen updates per second). Measuring draw call speed isn’t going to be very helpful either as Chrome’s drawing system puts your drawing commands into a command buffer that gets executed on the next screen refresh.
Using setTimeout for measuring graphics performance is another bad idea. The setTimeout interval is capped to 4 ms in browsers, so the most you can get out of it is 250 FPS. Historically, browsers had different minimum intervals, so you might have had a very broken trivial draw benchmark that showed browser A running at 250 FPS (4 ms min interval) and browser B running at 100 FPS (10 ms min interval). Clearly A is faster! Not! It could well be that B ran the draw code faster than A, say A took 3 ms and B took 1 ms. Doesn’t affect the FPS, as the draw time is less than the minimum setTimeout interval. And if the browser renders asynchronously, all bets are off. Don’t use setTimeout unless you know what you’re doing.
A better way to benchmark is to use a realistic drawing load and multiply it until the frame rate starts to chug. For example, if you’re writing a top-down game with a tilemap terrain, try drawing the tilemap every frame and seeing if it runs at 60 FPS. If yes, increase load (draw tilemap twice every frame, with a clear in between). Continue increasing until the FPS drops to a new stable level. Now you know how many layers of tilemap you can draw per frame.
Different graphics applications have different needs, so you should write your benchmarks with that in mind. Measure the graphics features that you are using in your app. When you find a slow scenario, try to reduce it down to the smallest piece of code that reproduces it (and file a bug report at new.crbug.com if it should be faster.)
To see how to write high-performance web graphics code, check out this Google I/O 2012 talk by Nat Duca and Tom Wiltzius from the Chrome GPU team:
The 2D canvas in Chrome got some hardware acceleration love back in February. The acceleration makes drawing 2D sprites really fast, as the implementation is using the GPU to do drawImage.
You can check out this example for the kinds of things the acceleration enables. The demo is drawing 180 transformed 256×256 PNG sprites on a full-window canvas and running an N-body simulation on them to make the sprites gravitate towards each other. And it all runs smoothly at 30 to 60 frames per second on a low-powered laptop.
There are still some rough edges in the acceleration implementation, so you may see performance degradation in some use cases (please let the Chrome team know through new.crbug.com so that we can fix them!)
SwiftShader is a software 3D renderer used in Chrome 18 that lets you use CSS 3D and WebGL even on blacklisted GPUs. SwiftShader is available only on Windows and kicks in when you visit a site that uses 3D graphics features.
The first time you run Chrome, it checks if your GPU is blacklisted. In the unfortunate case that it is, Chrome downloads and installs the SwiftShader component in the background. After the component is installed, you can view 3D content. If you visit a 3D site before the component has finished installing, you may need to close and re-open the tab to view the site.
The performance of SwiftShader should be good enough to view simple 3D content.
To force-enable SwiftShader for testing purposes, start Chrome from the command line with the —blacklist-accelerated-compositing and —blacklist-webgl flags.
You can read more about Chrome 18’s new graphics features (including GPU-accelerated 2D canvas) at the Chromium Blog
Filter effects have been around for awhile but were designed to work in SVG. They're fantastically powerful at applying effects like color intensity, warping, or blurring to an image before it's composited and rendered into the document.
Well,...way back in 2009 Mozilla said SVG wasn't enough! They ended up taking filters one step further and allowed them on HTML content in Firefox 3.5. Check out Paul Irish's timeless demo of SVG filters on a playing <video>. Again, only works in Firefox but still the bees knees.
Flash forward to the end of 2011 and Adobe (plus others) have been hard at work bringing this amazing technology to CSS. Specifically, I'm referring to CSS Filter Effects 1.0, which WebKit has started to implement.
The magic happens with a new prefixed `filter' property in a style rule:
/* gray all images by 50% and blur by 10px */
img {
-webkit-filter: grayscale(0.5) blur(10px);
}
Enabling filters directly in CSS means that nearly any DOM element can take advantage of them! Images, <video>, <canvas>,... you name it. You can even see what the web looks like without glasses.

The spec also defines CSS shaders, which will eventually bring OpenGL shader technology to CSS. That's very VERY exciting! However, there are security considerations any time you open up the GPU of a system. For this reason, WebKit only has CSS filter functions implemented for now.
Chrome 18.0.976.0 (currently canary), Webkit nightly
In Webkit nightlies, filters can be applied to hardware accelerated content ( e.g. img { -webkit-transform: translateZ(0); } ). In Chrome, filters on accelerated content are still a work in progress (use the --enable-accelerated-filters flag). This includes <video>, which is accelerated by default.
Heads-up from our friends at Opera, who have been testing WebGL on actual OpenGL ES 2.0 hardware: many demos and applications use highp precision in fragment shaders when it’s not really warranted.
Highp in fragment shaders is an optional part of the OpenGL ES 2.0 spec, so not all hardware supports it (and even when they do, there may be a performance hit). Using mediump will usually be good enough and it will ensure that your applications will work on mobile devices as well.
In practice, if your fragment shader previously started with
precision highp float;precision mediump float; // or lowp
Developers keep pushing the boundaries of what's possible to do in the browser. Here are some awesome new WebGL demos from around the web to showcase what your browser can really do. First off, have a gander at this impressive three.js dynamic terrain rendering demo from AlteredQualia. The second demo is Nouvelle Vague from the French web design agency Ultranoir, and it shows a very particular way to read tweets. Mr.doob comes back with another three.js demo, this time using data captured by a Kinect. Also using three.js is One Millionth Tower, a documentary funded by the Canadian National Film Board. And last, these guys got WebGL running on an iPad 2 through a WebView hack.
In other news, the Google Data Arts Team launched a workshop page to share the technology they've used to build some amazing demos. The page has a great tutorial on using dat.GUI, go check it out!