Professional Documents
Culture Documents
Animating Single Div Art - CSS-Tricks
Animating Single Div Art - CSS-Tricks
Published Updated
May 31, 2017 May 31, 2017
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
(http://a.singlediv.com/) and others where you take a single generic <div> to create a beautiful
cactus (http://a.singlediv.com/#cactus2) , Alamo (http://a.singlediv.com/#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
(https://hacks.mozilla.org/2014/09/single-div-drawings-with-css/) .
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
(http://a.singlediv.com/#bb8) ). 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.
CSS
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.
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.
CSS
div {
/* using background definition from earlier */
--shine: linear-gradient(to right, transparent 29.5%, red 29.5%, red
--shine-blue: linear-gradient(to right, transparent 29.5%, blue 29.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:
CSS
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 .
CSS
@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%
(https://www.w3.org/TR/css-variables/#syntax) . 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.
Embedded Pen Here
JavaScript
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(--whi
}
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).
Each of the nine buttons has CSS uses the following default circle, where --color1 is a light blue
and --button-dim is 1.4vmin:
CSS
--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:
CSS
--button4: radial-gradient(circle,
var(--button4-color, var(--color1)) var(--button4-dim, var(--button-d
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.
JavaScript
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.
CSS
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 .
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.
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.