10 thingsI wish peopleknew aboutWeb animation

A few things about me…

  • 2004~
  • SVG WG 2011~
    CSS WG 2015~

Introducing Meat “Meet Pie”

Meet Pie
  • The first social network for meat pie lovers!
  • PWA (Pie Web App)

Agenda

  • CSS Transitions
  • CSS Animations
  • Web Animations API
  • Performance (and DevTools)
  • Accessibility

CSS Transitions


.pie {
  
}
.pie:hover {
  transform: scale(2);
}
  

.button {
  background: hsl(40, 15%, 80%);
  transition: background-color .15s,
              transform .15s;
}
.button:hover {
  background: hsl(43, 13%, 90%);
  transform: scale(1.05);
}
  

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  panel.classList.add('panel');
  panel.textContent = 'Hello!';
  parent.appendChild(panel);

  // Stretch it in
  panel.style.transform = 'scale(0)';
  panel.style.transition = 'transform .5s';
  panel.style.transform = 'scale(1)';
};
  
Frame 1 Frame 2 Style Style display: block; opacity: 0; transform: scale(1.5); display: block; opacity: 1; transform: scale(1.5); Layout Layout 120px 120px Painting Painting
Frame 1 Frame 2 Style Style display: block; opacity: 0; transform: scale(1.5); display: block; opacity: 1; transform: scale(1.5); Layout Layout Painting Painting
Frame 1 Frame 2 Style Layout Painting Script (onclick) Style

  button.onclick = () => {
    const panel = document.createElement('div');
    ...
    panel.style.transform = 'scale(0)';
    panel.style.transition = 'transform .5s';
    panel.style.transform = 'scale(1)';
  };
  

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  ...

  // Stretch it in
  panel.style.transform = 'scale(0)';
  panel.style.transition = 'transform .5s';
  requestAnimationFrame(() => {
    panel.style.transform = 'scale(1)';
  });
};
  
Frame 1 Frame 2 Style Layout Painting Script (onclick) request-AnimationFrame Style

  button.onclick = () => {
    const panel = document.createElement('div');
    ...
    panel.style.transform = 'scale(0)';
    requestAnimationFrame(() => {
      panel.style.transform = 'scale(1)';
    });
  };
  

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  ...

  // Stretch it in
  panel.style.transform = 'scale(0)';
  panel.style.transition = 'transform .5s';
  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      panel.style.transform = 'scale(1)';
    });
  });
};
  
Frame 1 Frame 2 Frame 3 Style Layout Painting Script (onclick) request-AnimationFrame Style Resolvetransform as ‘scale(0)’ request-AnimationFrame Style Resolvetransform as ‘scale(1)’

  button.onclick = () => {
    ...
    panel.style.transform = 'scale(0)';
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        panel.style.transform = 'scale(1)';
      });
    });
  };
  

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  ...

  // Stretch it in
  panel.style.transform = 'scale(0)';
  getComputedStyle(panel).transform;
  panel.style.transition = 'transform .5s';
  panel.style.transform = 'scale(1)';
};
  
Frame 1 Frame 2 Style Layout Painting Script (onclick) Style Script (onclick) contd. Resolvetransform as ‘scale(0)’ Resolvetransform as ‘scale(1)’ Style

  button.onclick = () => {
    const panel = document.createElement('div');
    ...
    panel.style.transform = 'scale(0)';
    getComputedStyle(panel).transform;
    panel.style.transition = 'transform .5s';
    panel.style.transform = 'scale(1)';
  };
  

Point 1To create a transition,the browser needs to seea change in style

Tips

  • Be careful with:
    • createElement
    • “rendering” using a framework (e.g. React etc.)
    • display: none
  • getComputedStyle(elem) does NOT update style
    • getComputedStyle(elem).<property> does
  • Using getComputedStyle can be expensive
  • Consider CSS Animations or Web Animations instead

button.onclick = () => {
  // If the panel already exists, remove it.
  panel.remove();
};
  

