6. Interactions and Transitions – Architecting CSS: The Programmer’s Guide to Effective Style Sheets

© Martine Dowden and Michael Dowden 2020
M. Dowden, M. DowdenArchitecting CSShttps://doi.org/10.1007/978-1-4842-5750-0_6

6. Interactions and Transitions

Martine Dowden1  and Michael Dowden1
(1)
Brownsburg, IN, USA
 

When we think of HTML and CSS, we often think “static.” JavaScript much more commonly comes to mind when thinking of interactions or animations. CSS, however, includes several features that allow for manipulation of elements as a result of user interaction. In this chapter, we will look at how we can respond to user interaction using CSS and how to support those interactions using animations and transitions.

User Interaction Response

One of the most commonly used ways of responding to user interaction in CSS is by using the pseudo elements :hover, :focus, and :active.

The pseudo element :hover matches when an element is interacted with using a pointing device most commonly, when the user hovers with the mouse over the element1 such as a link or button. This can be used to give the user a visual indication that the element can be interacted with.

The :focus pseudo class is triggered when an element receives focus, such as when tabbed to using the keyboard or being clicked. Often overlooked, focus is important as it gives a visual indicator to the user as to which element they are currently interacting with or about to interact with. Changing border styles on an input field when it is in focus will tell the user which field they are about to type in, which is incredibly helpful in orienting the user as to where in the page they are currently at. Not all elements can natively receive focus. Outside of some exceptions such as the video element, buttons, anchor tags, and form items like input and select are the only elements that can receive focus without adding a tabindex attribute to the element.

The :active pseudo element triggers when an element is being activated such as a button being pressed or a link being clicked. A change in button style, such as removing a shadow when a physical button is being pressed, reflects real-world expectations of the action of depressing a physical button. Although a user might not be able to articulate why, small interactions such as this will make the interaction feel more natural to the user.

Accessibility and Focus

Most browsers will have default behavior around elements when focus is applied. If squashing the default behavior, some visual indication of focus needs to be reapplied so that a user can visually distinguish the element that is in focus from other elements.2 Furthermore, focus should not change the context, functionality, meaning, or operability.3,4

From the interaction, a response can then be set such as changing the element’s looks, size, or even position. Adding a transition to a visual change, if the animation is informative of the change about to take place, will help the user understand the change being applied. When expanding an accordion for example, animating the opening of the accordion section will help the user stay oriented to where they are in the page especially since content below will be moved to a different location, possibly outside the viewport.

When responding to a CSS-triggered event such as hover, focus, or active, it is much more maintainable to keep the associated transition in CSS as well rather than to use JavaScript. This allows both the trigger and the reaction to stay together and for their association to remain clear and evident. This helps keep visual instructions within the style sheets.

Transform

When creating transitions and animations, although not a requirement, the CSS transform property is often used. Transform allows elements styled with CSS to be transformed in two-dimensional space. Transform functions are based on the transformation matrix. The matrix() function is a shorthand for matrix3d() which takes six parameters a, b, c, d, tx, and ty, which are shown in bold in Figure 6-1.
Figure 6-1

Transform Matrix

Parameters a, b, c, and d describe the linear transformation and tx and ty describe the translation to be applied. CSS provides transform functions based on the preceding matrix to manipulate elements such as translate, scale, rotate, skew, and perspective. Using the translate() function to change the position of an item, such as sliding something into view, is generally going to be more performant than manipulating its position. The same can be said about scale() over changing an element’s height or width such as expanding or collapsing a menu or accordion. The rotate() function is often used in microanimations; continuing with the accordion example, it can be used to rotate an arrow or caret in the accordion’s header to distinguish if the associated panel is open or closed. When the panel is being opened, the arrow can rotate at the same time to inform the user as to the state of the panel in question. Although seemingly insignificant, small details such as this one, if informative, can help the user orient and understand what they are looking at and what is happening. Details regarding the transform functions can be found in Table 6-1.
Table 6-1

Transform Functions

Function

Description

Dimension

matrix()

Shorthand for matrix3d(). See the earlier description. Takes six parameters.

2D

matrix3d()

Linear transformation and translation over three dimensions. See the earlier matrix description. Takes 16 values.

3D

translate(tx, ty)

Translation by the vector, where x is the first translation value and y is the second. To individually manipulate the x- or y-axis, translateX(tx) and translateY(ty) can be used.

2D

translate3d(tx, ty, tz)

Same as translate() but on three dimensions. TranslateZ(tz) can be used to translate the element on the z index. This tz value cannot be a percentage, it must be a length.

3D

scale(sx, sy)

