Animating Single Div Art

Share this:

When you dig deep with your tools, it is amazing what you can create out of the most basic of HTML. I have been constantly impressed with "Single Div Art" by Lynn Fisher and others where you take a single generic <div> to create a beautiful cactus, Alamo, or panda.

It can be hard to jump in and deconstruct how these are made, due to the fact they are using layers of background gradients, box shadows, text shadows, and more in the midst of just a single div and it's ::before and ::after pseudo-elements. Before we go any further… check out Lynn Fisher's article on why and how she started working with single div art.

One thing that single div pieces rarely do is animate. If you can transform your div or one of its pseudo elements, that's fair (as Lynn Fisher does with her fantastic BB-8). But you cannot directly change the opacity or transform of the individual "elements" you create inside your div, since they are not actual DOM elements.

I am a big believer of trying something a little different and interesting to learn tools you otherwise might never learn. Working with the constraints of a single div might not be great for production work, but it can be a great exercise (and challenge) to stretch your skills in a fun way. In that spirit, we'll use this technique to explore how Custom Properties (CSS Variables) work and even provide us a path to animation inside our div. To illustrate along the way we will be breaking down the following example with multiple animation approaches:

This accordion (the instrument, not the UI construct) has three main parts, the keyboard side (our div), the bellows (the part that squeezes, which is the div::before), and the button side (div::after). Since the accordion naturally divides into these pieces, we can transform each piece inside CSS Keyframe animations to get our animation started. The bellows are going between different scaleX values and the two sides are using countering translateX values to move with the scaling bellows. Thus, the squeezebox is born.

Organizing the <div> with CSS Custom Properties

Animating and thinking about the three big pieces is more straightforward than thinking about what appears inside. It can be helpful to group and name the individual bits inside the div, and Custom Properties provide us a native way to do this. Instead of levels of seemingly infinite linear gradient stops, you can define the -white-keys and --black-keys for a piano keyboard. Instead of a cross-section of multiple layered gradients you can have a --tea-cup with its individual --tea-bag and a related --tea-bag-position defined inside.

While the same can be done with Sass or Less, Custom Properties allow us to modify these values in the future. We can now conceptually think about animating just our --button-key2 or the accordion's decorative --shine. There are a few ways to tackle this.

Animating Large Property Values with CSS Keyframes

The first way is to use CSS keyframe animations to change the property that contains the piece you want to move. If you want to change something inside your background (say, for example, we want to change the color of our "shine" lines from red to blue), you can set swap out values in the background property. Building on the previous code sample:

This give us a lot, especially since background is animatable (as are text-shadow and box-shadow). In this example there would be a transition from red to blue.

If your property is long, this can be hard to maintain, though Custom Properties can give us a help by extracting out the parts that don't change to minimize the repetition. We can take it further by abstracting out pieces that don't need to animate into a new variable - resulting in levels of variables:

A custom property has no predefined behavior and is not a useful property until it is used with var(…), so the spec states changing one's value will cause it to flip its value at 50%. This is the default behavior for all CSS properties that are not animatable and means it will not transition between the values.

You may have guessed since I already mentioned the spec that this is not available in all browsers. Currently, this is supported in Chrome and Opera only.

This will be a quick way to get jump states when it is supported across browsers. If you are viewing this in Chrome or Opera, the accordion uses this approach to animate the keys on the keyboard and the buttons on the right side. For a smaller example, here is a "Pixel Art" example using this approach where the eyes and eyebrows will move in Blink browsers. Other browsers will nicely fall back to a static image. This is in many ways will use the least amount of code, but will have the least amount of support.

Animating Custom Property Values via JavaScript

A third method is to use JavaScript to set new property values directly (or apply classes that have different property values). In its basic form, this could be a call to setInterval to toggle an on/off state for a value (for our piano this could be a key pressed or not).

We are using JavaScript to set the white-key-1 to be either the value from the variable white-key-color-default or white-key-color-active depending on its state.

This method is useful when toggling something on and off (such as with a direct change in size, position, or color). This is how the buttons on the right side of the accordion are animated (as a fallback when the Keyframe approach is not supported).

This property is similar, but it replaces the --button-dim and --color1 with values that are specific to this button combined with a default value inside the var(). This default value can be specified in our variables by using the form var(--my-specific-variable, 13px). We can take it a little further and even use another variable value as our default, e.g. var(--my-specific-variable, var(--my-default-variable)). This second form is what our previous code example uses to create a specific definition for our fourth button while keeping its default value the same. If you have buttons you want to remain unchanged, they can use the default --button property in a different background-position.

In the accordion example, --button4-color or --button4-dim are never explicitly defined in the CSS. So when loaded they use the default values of --color1 and --button-dim. The JS ultimately modifies the values and creates our on/off animation.

This will give us behavior similar to changing the Custom Properties directly in a keyframe where values jump from state to state with no transition. As we've already discussed, background and the *-shadow properties are animatable (and transitionable… not in a high performance transform or opacity kind of way… but in small uses that can be okay).

If we take our current JS on/off approach and combine it with a CSS transition on the background, we can get a transition instead of a jump state.

div {
transition: background 2000ms ease-in-out;
}

Combining with requestAnimationFrame

Depending on how your individual components are composed, the ability to transition the property may not be possible. If you want to move something, you might need to look to requestAnimationFrame.

One of my favorite Single Divs out there is a backpack by Tricia Katz:

I would love for that zipper to move back and forth. With a single custom property to represent the zipper's x position we can reach for requestAnimationFrame to change that value and move the zipper right and left.

Conclusion

There are several approaches to animating inside a div that can stretch your skills. To get the broadest support we can't rely on CSS alone right now, though we can still get pretty far. Custom Properties make modifying values more direct, even when we need to combine with JavaScript (and we can lean on our variable naming to be clear what we are changing).

Whenever you want to learn a new thing in CSS or JavaScript… see if you can find a "Single Div"-esque way to learn it. When you need to conceptually break properties apart or animate complicated values, Custom Properties can bring something new to the table.

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.