button.onclick = () => {
  // If the panel already exists, remove it.
  panel.style.transform = 'scale(0)';
  panel.addEventListener('transitionend', () => {
    panel.remove();
  });
};
  

But what if…

  • The element becomes display:none?
  • The element is re-created? Re-“rendered”?
  • The transition-property property changes?
  • The element is removed?

The transition will be canceled!
transitionend will never fire!


button.onclick = () => {
  // If the panel already exists, remove it.
  panel.style.transform = 'scale(0)';
  panel.addEventListener(
    'transitionend',
    () => { panel.remove(); }
  );
};
  

Handling canceledtransitions

  • setTimeout
  • transitioncancel event
    • 53+
    • 74+ (April)
    • Tech Preview
    • x
  • Also transitionrun event
    • Make sure there is a transition in the first place!

button.onclick = () => {
  // If the panel already exists, remove it.
  const dropPanel = () => { panel.remove(); };

  panel.style.transform = 'scale(0)';
  panel.addEventListener(
    'transitionend',
    dropPanel
  );
  panel.addEventListener(
    'transitioncancel',
    dropPanel
  );
};
  

Point 2Transitions can becanceled

CSS Animations


.pie {
  
}
@keyframes spin {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
  

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  ...

  // Stretch it in
  panel.style.transform = 'scale(0)';
  getComputedStyle(panel).transform;
  panel.style.transition = 'transform .5s';
  panel.style.transform = 'scale(1)';
};
  

@keyframes stretch-in {
  from { transform: scale(0); }
  to { transform: scale(1); }
}
    

button.onclick = () => {
  // Make the panel
  const panel = document.createElement('div');
  ...

  // Stretch it in
  panel.style.animation = 'stretch-in .5s';
};
    

.pie {
  animation: fancy 2s infinite;
}
@keyframes fancy {
  0%   { transform: none; }
  50%  { transform: scale(1.5) rotate(0deg); }
  90%  { transform: scale(1.5) rotate(360deg); }
  100% { transform: scale(1.5) rotate(360deg); }
}
  

.pie {
  animation: fade-out 3s forwards infinite
             cubic-bezier(0.7, 0.0, 1.0, 1.0);
}
@keyframes fade-out {
  0%   { transform: scale(1.0); opacity: 1.0; }
  50%  { transform: scale(1.5); opacity: 0.5; }
  100% { transform: scale(2);   opacity: 0.0; }
}
  

.pie {
  animation: fade-out 3s forwards infinite;
  animation-timing-function: ease;
}
@keyframes fade-out {
  0%   { transform: scale(1.0); opacity: 1.0; }
  50%  { transform: scale(1.5); opacity: 0.5; }
  100% { transform: scale(2);   opacity: 0.0; }
}
  

.pie {
  animation: fade-out 3s forwards infinite;
}
@keyframes fade-out {
  0%   { transform: scale(1.0); opacity: 1.0;
         animation-timing-function: ease; }
  50%  { transform: scale(1.5); opacity: 0.5;
         animation-timing-function: ease; }
  100% { transform: scale(2);   opacity: 0.0;
         animation-timing-function: ease; }
}
  

.pie {
  animation: fade-out 3s forwards infinite
             cubic-bezier(0.7, 0.0, 1.0, 1.0);
}
@keyframes fade-out {
  0%   { transform: scale(1.0); opacity: 1.0; }
  50%  { transform: scale(1.5); opacity: 0.5; }
  100% { transform: scale(2);   opacity: 0.0; }
}
  

Point 3animation-timing-functionalways appliesbetween keyframes

Web Animations


pie.animate(
  {
    transform: ['scale(1)', 'scale(2)'],
    opacity: [1, 0],
  },
  {
    duration: 3000,
    iterations: Infinity,
    easing: 'cubic-bezier(0.7, 0.0, 1.0, 1.0)',
  }
);
  

Why?

  • vs other JS
    • Performance
    • No library
    • Combines with CSS
  • vs CSS
    • Procedural
    • (In future) CSS to create, JS to control
