Core Interaction Lab: Week 8

CSS Animation

CSS Transitions

The transition property in CSS lets us define an amount of time to change properties from one value to another. In the past, we've written :hover pseudoselectors that change a value immediately, in this case, the color of the element:

Hover Me - No Transition

If we add a transition to this element on the color property, the browser will take some time to switch from one state to the other.

Hover Me - With Transition

Here's what the CSS looks like:

.hover-example {
    color: green;
    transition: color 1s;
}

.hover-example:hover {
    color: red;
}

In its most simple form, the transition property takes two values: the name of another property to transition (or all to apply the transition to every property) and the time to take. Time in CSS requires a unit - s for seconds or ms for milliseconds.

Any property that has a numerical value (including colors) can be transitioned. Hover over the box below, where there are four separate transitions. Before looking, can you identify them all?

.box {
    background-color: pink;
    height: 50px;
    width: 50px;
    transform: rotate(0deg);
    transition: all 500ms;
}

.box:hover {
    background-color: turquoise;
    height: 100px;
    transform: rotate(180deg);
    width: 100px;
}

Hovering isn't the only interaction we can use to invoke a transition. Open up the web inspector and change some of the properties - margin-left for example - notice how every change you make has the transition applied to it? The browser doesn't care how the style changes, it's just sitting around waiting for a change to happen so it can apply the transition. Remember also that we can use JavaScript to change CSS styles by using Element.classList.add() or by using the Element.style property.

Have you noticed the horse in the background of this page? As soon as you scroll 200px down the page, it transitions from the left to the right hand side of the page. The only JS I've written is to set the left CSS value to 90% of the page width when the user's scroll position is halfway down the page. The actual animation is handled by the transition property.

#horse {
    font-size: 100px;
    position: fixed;
    top: 200px;
    left: 0;
    transition: left 1s;
    transform: scaleX(-1); /* the horse emoji points to the right by default, so I need to flip it */
}
var horse = document.getElementById("horse")

window.addEventListener("scroll", onWindowScroll)

function onWindowScroll() {
    if (window.pageYOffset > 200) {
        horse.style.left = "90%"
    } else {
        horse.style.left = "0"
    }
}

Keyframes

What if we don't necessarily care about user interaction? This is where keyframe animations come in. With a keyframe animation, we can define as many states of CSS property/value pairs that we want and the browser will play through them over time.

😵

#dizzy {
    animation: 1s infinite spin;
    display: inline-block;
}

@keyframes spin {
    from {
        transform: rotateY(0);
    }

    to {
        transform: rotateY(360deg);
    }
}

Note that there are two components to a keyframe animation: the animation must be defined (with the @keyframes block) and then applied on to an element (with the animation property).

The @keyframes block has a few components of its own. It has a name, which in the above example is "spin." Then within the definition block, we have two more blocks named from and to. In both of those blocks are definitions for the state of the CSS property/value pairs at the beginning of the animation (from) and at the end (to).

When you apply an animation to an element, you define how long the animation should take to complete (1s in the above example) and how many iterations (infinite for me, since I want it to spin forever). Finally, I provide the name of the animation.

Advanced Keyframe Timing

Inside an animation block, we don't just have to use from and to. In fact, we can provide specific percentages for the state throughout the duration of the animation. For example:

🏀

@keyframes bounce {
    0% {
        top: 0;
    }

    40% {
        transform: scaleY(1);
    }

    50% {
        top: 80px;
        transform: scaleY(.6);
    }

    60% {
        transform: scaleY(1);
    }

    100% {
        top: 0;
    }
}

In the above example, you can see I've defined many blocks from 0% (the beginning of the animation) to 100% (the end). Within these blocks, I can start to animate different properties at different times. First, I am animating the basketball up and down with the top property. It spends 50% of the time going down, and then 50% of the time going back up. Second, for 10% on either side of my "down" point, I squish the ball a little to reinforce the feel of it hitting the "floor." At 40%, I set the scaleY of the ball to 1, which technically it already is, but by setting it at 40%, I define the beginning time of the animation. Then 10% later, at 50%, I set the scaleY to .6, which squishes the ball vertically. And then at 60%, we're back to an unsquished ball as it continues back up.

In-Class Example

Here's the code we wrote together in class.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Animations</title>
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div id="bike">
      <div class="frame"></div>
      <div class="wheel left"></div>
      <div class="wheel right"></div>
    </div>

    <script src="skate.js"></script>
  </body>
</html>
#bike {
  position: absolute;
  width: 300px;
}

.wheel {
  width: 100px;
  height: 100px;
  border-radius: 50px;
  background-color: black;
  position: absolute;
  top: 40px;
  animation: spin 1s infinite linear;
}

.right {
  right: 0;
  width: 110px;
}

.left {
  height: 110px;
}

.frame {
  width: 300px;
  height: 20px;
  background-color: red;
  position: absolute;
}

@keyframes spin {
  0% {
    transform: rotate(0);
  }

  100% {
    transform: rotate(360deg);
  }
}
// when a click happens
window.addEventListener("click", onWindowClick)

function onWindowClick(evt) {
  // figure out where to put the board
  console.log(evt.x, evt.y)

  // set the top and left CSS of the board
  var myBike = document.getElementById("bike")

  myBike.style.top = evt.y + "px"
  myBike.style.left = evt.x + "px"
}

References

🐎