Mozilla

Firefox 4: Drawing arbitrary elements as backgrounds with -moz-element


This is a guest post by Markus Stange. Markus usually works on the Firefox Mac theme implementation, but this time he went on a small side trip through the Gecko layout engine in order to implement -moz-element.

In Firefox Beta 4 we’re introducing a new extension to the CSS background-image property: the ability to draw arbitrary elements as backgrounds using -moz-element(#elementID).

<p id="myBackground1" style="background: darkorange; color: white;  width: 300px; height: 40px;">
  This element will be used as a background.
</p>
<p style="background: -moz-element(#myBackground1); padding: 20px 10px; font-weight: bold;">
  This box uses #myBackground1 as its background!
</p>



A -moz-element() image works just like a normal url() image. That means it’s subject to all the familiar background properties like background-position, background-repeat, and even background-size.

Using background-size you can create a thumbnail of the referenced element, for example:

<ul id="thumbnails">
  <li style="background-image: -moz-element(#slide-0)"></li>
  <li style="background-image: -moz-element(#slide-1)"></li>
  <li style="background-image: -moz-element(#slide-2)"></li>
  <li style="background-image: -moz-element(#slide-3)"></li>
</ul>
#thumbnails li {
  width: 160px;
  height: 120px;
  background-repeat: no-repeat;
  background-size: contain;
}



There are three things to keep in mind about -moz-element:

  1. It’s live: whenever something happens in the referenced element, the -moz-element background image will be updated. It will also show things like text selection or blinking carets.

  2. It’s purely visual. That means you can’t “click through” to the original element. That’s by design.

  3. It works with any HTML element. Even with <iframe>



    <video>



    … and <canvas>.



Canvas-as-background is in fact useful for some applications. For example, if you’re applying sepia tone to CSS background images in the browser, you now no longer have to convert the processed canvas image into a data URI. Instead, you can just set the canvas itself as the background image.

Using a canvas as a background image is supported by Webkit, too, using -webkit-canvas().

Painting loops

A quick note on recursive references: If you try to paint an element that is already being painted via -moz-element, a painting loop will be detected and prevented. So you’ll need to think of a different way of drawing your Sierpinski carpets.

Hiding the Referenced Element

Sometimes you don’t want the original referenced element to be visible, only the -moz-element background image. So what do you do? You can’t just set display: none or visibility: hidden on the element, because then there’s nothing to draw in the -moz-element background image either – it will be transparent.

Instead, you need to prevent the element from being rendered on the screen without really hiding it. One way of doing that is to wrap it with another element that has height: 0; overflow: hidden; set on it.

There are three types of elements that are exempt from this rule: images, canvases and videos. These kinds of elements can have display: none and still be used in -moz-element. In fact, they don’t even need to be in the DOM.

New DOM API:
document.mozSetImageElement

We added a new method to the document object: document.mozSetImageElement(<elementID>, <element>).

Consider this piece of code:

var slide5 = document.getElementById("slide-5");
document.mozSetImageElement("current-slide", slide5);

