Vincent De Oliveira Back home

CSS element() function

Blog

Reading : 10min

Article également dispo en [FR] La fonction CSS element()

If this article helps you Buy me a coffee!

In July, I wrote about advanced CSS filters techniques, such as backdrop-filter and filter(). Today, I want to share a much more awesome CSS feature. But before starting, let me warn you: the feature I’ll show is only supported in Firefox for now and no other browser vendor has shown interest with. Maybe, this could change in a near future. I really hope so. So, spread the world.

If you're not using Firefox right now, maybe you should switch to get live demos working. Otherwise, I've added videos.

element()

The CSS Image Values and Replaced Content Module Level 4 introduces the element() function. This function was previously defined in Level 3 and so Firefox already has support for it, since its version 4 (May 2011!). To put it simply, this function renders any part of a website as a live image. A. Live. Image! As you see a DOM element rendered right in the browser, you’ll get an image of it. Every changes to that element will be immediately seen in real-time in the image, even text selection.

When I first discover this property back in 2011, I don’t believe it myself. How cool is that? How can this be even possible?

Well, it’s actually working, and the syntax is very basic. Just reference the element you want to get a live view, using its id attribute. For example, here’s a text and an image in div#css-source. The live image of this element can be used as div#css-result's background.

<div id="css-source">
    <p>Lorem ipsum</p>
    <img src="" alt="">
</div>
<div id="css-result"></div>
#css-result {
    background: element(#css-source);
    background-size: 50% 50%;
}

As element() creates an image, you can use every CSS properties you’re familiar with to apply and control it, like background, background-repeat, background-size and so on.

Here's a live demo of what I'm talking about

See the Pen vOwaWz by iamvdo (@iamvdo) on CodePen

Live result with Firefox

Have in mind that any part of a website can be referenced, even the whole site if you need it. Be careful though, your element itself can be a child of your source, so elements may appear twice or more. However, Firefox deals well with recursive references.