Scaling vector, where x scales the height and y scales the width and initial value is 1. To scale the height or width independently, scaleX(sx) and scaleY(sy) can be used.

2D

scale3d()

Same as scale() but on three dimensions. ScaleZ(tz) can be used to translate the element on the z index.

3D

rotate()

Rotates the element from the point of transform-origin by the angle provided.

2D

rotate3d(x, y, z, a)

Rotates an element around a fixed axis in three-dimensional space, where x, y, and z describe the axis of rotation and a describes the angle of rotation.

3D

skew(x,y)

Distorts an element by the provided angle on the x- and y-axes. To skew the element by axis, skewX(∠x) and skewY(∠y) can be used.

2D

perspective(z)

Gives perspective to three-dimensional elements where 0 is the default. When z is increased, the element becomes larger, and when it is decreased, the element shrinks.

3D

Transitions

When the styles for an element are changed, transitions allow for the shift from initial state to the new state to be visually smooth. As its name implies, the transition property controls the visual aspect of how values change from one state to another over time.

The transition property is the shorthand property for the following: property, duration, timing function, and delay. Its syntax is described in Listing 6-1 and its properties are defined in Table 6-2.5
transition: property duration timing-function delay;
Listing 6-1

Transition Property Shorthand Syntax

Table 6-2

Transition Property Values

Value Name

Behavior

Initial Value

transition

-property

Defines the property the transition will affect

all

transition

-duration

Defines how long the transition will take to complete

0s

transition

-timing

-function

Defines the acceleration curb for how the values get applied during the transition

ease

transition

-delay

Defines the delay period before the transition starts

0s

Listings 6-2 and 6-3 show an on-hover transition.
<body>
  <a href="">
    <span>Transitions</span>
  </a>
</body>
Listing 6-2

HTML for Transition Example

html, body {
  padding: 36px;
  margin: 0;
}
a {
  align-items: center;
  background: gray;
  border: solid 1px white;
  color: white;
  display: flex;
  font-size: 36px;
  height: 100px;
  justify-content: center;
  text-decoration: none;
  transition: all 250ms ease-in-out;
}
a:hover {
  background: white;
  border-color: gray;
  color: gray;
  border-radius: 45px;
}
Listing 6-3

CSS for Transition Example

In the preceding listing , the link is hovered over causing the background-color, border-color, font color, and border radius to gradually change over 250 milliseconds (see Figure 6-2).
Figure 6-2

Animation Code Output Over Time

User Experience

Transitions can be a great way to help guide the user through an application by enhancing the relationship between elements when an action is performed. To achieve this goal, however, the animation should be informative, focused, and expressive.6 Animations should last between 200 and 500 milliseconds with smaller, less complex animation, or when on a smaller screen, in the 200–300 millisecond range.7

Keyframe Animations

Unlike transitions, which can only happen once when the user triggers the event, animations can be repeated over an indefinite period of time. They can also be applied when an element is added to the DOM such as an element going from a display:none to display:block. This might be the case when opening a menu. The menu items were hidden from the user, and they need to be slid into view rather than abruptly displayed. By animating the display of the menu element, the user implicitly understands the origin of the menu item. Animation also provides more control over the steps of the animation, allowing for much more complexity than in a transition. By percentage along the animation, the keyframe rules set when what changes need to occur. Listings 6-4 and 6-5 show an example using keyframes.
<body>
  <div class="animations">Animations</div>
</body>
Listing 6-4

Keyframes HTML

html {
  padding: 0;
  margin: 0;
}
body {
  box-sizing: border-box;
  padding: 36px;
  margin: 0;
}
body > div {
  box-sizing: border-box;
  margin-bottom: 3rem;
}
@keyframes myAnimation {
  0% {
    background: gray;
    border-color: white;
    color: white;
    border-radius: 0px;
    transform: scale(0);
  }
  25% {
    transform: rotate(5deg) scale(.25);
  }
  50% {
    transform: rotate(-10deg) scale(.5);
  }
  75% {
    transform: rotate(35deg) scale(.75);
  }
  100% {
    background: white;
    border-color: gray;
    color: gray;
    border-radius: 45px;
    transform: rotate(0) scale(1);
  }
}
.animations {
  animation: myAnimation 500ms ease-in-out 1;
  background: white;
  border: solid 1px gray;
  border-radius: 45px;
  box-sizing: border-box;
  color: gray;
  font-size: 2rem;
  padding: 2rem;
  text-align: center;
  width: 100%;
}
Listing 6-5

Keyframes CSS

Figure 6-3

Animation Code Output Over Time