https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/

Browser support

  • 48+
  • 36+
  • Tech Preview
  • x

Who?

Source: Chrome use counter for Element.animate()

5th most popular API in 2018 State of JS survey

Feature detection


// Check for browser support.
//
// TODO: Check for _user_ support too (see point 9)
if ('animate' in elem) {
  // ... Animate away...
}

You might not need the polyfill (web-animations-js).

Point 4You can useWeb Animations today

CSS transitions/animations Web Animations
animation-iteration-count iterations
transition-timing-function, animation-timing-function easing
Default easing is ease Default easing is linear

pieOne.animate(
  {
    transform: ['scale(1)', 'scale(1.5)', 'scale(2)'],
    opacity: [1, 0.5, 0],
    easing: 'ease',
  },
  {
    duration: 2000,
    iterations: Infinity,
  }
);

pieTwo.animate(
  {
    transform: ['scale(1)', 'scale(1.5)', 'scale(2)'],
    opacity: [1, 0.5, 0],
  },
  {
    duration: 2000,
    iterations: Infinity,
    easing: 'ease',
  }
);
  

Point 5Web Animations hassome differences to CSS,especially easing

Performance

transform and opacity

move
grow
spin
3D
fade
grow + spin + fade
colour change
shadow effect
split

Point 6Try to animate justtransform and opacity

Point 7You can use theanimation inspector tocheck animation performance

Single page apps


react.render(
  <Router>
    <Route exact path="/" component={Photos} />
    <Route path="/polls" component={Polls} />
    <Route path="/finder" component={Finder} />
  </Router>
);

Single page apps


<Router>
  <Route render={({ location }) => (
    <TransitionGroup>
      <CSSTransition key={location.pathname}
        timeout={400} classNames="slide">
        <Switch location={location}>
          <Route exact path="/" component={Photos} />
          <Route path="/polls" component={Polls} />
          <Route path="/finder" component={Finder} />
        </Switch>
      </CSSTransition>
    </TransitionGroup>
  )}/>
</Router>

When you click…

  1. Handle click
  2. Create DOM elements
  3. Resolve style
    • (<CSSTransition> resolves style and layout multiple times)
  4. Calculate layout
  5. Paint each new element
  6. Run the transition

What about…


<Router>
  <Route render={({ location }) => (
    <Photos active={location.pathname === '/'}/>
    <Polls active={location.pathname === '/stories'}/>
    <Finder active={location.pathname === '/notes'}/>
  )}/>
</Router>

When you click…

  1. Handle click
  2. Create DOM elements
  3. Resolve style
    • (<CSSTransition> resolves style and layout multiple times)
  4. Calculate layout
  5. Paint each new element
  6. Run the transition
Before (~100ms)
After (~30ms)

Point 8Try to have somethingready to animateright away

Accessibility

Sick of Animation

Headaches, dizziness, migraines, epilepsy…
Windows
Mac
prefers-reduced-motion: reduce | no-preference

div {
  animation: ...
  transition: ...
}

@media (prefers-reduced-motion: reduce) {
  animation-name: none;
  transition-property: none;
}
  • 64+
  • 74+ (April)
  • 10.1
  • x

// Check for browser support
if (!('animate' in elem)) {
  return;
}

// Check for user support
if (matchMedia('(prefers-reduced-motion)').matches) {
  return;
}

// Animate away...

Point 9You can (and probablyshould) check if the userprefers to reduce animation

10 things

Transitions

  1. Transitions only run when there is a change in style
  2. Transitions can be canceled

CSS Animations

  1. For CSS animations, easing always applies between keyframes

Web Animations

  1. You can use Web Animations today
  2. … but there are some minor differences to CSS animations

Performance

  1. Try to animate transform and opacity
  2. You can check animation performance in DevTools
  3. Try to have something ready to animate before you need it

Accessibility

  1. Disable large animations based on the user's preference

Don't forget…

  1. Have fun!

Thank you!