Animated Gameboy in CSS
This is the technical blog post. Click here to see the actual demo.

Credit to @brentclouse for the original inspiration.
   Drawing with CSS is all about knowing what properties and techniques are available for you to leverage and abuse. The more complex the picture or animation, the more rules you'll have to bend in order to make it work.

   With the CSS specifications being so diverse, there are many ways to arrive at a certain desired effect. Being able to do it in as little code as possible is key to elegance and performance. Therefore, for this project I set myself a constraint to use as few <div>'s as possible, and instead rely as much as I could on native styling.

   Below, I share five techniques that I've learned and used for doodling in CSS.
1. Make use of letters and fonts for shapes and objects
   Fonts provide a range of characters and that we can leverage to create interesting shapes and edges. font-size, text-shadow, and letter-spacing are amongst several animatable properties that can style letters. Together, they can be used to create intricate patterns not possible (or extremely difficult) with regular CSS techniques. The legs in the processor chip are actually just a string of "l"'s that have been rotated!
<div id="processor"></div>
#processor {
  position: absolute;
  width: 56px;
  height: 56px;
  background-color: #222222;
  color: #ffffff;
  top: -28px;
  left: -28px;
  font-size: 15px;
  letter-spacing: 3px;
  font-weight: 700;
  font-family: 'Oxygen', Helvetica, arial, sans-serif;
  -webkit-animation: processor 3s infinite linear;
  animation: processor 3s infinite linear;
}
#processor::before {
  content: "llllll";
  position: absolute;
  width: 50px;
  overflow: hidden;
  text-shadow: -1px 0 0 #808080;
  -webkit-transform: rotateZ(-90deg);
  transform: rotateZ(-90deg);
  -webkit-transform-origin: 0 0;
  transform-origin: 0 0;
  left: 45px;
  bottom: -15px;
  -webkit-animation: processor-before 3s infinite linear;
  animation: processor-before 3s infinite linear;
}
#processor::after {
  content: "llllll";
  position: absolute;
  width: 50px;
  overflow: hidden;
  text-shadow: -1px 0 0 #808080;
  -webkit-transform: rotateZ(90deg) rotateY(180deg);
  transform: rotateZ(90deg) rotateY(180deg);
  -webkit-transform-origin: 0 0;
  transform-origin: 0 0;
  left: 10px;
  top: 50px;
  -webkit-animation: processor-after 3s infinite linear;
  animation: processor-after 3s infinite linear;
}
@keyframes processor {
  0% {
    width: 0;
    height: 0;
    left: 0;
    top: 0;
  }
  24% {
    width: 0;
    height: 0;
    left: 0;
    top: 0;
  }
  28% {
    width: 56px;
    height: 56px;
    left: -28px;
    top: -28px;
  }
}
@keyframes processor-before {
  0% {
    width: 0;
  }
  32% {
    width: 0;
  }
  44% {
    width: 50px;
  }
}
@keyframes processor-after {
  0% {
    width: 0;
  }
  44% {
    width: 0;
  }
  56% {
    width: 50px;
  }
}
2. Animating text with a linear-gradient (webkit only, with fallback)
   Currently, only webkit browsers have CSS support for creating text with a linear gradient fill. In short, it involves setting a linear-gradient on your element background, while applying -webkit-background-clip: text to mask away the non text background, and finally using -webkit-text-fill-color: transparent to let the gradient become the font-color.

   Since webkit properties only work on selected browsers, the above technique will cause non webkit vendors to degrade and leave behind text on a undesireable linear gradient background.

   However, thanks to this clever trick, the issue can be avoided by using -webkit-linear-gradient to force a gradient background in webkit browsers only, using color as a cross browser fallback. From here, you can animate the gradient by using background-position to achieve the desired text effect, and animate color for non supporting browsers.

   Here's how it compares between browsers:

         webkit:
non-webkit:
GAME BOY
<div id="screen-gameboy-text">GAME BOY</div>
#screen-gameboy-text {
  position: absolute;
  font-family: 'Asap', sans-serif;
  font-size: 32px;
  font-weight: 700;
  font-style: italic;
  letter-spacing: -2px;
  text-align: center;
  opacity: 0;
  width: 500px;
  left: -250px;
  top: -22px;
  -webkit-transform: skew(-5deg);
  transform: skew(-5deg);
  overflow: hidden;
  white-space: nowrap;
  color: #3232fc;
  background: -webkit-linear-gradient(0deg, #3232fc 40%, #85e367 40%, #85e367 45%, #ff52e8 45%, #ff52e8 50%, #ff0151 50%, #ff0151 55%, #f9e52e 55%, #f9e52e 60%, #e8e8e8 60%);
  background-position: 130px;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  -webkit-animation: screen-gameboy-text 3s infinite linear;
  animation: screen-gameboy-text 3s infinite linear;
}
@keyframes screen-gameboy-text {
  0% {
    color: #f9e52e;
    background-position: -120px;
    opacity: 0;
  }
  11.7% {
    opacity: 0;
  }
  12% {
    color: #f9e52e;
    background-position: -120px;
    opacity: 1;
  }
  19.5% {
    color: #f9e52e;
  }
  21% {
    color: #ff0151;
  }
  28.5% {
    color: #ff0151;
  }
  30% {
    color: #ff52e8;
  }
  37.5% {
    color: #ff52e8;
  }
  39% {
    color: #85e367;
  }
  46.5% {
    color: #85e367;
  }
  48% {
    color: #3232fc;
  }
  54% {
    background-position: 130px;
  }
  81% {
    opacity: 1;
  }
  96% {
    opacity: 0;
  }
}
3. Thinking outside the box with ::pseudo elements
   Pseudo elements ::before and ::after offer two additional elements in the DOM. The advantages of using them include minimizing HTML code, where each of them can be individually styled and animated, while tightly coupling themselves to the parent root, making the positioning of the entire grouping much easier to handle.

   Animating the color text was a very interesting challenge. On one hand, I could easily create separate <div>'s for each letter, set their own individual colors, then animate them one by one. However, under the original constraint I set of minimizing the usage of <div>'s, I looked for alternate ways to make this happen. But, even by using pseudo elements, it would only provide three animatable objects in total, not five. Was it possible to achieve this effect by using just a single element?

   ... Yes it was. Don't let anything stop you from being creative and coming up with new ways of solving old problems. Exploit the rules of CSS beyond their intentions, and let pseudo elements further that exploration.
C
<div id="glass-color-text">C</div>
#glass-color-text {
  color: #ff0151;
  position: absolute;
  top: -26px;
  left: -45px;
  font-family: 'Comic Sans', 'Comic Sans MS', 'ChalkboardSE-Bold', sans-serif, cursive;
  font-size: 35px;
  letter-spacing: -2px;
  font-weight: 700;
  -webkit-animation: glass-color-text 3s infinite linear;
  animation: glass-color-text 3s infinite linear;
}
#glass-color-text::before {
  content: "L";
  position: absolute;
  left: 18px;
  top: 17px;
  line-height: 5px;
  text-indent: 13px;
  color: #85e367;
  border: 5px #5151dd solid;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  -webkit-animation: glass-color-text-before 3s infinite linear;
  animation: glass-color-text-before 3s infinite linear;
}
#glass-color-text::after {
  content: "R";
  position: absolute;
  left: 48px;
  top: 17px;
  line-height: 5px;
  text-indent: 13px;
  color: #01b4dd;
  border: 5px #f9e52e solid;
  border-radius: 50%;
  width: 10px;
  height: 10px;
  -webkit-animation: glass-color-text-after 3s infinite linear;
  animation: glass-color-text-after 3s infinite linear;
}
@keyframes glass-color-text {
  0% {
    color: transparent;
  }
  32% {
    color: transparent;
  }
  36% {
    color: #ff0151;
  }
}
@keyframes glass-color-text-before {
  0% {
    color: transparent;
    border-color: transparent;
  }
  36% {
    border-color: transparent;
  }
  40% {
    color: transparent;
    border-color: #5151dd;
  }
  42% {
    color: #85e367;
  }
}
@keyframes glass-color-text-after {
  0% {
    color: transparent;
    border-color: transparent;
  }
  42% {
    border-color: transparent;
  }
  46% {
    color: transparent;
    border-color: #f9e52e;
  }
  50% {
    color: #01b4dd;
  }
}
4. Drawing circles and squares
   You can "draw" circles and squares using a single div by masking the element inset border with it's own pseudo elements, then shrinking their width and height to reveal the drawing path. This one is fairly straight forward to learn and similarly easy to apply. The only caveat is that it will only work on top of a monochrome background, otherwise the effect is lost.
<div id="circle"></div>
<div id="square"></div>
#circle {
  position: absolute;
  box-shadow: inset 0 0 0 3px #5151dd;
  border-radius: 50% / 25%;
  width: 40px;
  height: 80px;
  top: -40px;
  left: -70px;
  -webkit-transform: rotateZ(-90deg);
  transform: rotateZ(-90deg);
}
#circle::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 50%;
  height: 0%;
  background-color: #e8e8e8;
  -webkit-animation: circle-before 3s infinite linear;
  animation: circle-before 3s infinite linear;
}
#circle::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 50%;
  height: 0%;
  background-color: #e8e8e8;
  -webkit-animation: circle-after 3s infinite linear;
  animation: circle-after 3s infinite linear;
}
@keyframes circle-before {
  0% {
    height: 100%;
  }
  10% {
    height: 100%;
  }
  20% {
    height: 0%;
  }
}
@keyframes circle-after {
  0% {
    height: 100%;
  }
  20% {
    height: 100%;
  }
  30% {
    height: 0%;
  }
}

