Animating Single Div Art

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.

See the Pen #dailycssimages 01: Bear Cub by Lynn Fisher (@lynnandtonic) on CodePen.

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:

See the Pen Single Div Accordion (Animated with CSS Variables) by Dan Wilson (@danwilson) on CodePen.

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.

See the Pen Single Div Accordion Breakdown: Transforms by Dan Wilson (@danwilson) on CodePen.

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.

The left side of the accordion boils down to:

background:
    var(--shine),
    var(--shine),
    var(--button-key1, var(--button)),
    var(--button-key2, var(--button)),
    var(--button-key3, var(--button)),
    var(--black-keys),
    var(--white-keys),
    var(--keyboard-base);

Those variable values might be several lines long (even hundreds), but conceptually how the layers of the keyboard come into play are clearer thanks to the variables.

See the Pen Single Div Accordion Breakdown: Keyboard by Dan Wilson (@danwilson) on CodePen.

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:

div {
  /* using background definition from earlier */
  --shine: linear-gradient(to right, transparent 29.5%, red 29.5%, red 70.5%, transparent 70.5%);
  --shine-blue: linear-gradient(to right, transparent 29.5%, blue 29.5%, blue 70.5%, transparent 70.5%);
  animation: modify-shine 2000ms infinite alternate ease-in-out;
}
@keyframes modify-shine {
  100% {
    background:
      var(--shine-blue), /*these two replace the original --shine*/
      var(--shine-blue),
      /* the rest of the background remains unchanged */
      var(--button-key1, var(--button)),
      var(--button-key2, var(--button)),
      var(--button-key3, var(--button)),
      var(--black-keys),
      var(--white-keys),
      var(--keyboard-base);
  }
}

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:

div {
  --static-component: 
    var(--button-key1, var(--button)),
    var(--button-key2, var(--button)),
    var(--button-key3, var(--button)),
    var(--black-keys),
    var(--white-keys),
    var(--keyboard-base);
  background: 
    var(--shine),
    var(--shine),
    var(--static-component); 
}
@keyframes modify-shine {
  100% {
    background:
      var(--shine-blue),
      var(--shine-blue),
      var(--static-component); 
  }
}

The three musical notes in the accordion are based on this approach by animating text-shadow.

See the Pen Single Div Accordion Breakdown: Music Notes by Dan Wilson (@danwilson) on CodePen.

Animating with Custom Properties inside CSS Keyframes

A related way to change states is to directly change the custom property inside the keyframes.

@keyframes {
  0% {
    --button1-color: var(--color-primary);
  }
  100% {
    --button1-color: var(--color-secondary);
  }
}

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.

See the Pen Pixel Art Animated with Custom Properties by Dan Wilson (@danwilson) on CodePen.

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).

var div = document.querySelector('div');
var active = false;
setInterval(function() {
  active = !active;
  div.style.setProperty('--white-key-1', 
    'var(--white-key-color-' + (active ? 'active' : 'default') +')')
}, 1000);

With the corresponding CSS:

div {
  --white-key-1: var(--white-key-color-default);
  --white-key-color-default: #fff;
  --white-key-color-active: #ddd;
  /* And a linear gradient that follows the following pattern */
  background: linear-gradient(to right, var(-white-key-1) 5%, var(--white-key-2) 5%, var(--white-key-2) 10%, ...); 
}

See the Pen Single Div Piano Keys by Dan Wilson (@danwilson) on CodePen.

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).

See the Pen Single Div Accordion Breakdown: Right by Dan Wilson (@danwilson) on CodePen.

Each of the nine buttons has CSS uses the following default circle, where --color1 is a light blue and --button-dim is 1.4vmin:

--button: radial-gradient(circle, 
  var(--color1) var(--button-dim), 
  transparent var(--button-dim));

If i want to change a specific button later to a "pressed" state I can set up a specific value in the CSS, for example the fourth button:

--button4: radial-gradient(circle, 
  var(--button4-color, var(--color1)) var(--button4-dim, var(--button-dim)),
  transparent var(--button4-dim, var(--button-dim)));

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.

var enabled = false;
setInterval(function() {
  enabled = !enabled;
  div.style.setProperty('--button4-dim', enabled ? '1.2vmin' : 'var(--button-dim)');
  div.style.setProperty('--button4-color', enabled ? 'var(--color1alt)' : 'var(--color1)');
}, 1000);

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:

See the Pen Single Div Backpack by Trish Katz (@techxastrish) on CodePen.

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.

See the Pen Single Div Backpack with CSS Variables for Animation by Dan Wilson (@danwilson) on CodePen.

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).

See the Pen Single Div Animation Options by Dan Wilson (@danwilson) on CodePen.

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.