Now all elements with background-image: -moz-element(#current-slide) will draw the element with the ID slide-5, even if there is a real element with the ID current-slide!

Calling document.mozSetImageElement("current-slide", null) will stop the override.

This API can be handy in a variety of use cases. I already alluded to one of them in the previous section: with mozSetImageElement you can use canvas and image elements that aren’t part of the DOM tree.

var img = new Image();
img.src = "my_image.png";
document.mozSetImageElement("image", img);
 
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 100;
var ctx = canvas.getContext("2d");
// ... draw into ctx ...
document.mozSetImageElement("canvas", canvas);

View Demo

Another scenario that benefits from mozSetImageElement involves JavaScript utility libraries. You might have a function like this:

var runningNumber = 0;
function addReflectionToElement(reflectedElement) {
  var referenceID = "reflected-element-" + runningNumber++;
  var reflection = document.createElement("div");
  reflection.className = "reflection";
  reflection.style.backgroundImage =
    "-moz-element(#" + referenceID + ")";
  document.mozSetImageElement(referenceID, reflectedElement);
  // ... insert reflection into the DOM ...
}

This way you can minimize the impact of your utility function because you don’t have to manipulate the ID of the element that was passed in.

Finally, mozSetImageElement also allows you to reference elements from other documents, for example from inside iframes – obeying same origin restrictions, of course.

-moz-element for SVG paint servers: patterns and gradients

If you’ve ever written any SVG by hand you’re familiar with the concept of paint servers: those are the things you can use in your fill and stroke attributes when you don’t just want a single solid color. Now you can use them on HTML elements, too, using -moz-element:

<p style="background: -moz-element(#pattern),
                      -moz-element(#gradient);
          padding: 10px; color: white">
  This element has both types of SVG paint servers
  in its background: a pattern and a gradient.
</p>
 
<svg height="0">
  <linearGradient id="gradient" x2="0" y2="1">
    <stop stop-color="black" offset="0%"/>
    <stop stop-color="red" offset="100%"/>
  </linearGradient>
  <pattern id="pattern" patternUnits="userSpaceOnUse"
           width="60" height="60">
    <circle fill="black" fill-opacity="0.5"
            cx="30" cy="30" r="10"/>
  </pattern>
</svg>


Note that we didn’t even have to use XHTML in order to be able to embed SVG thanks to our new HTML5 parser.

This feature overlaps the functionality of CSS gradients and SVG images, but is very useful in some situations, such as animations. For example, say you want to create a progress bar with an animated gradient like this:

You could do this with a CSS gradient and some JavaScript that periodically updates the background-position property. But you could also use an SVG gradient that’s animated with SMIL, requiring no JavaScript whatsoever:

<div style="background: -moz-element(#animated-gradient);">
</div>
 
<svg height="0">
 
  <linearGradient id="animated-gradient" spreadMethod="reflect"
                  gradientUnits="userSpaceOnUse"
                  x1="16" x2="24" y2="0">
    <animate attributeName="x1" values="16; 0" dur="350ms"
             repeatCount="indefinite"/>
    <animate attributeName="x2" values="24; 8" dur="350ms"
             repeatCount="indefinite"/>
 
    <stop stop-color="#0F0" offset="0"/>
    <stop stop-color="#0D0" offset="100%"/>
  </linearGradient>
 
</svg>

View Demo

The same could be achieved with CSS animations, but as long as they’re not implemented in Gecko you can use this.

Support for SVG as a CSS background (bug 276431) will be added soon.

Also, here’s a CSS + SVG Pacman for you.

Applications

I have two more suggestions for -moz-element usage:

Reflections

What is a reflection?

#reflection {
  /* It's a copy of the original element... */
  background: -moz-element(#reflected-element)
              bottom left no-repeat;
 
  /* ... turned upside down ... */
  -moz-transform: scaleY(-1);
 
  /* ... with a gradual fade-out effect towards the bottom. */
  mask: url(#reflection-mask);
}



Because we can apply arbitrary styles to the reflection, we can produce effects like animated water ripples.

Fancy Slide Transitions

In this demo I’d like to have a slideshow transition that looks like the upper half of the previous slide gets folded down to reveal the next slide:

How would you implement this? You’ll obviously need to use some kind of transform, but on what element? The upper half of the slide needs to have a different transform than the lower half, so you can’t just set the transform on the slide itself.

I ended up creating four new elements: #previousUpper, #previousLower, #nextUpper and #nextLower. I put them into a separate container called #transition which is only made visible while a transition is in progress. Then I gave them the right size and assigned the corresponding subimage of the previous / next slides to them using background-image: -moz-element(#previous/nextSlide) and the right background-position. And finally I set the transform on these helper elements.

The code for it gets quite complex, though, so I’ll just direct you to the finished demo.

More?

My ideas for -moz-element demos have run out for the moment, but there’s bound to be more stuff one can do with it. Now it’s your turn!

Credits

Most of the credit here should go to Robert O’Callahan who cooked up the initial implementation back in 2008. After his initial experiments he had to work on more important things, though, so his patches lay dormant for about a year until he started a newsgroup thread to work out the right API in July 2009. Shortly after that, Ryo Kawaguchi revived roc’s work and spent the last weeks of his internship at Mozilla on it. Another year later I made the patch ready for review and drove it through the final stages until checkin.

The same warnings as for mozRequestAnimationFrame apply: -moz-element and document.mozSetImageElement are experimental APIs. We do not guarantee to support them forever, and we wouldn’t evangelize sites to depend on them. We’ve implemented them so that people can experiment with them and we can collect feedback. We’ll propose it as a standard (minus the moz prefix, obviously), and author feedback on our implementation will help us make a better standard.

37 comments

Comments are now closed.

  1. DesignMango wrote on August 24th, 2010 at 14:38:

    Amazing, simply amazing.

  2. Ken Snyder wrote on August 24th, 2010 at 18:14:

    Brilliant! Innovation like this pushes standards, pushes other browser makers, and inspires developers.

  3. foxfan wrote on August 24th, 2010 at 20:00:

    Couldn’t you have used a css gradient as a 2nd background image for the reflection mask? …. nevermind, the would only work going from black to transparent or white to transparent to match the background color.

    But speaking of transparent gradients. It seems if you use the keyword “transparent” as a color stop you get a transition to to a transparent gray. Not pretty.

    p.s.
    anxiously awaiting the loop attribute for audio and video

    keep up the good work

    1. Markus Stange wrote on August 25th, 2010 at 01:11:

      That’s because “transparent” in fact means “transparent black”, so while the color’s opacity is transitioning to zero, the color value transitions to black. If you want to have a gradient between opaque white and transparent white, you need to use rgba(255, 255, 255, 0) instead of “transparent” as a color stop.

  4. narendra wrote on August 24th, 2010 at 22:19:

    Brilliant ! But I am thinking about security issues with this.
    is it possible to post html page as background image to someother server ?

  5. Robert O’Callahan wrote on August 24th, 2010 at 23:36:

    No.

  6. JM wrote on August 25th, 2010 at 02:38:

    Can mozSetImageElement accept HTMLElement for its first argument? It would be useful to use e.g. querySelector there.

    1. Markus Stange wrote on August 26th, 2010 at 02:46:

      I don’t understand. mozSetImageElement takes a string as the first argument (the overriden ID), so the answer to your question is probably no. What exactly do you have in mind?

      Of course it’s possible to do things like this:
      document.mozSetImageElement(“someid”, document.querySelector(“#nav li.active a”)), but I’m not sure if that’s what you mean.

  7. Ric wrote on August 25th, 2010 at 02:44:

    Great stuff. Seeing the examples really shows the power…

  8. Ric wrote on August 25th, 2010 at 02:50:

    One more thing, will we see Prism being updated to the latest version of Gecko any time soon? I like using it for clients who’s IT departments don’t like Firefox being installed.

  9. Alexis Deveria wrote on August 25th, 2010 at 05:32:

    Brilliant! I love how this this offers an alternative solution to the similar webkit properties. Great work Robert, Ryo and Markus.

    One question: Are you planning on writing a spec for this to be submitted to the CSS WG?

    1. Robert O’Callahan wrote on August 25th, 2010 at 21:37:

      Yes, of course! :-)

  10. Tiago Sá wrote on August 25th, 2010 at 05:48:

    I can see a potential problem with this, from he user’s perspective. Which is, basically, those sites that don’t want the user to copy their text, they will use a non clickable copy of their text on top of their text, and that’s it. It’s good for them and for the user, if they did it with Flash before, but it’s not so good if it becomes so simple to do it becomes widespread.

    I’ll try to make a proof of concept if I have time and try to see what can possibly be done about that, from our side.

    1. Robert O’Callahan wrote on August 25th, 2010 at 15:00:

      There are already many ways to do that, e.g. by placing a DIV over the content to capture mouse clicks.

      The good news is that unlike with Flash, on the Web Greasemonkey-like hacks can always get the user back in control with not much effort.

  11. Andy L wrote on August 25th, 2010 at 06:04:

    Extraordinary!

  12. dimmaq wrote on August 25th, 2010 at 06:27:

    > Fancy Slide Transitions

    Error: uncaught exception: [Exception… “Index or size is negative or greater than the allowed amount” code: “1” nsresult: “0x80530001 (NS_ERROR_DOM_INDEX_SIZE_ERR)” location: “http://hacks.mozilla.org/wp-content/uploads/2010/08/tada.html Line: 180″]

  13. Danny Moules wrote on August 25th, 2010 at 08:52:

    Question: What about WebGL support?

    1. Robert O’Callahan wrote on August 25th, 2010 at 21:38:

      What about it? -moz-element() references to WebGL canvas should work fine.

  14. Andrés Delfino wrote on August 25th, 2010 at 09:51:

    Note that this is not working for Flash, and I believe other plug-ins are not supported.

    I guess this is by design, right?

    1. Robert O’Callahan wrote on August 25th, 2010 at 15:01:

      Windowless plugins (wmode=transparent/opaque) should render fine. Windowed plugins can’t have their rendering captured this way, it’s a fundamental technical limitation.

  15. Matthew Holloway wrote on August 25th, 2010 at 16:13:

    Great idea!

    I think that having to surround it in a height:0px seems a bit daft… it would still be available to screen readers and so for accessibility reasons you wouldn’t want to have background content in the foreground page. Perhaps you need a flag in the -moz-element() expression, e.g.,

    -moz-element(#myBackground1, display:none);

    1. Markus Stange wrote on August 26th, 2010 at 02:32:

      Over on Robert’s blog [1], Dave Hyatt suggested adding display:paint-server for that use case. This might be worth doing.

      [1] http://weblogs.mozillazine.org/roc/archives/2008/07/the_latest_feat.html#comments

  16. Jose wrote on August 25th, 2010 at 16:38:

    Amazing! One question regarding reflections: Webkit browsers are using -webkit-box-reflect. Is this a sign that -moz-box-reflect is not going to happen? Has there been any communication with webkit developers (or other browsers) about -moz-element? And if so, what’s the outlook for them to implement this? Thanks!

    1. Robert O’Callahan wrote on August 25th, 2010 at 21:53:

      -webkit-box-reflect is more convenient but significantly less powerful than -moz-element reflections. I like the extra power, especially the ability to apply arbitrary styles to the reflection. I’m not sure if it’s worth having a more convenient syntax for reflections than -moz-element gives you. If we added the small extension -moz-element(self) to refer to the styled element, you could create a reflection like this:
      .reflect { position:relative; }
      .reflect::after { position:absolute; left:0; top:100%; width:100%; height:100%; transform:scaleY(-1); mask:url(effects.svg#gradientMask); background:element(self); }
      That’s not too bad, maybe. It gives you lots of flexibility in positioning, transforming, masking, filtering, etc.

      Another option might be to introduce a ::reflection CSS psuedo-element, so you could write something like this:
      .reflect::reflection { reflection:bottom; mask:url(effects.svg#gradientMask); }
      I dunno if it’s worth it. That’s something we need author experience to help guide us with.

      A couple of years ago when I first proposed -moz-element on my blog, Dave Hyatt at Apple thought it was a good idea. But we haven’t talked to them about it since. We’ll propose a spec for it and see what the reaction is.

      1. Jose wrote on August 26th, 2010 at 02:06:

        Thanks, that all makes sense. Regarding the (self) extension, I think that would be the icing on the cake.

      2. Stan Rogers wrote on August 31st, 2010 at 16:59:

        Why in the world would a designer want to become entangled with unnecessary SVG? There ought to be at least the option to make the mask a simple gradient. And this implementation would affect document layout, unlike -webkit-box-reflect, so the two implementations would require different margin settings on the reflected element. That takes us out of the realm of progressive enhancement (if it doesn’t work in Browser X, no harm done) back to the world of browser sniffing and bug-hacking. That’s bad.

  17. narendra wrote on August 25th, 2010 at 23:25:

    just curious, is it possible to have recursion ?
    two div element referring each other for background image !!

    1. Markus Stange wrote on August 26th, 2010 at 02:39:

      It’s not possible, see the section under “Painting loops”.

  18. James Tang wrote on August 26th, 2010 at 00:28:

    It’s really amazing an powerful!

  19. Lachu wrote on August 26th, 2010 at 01:52:

    Many people can don’t understood idea of -moz-element. In my opinion more impressive is background-size: contain.

    We will probably only uses -moz-element to show svg as a background, but we can also use z-order.

  20. pawel wrote on August 26th, 2010 at 10:01:

    Crazy, the nightly support the embedding of SVG via the new element, but not as url(circle.svg).

    This element will be used as a background.

    1. Robert O’Callahan wrote on August 26th, 2010 at 14:09:

      Support for SVG images is very nearly done and should land soon.

  21. Jason wrote on August 26th, 2010 at 11:30:

    I’ve actually tried setting the background to a -moz-element of a flash object. And with the one I’ve tried it really slowed down the page and rendering. Of course it’s probably not recommended doing it for large areas, with normal repeat.

    I can see -moz-element being useful for navigation on single page sites, with thumbnails showing where to jump to.

    I guess you could also try building simple geometric patterns as backgrounds with differently coloured squares such as a checker pattern.

  22. LG wrote on September 1st, 2010 at 15:55:

    animated text that glows and flies around in the background with text-shadow to make them look ethirial, maybe a persons’ own twitter feed that could fly around… add to that the possibility of ordering the flying text when a text-box is focused upon for replying, and a mouse-over a tweet adds the username that made the tweet to the text-box for easier replying?

    all without flash, webgl or svg?
    is it even possible?

    1. LG wrote on September 2nd, 2010 at 02:35:

      of course, while your writing a reply to a tweet in the cloud you can see a preview of what your next tweet will look like in the cloud as you form it.

  23. josh wrote on September 3rd, 2010 at 18:36:

    are there any web pages using it now

  24. Dan wrote on March 19th, 2011 at 09:55:

    I don’t like how this encourages you to put all sorts of purely superfluous, cosmetic markup in a page.

Comments are closed for this article.