#square {
  position: absolute;
  box-shadow: inset 0 0 0 3px #ff0151;
  width: 60px;
  height: 60px;
  top: -30px;
  left: 30px;
}
#square::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 3px;
  height: 0;
  background-color: #e8e8e8;
  -webkit-animation: square-before 3s infinite linear;
  animation: square-before 3s infinite linear;
}
#square::after {
  content: "";
  position: absolute;
  top: 3px;
  right: 0;
  width: 3px;
  height: 0;
  background-color: #e8e8e8;
  -webkit-animation: square-after 3s infinite linear;
  animation: square-after 3s infinite linear;
}
@keyframes square-before {
  0% {
    height: 100%;
    width: 100%;
  }
  40% {
    height: 100%;
    width: 100%;
  }
  45% {
    height: 100%;
    width: 3px;
  }
  50% {
    height: 0;
    width: 3px;
  }
}
@keyframes square-after {
  0% {
    height: 57px;
    width: 57px;
  }
  50% {
    height: 57px;
    width: 57px;
  }
  55% {
    height: 57px;
    width: 3px;
  }
  60% {
    height: 0;
    width: 3px;
  }
}
5. Managing repetition using box-shadow (and similarly, text-shadow)
   box-shadow's are quite limited in how they can be styled, but prove quite useful when dealing with repetition. Since they always take the shape of the parent element, simple monochrome shapes can be cloned infinitely by extending and repositioning the shadow effect.

   You can extend this further by managing the shadow offsets to create cool fill effects. Keep in mind that the shadows reflect the parent element, so the parent must be made available in the DOM first before the shadows are visible. This likely means that the parent must be animated first, with the shadows initially hidden by an offset so they can show at a delayed frame of time.
<div id="cover-vertical"></div>
#cover-vertical {
  position: absolute;
  width: 20px;
  height: 50px;
  bottom: 0;
  background-color: #85e367;
  -webkit-animation: cover-vertical 3s infinite linear;
  animation: cover-vertical 3s infinite linear;
  box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 0 #85e367, 140px 0 #85e367, 160px 0 #85e367, 180px 0 #85e367;
}
@keyframes cover-vertical {
  0% {
    width: 0;
    height: 0;
    box-shadow: none;
  }
  20% {
    width: 20px;
    height: 0;
  }
  25.45454545% {
    height: 25px;
    box-shadow: 20px 50px #85e367, 40px 50px #85e367, 60px 50px #85e367, 80px 50px #85e367, 100px 50px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  30.90909091% {
    height: 50px;
    box-shadow: 20px 25px #85e367, 40px 50px #85e367, 60px 50px #85e367, 80px 50px #85e367, 100px 50px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  36.36363636% {
    box-shadow: 20px 0 #85e367, 40px 25px #85e367, 60px 50px #85e367, 80px 50px #85e367, 100px 50px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  41.81818182% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 25px #85e367, 80px 50px #85e367, 100px 50px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  47.27272727% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 25px #85e367, 100px 50px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  52.72727273% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 25px #85e367, 120px 50px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  58.18181818% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 25px #85e367, 140px 50px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  63.63636364% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 0 #85e367, 140px 25px #85e367, 160px 50px #85e367, 180px 50px #85e367;
  }
  69.09090909% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 0 #85e367, 140px 0 #85e367, 160px 25px #85e367, 180px 50px #85e367;
  }
  74.54545455% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 0 #85e367, 140px 0 #85e367, 160px 0 #85e367, 180px 25px #85e367;
  }
  80% {
    box-shadow: 20px 0 #85e367, 40px 0 #85e367, 60px 0 #85e367, 80px 0 #85e367, 100px 0 #85e367, 120px 0 #85e367, 140px 0 #85e367, 160px 0 #85e367, 180px 0 #85e367;
  }
}
   Drawing with CSS is a very tedious task of eyeballing positions and tweaking element properties, in order to achieve that pixel perfect curve. There's no shortcuts or automated tools (yet) to help generate CSS for intricate drawings loaded with animations.

   ... But not that there ever needs to be. From a Time Spent vs Outcome perspective, drawing with CSS is definitely unfavorable compared to traditional (and digital) pen and paper methods. Hours of work spent tweaking and tweening CSS can easily be replicated in swift minutes using photoshop or other similar means.

   That being said, learning and drawing with CSS allows you the opportunity to dive deep and explore the vast specification that defines most of what we see on the web. It gives you a chance to familiarize yourself with existing properties, invoke discoveries of undiscovered rules, and surprise yourself with bleeding edge features that you had no idea existed. This knowledge is invaluable for achieving desirable UX and meeting design expectations in a quick and performant manner, whether you're building a production scale web app or tinkering a personal web tool.

   All in all, do what you love, and never stop learning.
comments powered by Disqus