Background-color , border-color, color, border radius, and scale are only defined at 0 and 100% and are therefore interpolated. The element will rotate to the specified degree at each percent. Even though 100% does not specify a rotation degree, at the end of the animation, the element will set its rotation to what ever is set on the element, or 0.

To trigger the keyframe, the animation property is used (see Listing 6-6). The animation property can take up to seven values: name, duration, timing-function, delay, iteration-count, direction, and fill-mode (details in Table 6-3).
animation: name duration timing-function delay iteration-count direction
           fill-mode;
Listing 6-6

Animation Property

Table 6-3

Animation Property Values

Value Name

Behavior

Initial Value

animation-name

Defines the keyframe at-rule the animation will use

none

animation-duration

Defines how long the animation will take to complete

0s

animation-timing

-function

Defines the acceleration curb for how the values get applied during the animation

ease

animation-delay

Defines the delay period before the animation starts

0s

animation-iteration

-count

Defines the number of times the animation will play

1

animation-direction

Defines whether the animation should play forward, backward, or toggle forward and backward

normal

animation-fill-mode

Defines how styles are applied to the target before and after animation completes

none

Another property that can be used with animation is animation-play-state which allows the developer to pause and start an animation. When resumed, the animation will restart where it was paused rather than the beginning of the sequence. The default value for animation-play-state is running. It needs to be defined individually as its own property, however, and is not part of the animation shorthand described in Listing 6-4. Giving the user the ability to pause an animation, especially if the animation is not necessary to understanding the content or the state of the application, can radically improve the usability of the application. When considering an auto-advancing carousel, for example, adding the ability to pause the auto-incrementation of panels will allow the user to control the speed at which they view the content.

Animations can also be used when an object is being removed from the DOM, such as when adding a display value of none, but because the display:none property will be applied and completed before the animation finishes, this cannot be done with CSS alone. If when closing a menu, display:none is added to the menu items, regardless of any animations or transitions set on the elements, the menu will abruptly disappear as the animation will not be given the time to run before the menu items are hidden. To counteract this, the JavaScript animationend event is used in conjunction with the CSS to listen to the animation state. animationend will trigger upon completion of the animation, at which point display:none can be added to the elements which need to be hidden (see Listings 6-7 and 6-8).
<body>
  <div class="show-hide">
    <button onclick="toggleAnimation()" id="button">
      Show
    </button>
    <div
      class="animation-container"
      id="animationContainer">
    </div>
  </div>
  <script>
    function showContainer() {
      animationContainer.classList.add('show');
    }
    function hideContainer() {
      animationContainer.addEventListener('animationend', cleanup);
      animationContainer.classList.replace('show', 'close');
    }
    function cleanup() {
      animationContainer.classList.remove('close');
      animationContainer.removeEventListener('animationend', cleanup);
    }
  </script>
</body>
Listing 6-7

Animation End Event HTML and JavaScript

@keyframes roll {
  0%   { transform: translateX(-75vw) rotate(-360deg);  }
  100% { transform: translate(0) rotate(0)}
}
@keyframes roll-reverse {
  0% { transform: translate(0) rotate(0)}
  100%   { transform: translateX(-75vw) rotate(-360deg);  }
}
.animation-container {
  background: linear-gradient(lightgrey, grey);
  border-radius: 50%;
  display: none;
  height: 100px;
  margin: 1rem auto;
  width: 100px;
}
.show {
  display: block;
  animation: roll 1s cubic-bezier(0.280, 0.840, 0.420, 1);
}
.close {
  display: block;
  animation: roll-reverse 1s cubic-bezier(0.280, 0.840, 0.420, 1);
}
Listing 6-8

Animation End Event CSS

When the element is “closed” or hidden, first a class with the exit animation is added. Once the animation ends, the animationend event listener is triggered and only then can the display property value be changed to none. The same can be achieved with transitions using the transitionend event listener. Adding and removing classes, rather than handling the close animation in JavaScript, helps keep display-related logic in the CSS style sheet, increasing maintainability and keeping separation of concerns.

Timing Functions

Whether creating a transition or an animation, a common value to define is the timing function. It determines the speed at which values change over the time it takes for the animation to complete. Timing can help make the animation feel more natural and reflect physical world interactions more closely. When animating a bouncing ball, one would expect the ball to accelerate after hitting the ground. If the animation was linear, and the ball always moved at the same speed, the animation would seem off. There are two specific types of timing functions available.

Easing Functions

Easing functions define smooth transitions based on the Bézier curve, named after the French engineer Pierre Bézier. The curve is parametric,8 and the cubic variant is defined by four points: P0, P1, P2, and P3. P0 and P3 define the beginning and end of the curve, respectively. P1 and P2 represent the control points which give the curve its shape. Each point is defined by (x, y) coordinates.