element() brings CSS design to a new level, in an easily way. Few ideas that comes to my mind (some that I've already used since the last 4 years):

Few things we can note:

Reflections

We all know reflections are no longer a web design trend anymore (hello web 2.0!), but it’s a good starting point to better understand element(). The following demo is composed by an image and its <figcaption>, both inside a <figure> tag. The element() function is used on the ::after pseudo-element’s background to get a live view of <figure>, while flipping it along the Y axis, and masking it using a SVG mask. The whole effect is done inside @supports at-rule to deal with progressive enhancement:

<figure class="reflection" id="css-element">
    <img src="image.jpg" alt="">
    <figcaption>San Francisco, CA</figcaption>
</figure>
@supports (background: element(#css-element)) {
    .reflection::after {
        background: element(#css-element);
        transform: scaleY(-1);
        mask: url('#mask');
        opacity: .3;
    }
}

The live demo works in Firefox, and fallbacks to the old, non-standard -webkit-box-reflect property for WebKit based browsers (no support in IE/Edge)

See the Pen aOraoG by iamvdo (@iamvdo) on CodePen

Yeah, I know you’re tired of seeing this effect. Let’s go deeper.

3D Paperfold effect

In some advanced effects, you sometimes have to deal with duplicated content, and the only reasonable choice here is JavaScript. It’s pretty easy when handle static content (images, texts, etc.) but it becomes really painful with dynamic ones. With element(), it’s really straightforward.

For example, you can easily fold this Twitter login form into 2 pieces (hover over it with Firefox):

See the Pen xGNaGJ by iamvdo (@iamvdo) on CodePen

Live result with Firefox

Let me explain:

  • the HTML login form is created and positioned
  • then, a layer mask is added on top of it, so the form is no longer visible
  • two pseudo-elements (::before and ::after) are added on the form, and are put on top of the layer mask
  • each pseudo-element is positioned at the exact same position than the login form and reference it using element()
  • then, CSS transforms, animations and filters are applied to these two pseudo-elements
  • also pointer-events: none is used to delegate events to the underlying layer that contains the login form, so the form is fully working
  • all this stuff happen only if element() is supported, inside @supports

Going further, we can fold anything that’s inside the page, like an interactive map:

See the Pen OVYoXy by iamvdo (@iamvdo) on CodePen

Live result with Firefox

Animated background

A simple effect could also be to create animated backgrounds. Well, you may think of ol’ GIF animated background, but element() offers new possibilities like using a <video>, a <canvas> or a <svg> tag. Combine <video>, <canvas> and duplicated content and you can create this crazy 30+ pieces fold effect where you can draw in it while animation occurs. Pretty fun!

See the Pen GJaXvJ by iamvdo (@iamvdo) on CodePen

Live result with Firefox

You may notice that this demo is also working in WebKit based browsers. Here’s how:

  • I’ve replaced <video> tag with an animated GIF as it works in CSS background. The drawback is that GIF file size is very heavy compared to the video: ~4MB (GIF) vs ~400KB (MP4) and ~600KB (WEBM). So I reduced frames in this case.
  • I’ve used -webkit-canvas() which is similar to element(), but restricted to, well, <canvas>. It’s a “not so bad” solution here because I was referencing a canvas. Be careful though, this function is non-standard and deprecated.

Faking backdrop-filter

With element(), it becomes quite simple to create a backdrop-filter workaround, and so, increase browser support. What you have to do is set an element’s background to be the live view of the element that is below it. Simple, right?

You can see one of my demos from previous article, now including Firefox support using element():

See the Pen RPmYQP by iamvdo (@iamvdo) on CodePen

And one with dynamic content:

See the Pen djEBu by iamvdo (@iamvdo) on CodePen

Code is self explanatory:

h1 { … }

@supports ( backdrop-filter: blur(1px) ) {
    h1 {
        backdrop-filter: grayscale(1) contrast(3) blur(1px);
    }
}

@supports (not (backdrop-filter: blur(1px))) and (background: element(#back)) {
    h1::before {
        content: '';
        position: absolute;
        z-index: -1;
        top: 0; left: 0; bottom: 0; right: 0;
        background: element(#back) fixed;
        filter: grayscale(1) contrast(3) blur(1px);
    }
}

Using @supports, you can test:

  • if backdrop-filter is supported, apply it to the <h1>
  • if backdrop-filter isn’t supported but element() is, create a pseudo-element that will be positioned below the title, set its background to be the live view of the background website and apply filter.

It is also worth mentioning that you can fake backdrop-filter with SVG filters. Something like (see HTML tab):

See the Pen VLOGdw by iamvdo (@iamvdo) on CodePen

This way, you provide much better support, but there are many drawbacks. This SVG filter is not dynamic, even if it is theoritically possible. Indeed, no browsers support backgroundImage as input for filter primitives. IE/Edge supports the deprecated enable-background property to access the backgroundImage input, but only for SVG content.

How to hide references

In many effects, I had to create a layer mask to hide parts of the page. It’s because you can’t display: none an element that is currently used as a live background. Actually, the live image will no longer display that element at all.

I’ve also tried to wrap the reference element inside a <div> tag with height: 0 and overflow: hidden. That way, the element is still present in the page (and can be referenced as a live view) but no longer visible, so you don’t need for a layer mask. The problem is that some browsers degrade performance of unvisible elements (CSS animations, unanimated GIF images, etc.) and this is not what we want in that specific case.

So I ended up using the mask technique. Do you think of any other solution?

Sum up

Hopefully, I’ve convinced you that CSS element() is so awesome, despite its poor browser support and rendering performance apart. You should try it for yourself and share your awesome demos. We have to show our interest in. No doubt this will encourage browsers to consider it (again for Firefox)

If this article helps you Buy me a coffee!

Advanced CSS filters UI with rotating color scheme