Custom Filters, or CSS Shaders as they used to be called, allow you to use the power of WebGL's shaders with your DOM content. Since in the current implementation the shaders used are virtually the same as those in WebGL, you need to take a step back and understand some 3D terminology and a little bit of the graphics pipeline.
I've included a recorded version of a presentation I recently delivered to LondonJS. In the video I step through an overview of the 3D terminology you need to understand, what the different variable types are that you'll encounter, and how you can start playing with Custom Filters today. You should also grab the slides so you can play with the demos yourself.
I've previously written an introduction to shaders which will give you a good breakdown of what shaders are and how you can use them from a WebGL point of view. If you've never dealt with shaders it's something of a required read before you go much further, because many of the Custom Filters concepts and language hinges on the existing WebGL shader terminology.
So with that said, let's enable Custom Filters and plough on!
Custom Filters are available in both Chrome and Canary as well as Chrome for Android. Simply head over to about:flags and search for "CSS Shaders", enable them and restart the browser. Now you're good to go!
Custom Filters expands on the set of filters that you can already apply, like blur or sepia, to your DOM elements. Eric Bidelman wrote a great playground tool for those, which you should check out.
To apply a Custom Filter to a DOM element you use the following syntax:
.customShader {
-webkit-filter:
custom(
url(vertexshader.vert)
mix(url(fragment.frag) normal source-atop),
/* Row, columns - the vertices are made automatically */
4 5,
/* We set uniforms; we can't set attributes */
time 0)
}
You'll see from this that we declare our vertex and fragment shaders, the number of rows and columns we want our DOM element to get broken down into, and then any uniforms we want to pass through.
A final thing to point out here is that we use the mix() function around the fragment shader with a blend mode (normal), and a composite mode (source-atop). Let's take a look at the fragment shader itself to see why we even need a mix() function.
If you're familiar with WebGL's shaders you'll notice that in Custom Filters things are a little different. For one we don't create the texture(s) that our fragment shader uses to fill in the pixels. Rather the DOM content that has the filter applied gets mapped to a texture automatically, and that means two things:
gl_FragColor is off limits. Rather, it's assumed that you will want to render the DOM content, and what you get to do is manipulate its pixels indirectly through css_ColorMatrix and css_MixColor.That means our Hello World of fragment shaders looks more like this:
void main() {
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
css_MixColor = vec4(0.0, 0.0, 0.0, 0.0);
// umm, where did gl_FragColor go?
}
Each pixel of the DOM content is multiplied by the css_ColorMatrix, which in the above case does nothing as its the identity matrix and changes none of the RGBA values. If we did want to, say, just keep the red values we would use a css_ColorMatrix like this:
// keep only red and alpha
css_ColorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0);
You can hopefully see that as you multiply the 4D (RGBA) pixel values by the matrix that you get a manipulated pixel value out of the other side, and in this case one that zeros out the green and blue components.
The css_MixColor is mainly used as a base color that you want to, well, mix in with your DOM content. The mixing is done through the blend modes that you'll be familiar with from art packages: overlay, screen, color dodge, hard light and so on.
There are plenty of ways that these two variables can manipulate the pixels, and included in my presentation is a demo that you can play around with. You should check out the Custom Filters specification to get a better handle on how the blend and composite modes interact.
In WebGL we take full responsibility for the creation of our mesh's 3D points, but in Custom Filters all you have to do is specify the number of rows and columns that you want and the browser will automatically break down your DOM content into a bunch of triangles:
Each one of those vertices then gets passed through to our vertex shader for manipulation, and that means we can start moving them around in 3D space as we need. It's not long before you can make some pretty crazy effects!
Bringing in animations to your shaders is what makes them fun and engaging. To do that you simply use a transition (or animation) in your CSS to update uniform values:
.shader {
/* transition on the filter property */
-webkit-transition: -webkit-filter 2500ms ease-out;
-webkit-filter: custom(
url(vshader.vert)
mix(url(fshader.frag) normal source-atop),
1 1,
time 0);
}
.shader:hover {
-webkit-filter: custom(
url(vshader.vert)
mix(url(fshader.frag) normal source-atop),
1 1,
time 1);
}
So the thing to notice in the code above is that time is going to ease from 0 to 1 during the transition. Inside the shader we can declare the uniform time and use whatever its current value is:
uniform float time;
uniform mat4 u_projectionMatrix;
attribute vec4 a_position;
void main() {
// copy a_position to position - attributes are read only!
vec4 position = a_position;
// use our time uniform from the CSS declaration
position.x += time;
gl_Position = u_projectionMatrix * position;
}
Custom Filters are great fun to play with, and the amazing effects you can create are difficult (and in some cases impossible) without them. It is still early days, and things are changing around, but adding them will add a little bit of showbiz to your projects, so why not give them a go?
: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.
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.
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.
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.
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.
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.
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).
Continuous painting mode for paint profiling is now available in Chrome Canary. This article explains how you identify a problem in page painting time and how you can use this new tool to detect bottlenecks in painting performance.
So you noticed that your page doesn't scroll smoothly. This is how you would start tackling the problem. For our example, we'll use the demo page Things We Left On The Moon by Dan Cederholm as our example.
You open the Web Inspector, start a Timeline recording and scroll your page up
and down. Then you look at the vertical timelines, that show you what happened
in each frame.
If you see that most time is spent painting (big green bars above 60fps), you need to take a closer look at why this is happening. To investigate your paints, use the Show paint rectangles setting of the Web Inspector (cog icon in the bottom right corner of the Web Inspector). This will show you the regions where Chrome paints.
There are different reasons for Chrome to repaint areas of the page:
As a developer you need to be aware of the repaints happening on your page.
Looking at the paint rectangles is a great way of doing that. In the example
screenshot above you can see that the whole screen is covered in a big paint
rectangle. This means the whole screen is repainted as you scroll, which is not
good. In this specific case this is caused by the CSS style
background-attachment:fixed which causes the background image of the page to
stay at the same position while the content of the page moves on top of it as
you scroll.
If you identify that the repaints cover a big area and/or take a long time, you have two options:
You can try to change the page layout to reduce the amount of painting. If
possible Chrome paints the visible page only once and adds parts that have
not been visible as you scroll down. However, there are cases when Chrome
needs to repaint certain areas. For example the CSS rule position:fixed,
which is often used for navigation elements that stay in the same position,
can cause these repaints.
If you want to keep your page layout, you can try to reduce the painting cost of the areas that get repainted. Not every CSS style has the same painting cost, some have little impact, others a lot. Figuring out the painting costs of certain styles can be a lot of work. You can do this by toggling styles in the Elements panel and looking at the difference in the Timeline recording, which means switching between panels and doing lots of recordings. This is where continuous painting mode comes into play.
Continuous painting mode is a tool that helps you identify which elements are costly on the page. It puts the page into an always repainting state, showing a counter of how much painting work is happening. Then, you can hide elements and mutate styles, watching the counter, in order to figure out what is slow.
In order to use continuous painting mode you need to use Chrome Canary.
On Linux systems (and some Macs) you need to make sure that Chrome runs in
compositing mode. This can be permanently enabled using the GPU compositing on
all pages setting in about:flags.
Continuous painting mode can be enabled via the checkbox Enable continuous page repainting in the Web Inspector's settings (cog icon in the bottom right corner of the Web Inspector).
The small display in the top right corner shows you the measured paint times in milliseconds. More specifically it shows:
The paint time measurements are dependent on screen resolution, window size and the hardware Chrome is running on. Be aware that these things are likely to be different for your users.
This is how you can use continuous painting mode to track down elements and styles that add a lot of painting cost:
The animation below shows toggling styles and its affect on paint time:
This example demonstrates how turning either one of the CSS styles box-shadow
or border-radius off, reduces the painting time by a big amount. Using both box-shadow andborder-radius on an element leads to very expensive painting
operations, because Chrome can't optimize for this. So if you have an element
that gets a lot of repaints, like in the example, you should avoid this
combination.
Continuous painting mode repaints the whole visible page. This is usually not the case when browsing a web page. Scrolling usually only paints the parts that haven't been visible before. And for other changes on the page, only the smallest possible area is repainted. So check with another Timeline recording if your style improvements actually had an impact on the paint times of your page.
When using continuous painting mode you might discover that e.g. the CSS
styles border-radius and box-shadow add a lot of painting time. It is not
discouraged to use those features in general, they are awesome and we are happy
they are finally here. But it's important to know when and where to use them.
Avoid using them in areas with lots of repaints and avoid overusing them in
general.
Learn more about painting and related topics on jankfree.com
Click below for a demo where Paul Irish uses continuous painting to identify a uniquely expensive paint operation.
More good news from our old friend WebRTC...
To be precise: three pieces of good news and a couple of minor API changes.
RTCDataChannel has been implemented in Chrome, and there's a great little demo at simpl.info/dc.
This demo shows peer-to-peer communication of arbitrary data – in less than a hundred lines of code. You'll need Chrome 25 or above for this, which at this point means Beta or Canary.
RTCDataChannel makes the most of features built into RTCPeerConnection – not least, the use if the ICE framework to get through firewalls and NATs – and has lots of potential applications where low latency is paramount: for gaming, remote desktop applications, real-time text chat and file transfer.
For more information about RTCDataChannel, take a look at Getting Started with WebRTC.
Less exciting, but still important: from Chrome 26, some RTCPeerConnection and MediaStream API properties have become getter methods:
getAudioTracks() method instead of the audioTracks property, and getVideoTracks() instead of videoTracks.getLocalStreams() instead of localStreams, and getRemoteStreams() instead of remoteStreams.To get a glimpse of MediaStream in action, take a look at the simpl.info/gum getUserMedia demo. The stream variable is in global scope: examine it from the console. Likewise for RTCPeerConnection at simpl.info/pc: the RTCPeerConnection objects pc1 and pc2 are in global scope.
And in case you missed it, Chrome can now 'talk' to Firefox.
You can try this out now at webrtc.org/demo, which has full instructions, links to source code, and information about API differences.
Tip of the hat to those at Mozilla and Google who made it all happen.
Happy coding! And please let us know of any bugs, either by commenting on this post, or at crbug.com/new.
...and don't forget, you can always get up-to-date implementation information from the excellent chromestatus.com.
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
The new JavaScript Web Speech API makes it easy to add speech recognition to your web pages. This API allows fine control and flexibility over the speech recognition capabilities in Chrome version 25 and later. Here's an example with the recognized text appearing almost immediately while speaking.
Let’s take a look under the hood. First we check to see if the browser supports the Web Speech API by checking if the webkitSpeechRecognition object exists. If not, we suggest the user upgrades his browser. (Since the API is still experimental, it's currently vendor prefixed.) Lastly, we create the webkitSpeechRecognition object which provides the speech interface, and set some of its attributes and event handlers.
if (!('webkitSpeechRecognition' in window)) {
upgrade();
} else {
var recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.onstart = function() { ... }
recognition.onresult = function(event) { ... }
recognition.onerror = function(event) { ... }
recognition.onend = function() { ... }
...
The default value for continuous is false, meaning that when the user stops talking, speech recognition will end. This mode is great for simple text like short input fields. In this demo, we set it to true, so that recognition will continue even if the user pauses while speaking.
The default value for interimResults is false, meaning that the only results returned by the recognizer are final and will not change. The demo sets it to true so we get early, interim results that may change. Watch the demo carefully, the grey text is the text that is interim and does sometimes change, whereas the black text are responses from the recognizer that are marked final and will not change.
To get started, the user clicks on the microphone button, which triggers this code:
function startButton(event) {
...
final_transcript = '';
recognition.lang = select_dialect.value;
recognition.start();
We set the spoken language for the speech recognizer "lang" to the BCP-47 value that the user has selected via the selection drop-down list, for example “en-US” for English-United States. If this is not set, it defaults to the lang of the HTML document root element and hierarchy. Chrome speech recognition supports numerous languages (see the “langs” table in the demo source), as well as some right-to-left languages that are not included in this demo, such as he-IL and ar-EG.
After setting the language, we call recognition.start() to activate the speech recognizer. Once it begins capturing audio, it calls the onstart event handler, and then for each new set of results, it calls the onresult event handler.
recognition.onresult = function(event) {
var interim_transcript = '';
for (var i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
final_transcript += event.results[i][0].transcript;
} else {
interim_transcript += event.results[i][0].transcript;
}
}
final_transcript = capitalize(final_transcript);
final_span.innerHTML = linebreak(final_transcript);
interim_span.innerHTML = linebreak(interim_transcript);
};
}
This handler concatenates all the results received so far into two strings: final_transcript and interim_transcript. The resulting strings may include "\n", such as when the user speaks “new paragraph”, so we use the linebreak function to convert these to HTML tags <br> or <p>. Finally it sets these strings as the innerHTML of their corresponding <span> elements: final_span which is styled with black text, and interim_span which is styled with gray text.
interim_transcript is a local variable, and is completely rebuilt each time this event is called because it’s possible that all interim results have changed since the last onresult event. We could do the same for final_transcript simply by starting the for loop at 0. However, because final text never changes, we’ve made the code here a bit more efficient by making final_transcript a global, so that this event can start the for loop at event.resultIndex and only append any new final text.
That’s it! The rest of the code is there just to make everything look pretty. It maintains state, shows the user some informative messages, and swaps the GIF image on the microphone button between the static microphone, the mic-slash image, and mic-animate with the pulsating red dot.
The mic-slash image is shown when recognition.start() is called, and then replaced with mic-animate when onstart fires. Typically this happens so quickly that the slash is not noticeable, but the first time speech recognition is used, Chrome needs to ask the user for permission to use the microphone, in which case onstart only fires when and if the user allows permission. Pages hosted on HTTPS do not need to ask repeatedly for permission, whereas HTTP hosted pages do.
So make your web pages come alive by enabling them to listen to your users!
We’d love to hear your feedback...
As we reported last week, there's been a lot happening lately with our old friend WebRTC.
Well... here's another first: WebRTC screensharing.
Here's a screencast: youtube.com/watch?v=tD0QtBUZsF4
...and here's the code: github.com/samdutton/rtcshare
In essence, we've built an experimental Chrome extension that uses RTCPeerConnection and chrome.tabCapture to share a live 'video' of a browser tab. If you want to try it out, you'll need Chrome Canary, and you'll need to enable Experimental Extension APIs on the about:flags page.
Our prototype relies heavily on the mighty apprtc.appspot.com demo and, to be frank, it's a bit of a hack! But... it's a proof of concept, and it works.
Here's how we did it:
When the user clicks the extension icon (the 'record button' next to the address bar), the extension's background script background.js, appends an iframe to itself, the src of which is rtcshare.appspot.com. In background.js it's only used to get values such as token and room_key. We told you this was a hack :^}! This is a chopped and channeled version of apprtc.appspot.com. As with the apprtc example, rtcshare.appspot.com is also used for the remote client.
chrome.browserAction.onClicked.addListener(function(tab) {
var currentMode = localStorage["capturing"];
var newMode = currentMode === "on" ? "off" : "on";
if (newMode === "on"){ // start capture
appendIframe();
} else { // stop capture
chrome.tabs.getSelected(null, function(tab){
localStream.stop();
onRemoteHangup();
});
// set icon, localStorage, etc.
}
}
When the iframe has loaded, background.js gets values from it (generated by the rtcshare.appspot.com app) and calls chrome.tabCapture.capture() to start capturing a live stream of the current tab.
function appendIframe(){
iframe = document.createElement("iframe");
iframe.src="https://rtcshare.appspot.com";
document.body.appendChild(iframe);
iframe.onload = function(){
iframe.contentWindow.postMessage("sendConfig", "*");
};
}
// serialised config object messaged by iframe when it loads
window.addEventListener("message", function(event) {
if (event.origin !== "https://rtcshare.appspot.com"){
return;
}
var config = JSON.parse(event.data);
room_link = config.room_link; // the remote peer URL
token = config.token; // for messaging via Channel API
// more parameter set from config
);
function startCapture(){
chrome.tabs.getSelected(null, function(tab) {
var selectedTabId = tab.id;
chrome.tabCapture.capture({audio:true, video:true}, handleCapture); // bingo!
});
}
Once the live stream is available (in other words, a live 'video' of the current tab), background.js kicks off the peer connection process, and signalling is done via rtcshare.appspot.com using XHR and Google's Channel API. All in all, it works like the apprtc demo, except that the video stream communicated to the remote peer is from chrome.tabCapture and not getUserMedia().
function handleCapture(stream){
localStream = stream; // used by RTCPeerConnection addStream();
initialize(); // start signalling and peer connection process
}
For demo purposes, this prototype extension opens a new tab with the URL provided by rtcshare.appspot.com, which has a 'room number' query string added. Of course, this URL could be opened on another computer, in another place, and THAT might be the start of something useful!
chrome.tabs.create({url: room_link});
We envisage a lot of interesting use cases for screensharing and, even at this early stage of development, we're impressed at how responsive and stable plugin-free tab capture and sharing can be.
As ever, we welcome your comments: about this extension and about the WebRTC APIs in general. If you want to learn more about WebRTC, check out the HTML5 Rocks article or our Quick Start Guide.
Happy hacking -- and best wishes for 2013 from everyone at HTML5R and WebRTC!
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.
A lot has happened with WebRTC over the last few weeks. Time for an update!
In particular, we're really excited to see WebRTC arriving on multiple browsers and platforms.
getUserMedia is available now in Chrome with no flags, as well as Opera, and Firefox Nightly/Aurora (though for Firefox you'll need to set preferences). Take a look at the cross-browser demo of getUserMedia at simpl.info/gum—and check out Chris Wilson's amazing examples of using getUserMedia as input for Web Audio.
webkitRTCPeerConnection is now in Chrome stable and it's flagless. TURN server support is available in Chrome 24 and above. There's an ultra-simple demo of Chrome's RTCPeerConnection implementation at simpl.info/pc and a great video chat application at apprtc.appspot.com. (A word of explanation about the name: after several iterations, it's currently known as webkitRTCPeerConnection. Other names and implementations have been deprecated. When the standards process has stabilised, the webkit prefix will be removed.)
WebRTC has also now been implemented for desktop in Firefox Nightly and Aurora, and for iOS and Android via the Ericsson Bowser browser.
DataChannel is a WebRTC API for high performance, low latency, peer-to-peer communication of arbritary data. The API is simple—similar to WebSocket—but communication occurs directly between browsers, so DataChannel can be much faster than WebSocket even if a relay (TURN) server is required (when 'hole punching' to cope with firewalls and NATs fails).
DataChannel is planned for version 25 of Chrome, behind a flag – though it may miss this version. This will be for experimentation only, may not be fully functional, and communication won't be possible with the Firefox implementation. DataChannel in later versions should be more stable and will be implemented so as to enable interaction with DataChannel in Firefox.
Firefox Nightly/Aurora supports mozGetUserMedia, mozRTCPeerConnection and DataChannel (but don't forget to set your about:config preferences!)
Here's a screenshot of DataChannel running in Firefox:
This demo is at http://mozilla.github.com/webrtc-landing/data_test.html. Here's a code snippet:
pc1.onconnection = function() {
log("pc1 onConnection ");
dc1 = pc1.createDataChannel("This is pc1",{}); // reliable (TCP-like)
dc1 = pc1.createDataChannel("This is pc1",{outOfOrderAllowed: true, maxRetransmitNum: 0}); // unreliable (UDP-like)
log("pc1 created channel " + dc1 + " binarytype = " + dc1.binaryType);
channel = dc1;
channel.binaryType = "blob";
log("pc1 new binarytype = " + dc1.binaryType);
// Since we create the datachannel, don't wait for onDataChannel!
channel.onmessage = function(evt) {
if (evt.data instanceof Blob) {
fancy_log("*** pc2 sent Blob: " + evt.data + ", length=" + evt.data.size,"blue");
} else {
fancy_log('pc2 said: ' + evt.data, "blue");
}
}
channel.onopen = function() {
log("pc1 onopen fired for " + channel);
channel.send("pc1 says Hello...");
log("pc1 state: " + channel.state);
}
channel.onclose = function() {
log("pc1 onclose fired");
};
log("pc1 state:" + channel.readyState);
}
More information and demos for the Firefox implementation are available from the hacks.mozilla.org blog. Basic WebRTC support is due for release in Firefox 18 at the beginning of 2013, and support is planned for additional features including getUserMedia and createOffer/Answer constraints, as well as TURN (to allow communication between browsers behind firewalls).
For more information about WebRTC, see Getting Started With WebRTC. There's even a WebRTC book, available in print and several eBook formats.
Constraints have been implemented in Chrome 24 and above. These can be used to set values for video resolution for getUserMedia() and RTCPeerConnection addStream() calls.
There's an example at simpl.info/getusermedia/constraints. Play around with different constraints by setting a breakpoint and tweaking values.
A couple of gotchas... getUserMedia constraints set in one browser tab affect constraints for all tabs opened subsequently. Setting a disallowed value for constraints gives a rather cryptic error message:
navigator.getUserMedia error: NavigatorUserMediaError {code: 1, PERMISSION_DENIED: 1}
Likewise the error if you try to use getUserMedia from the local file system, not on a server!
Tab Capture is now available in the Chrome Dev channel. This makes it possible to capture the visible area of the tab as a stream, which can then be used locally, or with RTCPeerConnection's addStream(). Very useful for sceencasting and web page sharing. For more information see the WebRTC Tab Content Capture proposal.
Keep us posted by commenting on this update: we'd love to hear what you're doing with these APIs.
...and don't forget to file any bugs you encounter at new.crbug.com!