The CSS cubic-bezier predefines P0 and P3 at fixed points of (0, 0) and (1, 1) representing the initial and final states of the animation. Left to be defined are P1 and P2 whose x values need to remain in a [0, 1] range, while the y values may exist outside of the bounding box.

The CSS function looks as follows: cubic-bezier(x1, y1, x2, y2).

Although the timing can be customized , for convenience CSS includes named common timing functions which include linear, ease, ease-in, ease-in-out, and ease-out (see Table 6-4).
Table 6-4

Named Easing Functions9

Name

Formula

Curve

linear

cubic-bezier(0.0, 0.0, 1.0, 1.0)

ease

cubic-bezier(0.25, 0.1, 0.25, 1.0)

ease-in

cubic-bezier(0.42, 0.0, 1.0, 1.0)

ease-in-out

cubic-bezier(0.42, 0.0, 0.58, 1.0)

ease-out

cubic-bezier(0.42, 0.0, 0.58, 1.0)

To create bouncing effects, either or both y values should be set outside the [0, 1] range . For this, a custom function needs to be written such as in the following function: cubic-bezier(0, 0.71, 0.64, 1.23).

The curve is plotted in Figure 6-4.
Figure 6-4

Sample Bounce Curve

Stepping Functions

Although not currently well supported across browsers, instead of a curve, a stepping function which divides the animation into equal segments across time can also be used. Two values are used to define the animation’s timing: number of steps (n) and step position (see Table 6-5). The syntax is as follows:
 animation-timing-function: steps(n, step-position);
Table 6-5

Named Stepping Functions10

Name

Function

Steps

step-start

steps(1, start)

step-end

steps(1, end)

jump-start

steps(3, jump-start)

jump-end

steps(3, jump-end)

jump-none

step(3, jump-none)

jump-both

step(3, jump-both)

When applied, the code and output would be as in Listings 6-9 and 6-10 and Figure 6-5.

<body>
  <div class="jump-start">jump-start</div>
</body>
Listing 6-9

jump-start Sample Code CSS

body {
  box-sizing: border-box;
  padding: 36px;
  margin: 0;
}
body > div {
  box-sizing: border-box;
  margin-bottom: 3rem;
}
@keyframes jumpStart {
 0% {
    width: 0;
    background-color: white;
    border: 1px solid gray;
 }
 100% {
    width: 90vw;
    background-color: gray;
    border: 1px solid gray;
 }
}
.jump-start {
  animation-name: jumpStart;
  animation-duration: 5s;
  animation-iteration-count: infinite;
  margin-bottom: 4px;
  animation-timing-function: steps(5, jump-start);
}
Listing 6-10

jump-start Sample Code CSS

Figure 6-5

Jump-Start Output

Notice how the animation is already partially started. Because jump-start is used, the initial state of width 0 and color white is skipped and the animation starts with the container at a width of 20% of final state width. If jump-end had been used, the container would have started at a width of 0, but never reached a width of 100%. The container would only have a width of 80% when the animation ended.

Accessibility and Timing

When considering timing, it is important to make sure that the content does not flash more than three times in a one-second period. This is to prevent the induction of seizures due to photosensitivity in users.11

Performance Considerations

When considering the effects of animations on performance, not all animations are created equal. Animations that cause layout changes or the view to be repainted are particularly taxing.12 For example, changes to height, width, or position affect layout and cause elements on the page to be repositioned. Properties that cause the view to repaint include color, background-position, and visibility. Animations affecting layout and paint will be less performant than those that don’t.

Generally, for best performance, using the transform property is the best way to go as it can lean on the GPU. Whenever possible, it is best to try and stick to animation using opacity, translate, rotate, and scale.13

When performance issues do arise, it can be tempting to use the will-change property. Will-change informs the browser ahead of time of the changes that will be animated, allowing the browser to optimize for them; however, when misused, it can do more harm than good. Some guidelines to the proper use of will-change include the following:
  • Sparse use – It should only be used when it is actually needed. The browser already attempts to optimize everything. Unnecessary use will actually slow down the page.

  • Only on when needed – Should be turned on before the animation will trigger and then turned off again to free up browser resources being used for optimization.

  • Enough time – Optimization is time-consuming; therefore, will-change needs to be applied to the element with enough time to take effect before the animation is set to begin.14

Summary

This chapter covered transition, animations, and their differences as well as the functions used to change the timing of how animations and transforms are applied. Also covered were performance and accessibility considerations when dealing with animations. Chapter 7 will go over preprocessors and their architecture considerations and benefits.