10 things I hate about animation
(and how to fix them)

Mozilla Roadshow 2019
Brian Birtles, Birchill, Inc.

자기 소개

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

애니메이션은.…

  • 복잡합니다
  • 버그가 많습니다
  • 느립니다
  • 힘듭니다

① 프레임 기반 애니메이션은 어렵습니다


1
2
3
4


5
6
7


.filmstrip {
  animation: slide 1s infinite steps(7);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

.filmstrip {
  /* steps(7) = 마지막을 포함하여 7번의 '변경' */
  animation: slide 1s infinite steps(7);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

.filmstrip {
  animation: slide 1s infinite steps(6);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

.filmstrip {
  /* steps(6) = steps(6, end) */
  animation: slide 1s infinite steps(6);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

.filmstrip {
  animation: slide 1s infinite steps(6, start);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

.filmstrip {
  /* 신규! jump-none! */
  animation: slide 1s infinite steps(7, jump-none);
}
@keyframes slide {
  to { transform: translate(-700px); }
}

CSS Easing Functions

jump-none, jump-both

  • 65+
  • 77+
  • x
  • x

② Transitions에는 버그가 많습니다

1. 종종 작동하지 않는 transitions 2. 종종 완료되지 않는 transitions

새 이벤트!

  • transitionrun

    → transition이 생성됩니다 🆕

    transitionend를 기다려도 괜찮습니다 🙆‍♂️

  • transitioncancel

    → 요소가 사라집니다 🗑️ (제거되거나, 재생성되거나, display:none 되는 등)

    transitionend를 기다리지 마세요 ❌

  • transitionstart (c.f. animationstart)
  • animationcancel (c.f. transitioncancel)

transitioncancel과 친구들

  • 53+
  • 74+ No animationcancel
  • Tech Preview
  • x Only transitionstart
  • No animationcancel

#3: I ❤️ JS

This slide requires support for Element.animate.

CSS animations from JS


const uniqueKeyframesName = generateUuid();
document.styleSheets[0].insertRule(
  `@keyframes ${uniqueKeyframesName} {
    from { transform: scale(0) }
    95% { transform: scale(${fullSize}) }
    to { transform: scale(0) }
  }`
);
star.style.animation =
  `${uniqueKeyframesName} ${duration}ms ` +
  `${delay}ms infinite`;

// 정리를 잊지 마세요!

CSS transitions from JS

  • transitionrun, transitioncancel
  • JS로 transitions를 트리거하기는 어렵습니다
https://birtles.github.io/cssconf2019/

Element.animate() 사용


star.animate(
  [
    { transform: 'scale(0)' },
    { transform: `scale(${fullSize})`, offset: 0.95 },
    { transform: 'scale(0)' },
  ],
  { easing, duration, delay, iterations: Infinity }
);

Element.animate() 사용


const starAnimation = star.animate(
  [
    { transform: 'scale(0)' },
    { transform: `scale(${fullSize})`, offset: 0.95 },
    { transform: 'scale(0)' },
  ],
  { easing, duration, delay, iterations: Infinity }
);

// starAnimation.reverse();
// starAnimation.cancel();
// starAnimation.updatePlaybackRate(0.5);

starAnimation.finished.then(() => { ... });

Element.animate()

  • 48+
  • 36+
  • Tech Preview
  • x

④ 전체 애니메이션을 이징(Easing)하는 것은 어렵습니다


.body, .head {
  animation-timing-function: steps(4);
}

.head {
  animation-timing-function: steps(4);
}
@keyframes drop-head {
  60% { transform: rotate(45deg) }
}
  

@keyframes drop-head {
  0% {
    transform: none;
    animation-timing-function: steps(4);
  }
  50% {
    transform: rotate(45deg);
    animation-timing-function: steps(4);
  }
  100% { transform: none; }
}
  

head.animate(
  {
    // Chrome/Firefox는 아직 암시적 0%/100%
    // 키프레임을 지원하지 않습니다
    transform: ['none', 'rotate(60deg)', 'none'],
    offset: [0, 0.6, 1],
  },
  {
    duration: 2000,
    iterations: Infinite,
    easing: 'steps(4)'
  }
);
This slide requires support for Element.animate.

head.animate(
  {
    transform: ['none', 'rotate(60deg)', 'none'],
    offset: [0, 0.6, 1],
    easing: 'ease-in',
  },
  {
    duration: 2000,
    iterations: Infinite,
    easing: 'steps(4)'
  }
);
This slide requires support for Element.animate.

⑤ 애니메이션을 시간이 아닌 속도로 정의할 수 없습니다

CSS 워킹 그룹

어서 속도 기반 애니메이션을 추가하세요!

Element.getAnimations()


document.addEventListener('transitionrun', evt => {
  if (evt.propertyName !== 'transform') {
    return;
  }

  const transition = evt.target
    .getAnimations()
    .find(animation => animation.transitionProperty === 'transform');

  const keyframes = transition.effect.getKeyframes();
  const distance = calculateDistance(
    keyframes[0].transform,
    keyframes[1].transform
  );

  // 초당 700px로 이동
  const duration = (distance / 700) * 1000;
  transition.effect.updateTiming({ duration });
  // 참고: transition.updatePlaybackRate()가 더 좋습니다
});
This slide requires support for Element.getAnimations.

Element.getAnimations()

  • Nightly
  • Canary
  • Tech Preview
  • x
  • Canary

⑥ 중간점을 가진 transitions를 정의할 수 없습니다

rgb(255, 0, 0)
rgb(?, ?, ?) rgb(128, 64, 0)
rgb(0, 128, 0)
😧
Color #1:
Color #2:
rgb(255, 0, 0)
rgb(255, 255, 0)
rgb(0, 128, 0)
🤔
hsl(0, 100%, 50%)
hsl(60, 100%, 37.5%)
hsl(120, 100%, 25%)

CSS 워킹 그룹

어서 보간 모드를 더 추가하세요!

CSSTransition.setKeyframes()


document.addEventListener('transitionrun', evt => {
  if (evt.propertyName !== 'fill') {
    return;
  }

  const transition = evt.target
    .getAnimations()
    .find(animation => animation.transitionProperty === 'fill');

  const keyframes = transition.effect.getKeyframes();
  const hslKeyframes = generateHslKeyframes(
    keyframes[0].fill,
    keyframes[1].fill
  );

  transition.effect.setKeyframes(hslKeyframes);
});
Color #1:
Color #2:
This slide requires support for Element.getAnimations.

CSSTransition.setKeyframes() (Element.getAnimations)

  • Nightly
  • Canary
  • Tech Preview
  • x
  • Canary

⑦ 날고 싶어요

This slide requires support for offset-path.

offset-path


.goose {
  offset-path: path('M100 100...');
  animation: fly 10s linear infinite;
}
@keyframes fly {
  to { offset-distance: 100%; }
}

offset-path

  • 72+
  • 55+ (⚠️ offset-anchor은 구현 진행 중)
  • x
  • x
  • (⚠️ offset-anchor은 구현 진행 중)

⑧ 성능이 떨어집니다

transformopacity

이동
확대
회전
3D
흐리기
조합
색상 변화
그림자 효과
날기
This slide requires support for offset-path.

Firefox Animation DevTools

⑨ 애니메이션은 머리가 아픕니다

애니메이션 부작용

두통, 어지러움, 편두통, 뇌전증…
Windows
Mac
prefers-reduced-motion: reduce | no-preference

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

@media (prefers-reduced-motion: reduce) {
  * {
    animation-name: none !important;
    transition-property: none !important;

    /* transition 이벤트가 여전히 필요하다면
     *  transition-duration: 0.01s !important;
     *  transition-delay: 0s !important;
     *  ...
     */
  }
}

From JS…


// 브라우저 지원을 확인하세요
if (!('animate' in elem)) {
  return;
}

// 사용자 지원을 확인하세요
if (matchMedia('(prefers-reduced-motion)').matches) {
  return;
}

// 애니메이션을 작성하세요

prefers-reduced-motion

  • 64+
  • 74+
  • 10.1+
  • x

10 things I hate about animations…

  1. 프레임 기반 애니메이션… jump-none
  2. Transition 버그… transitioncancel
  3. JS으로 애니메이션 생성… Element.animate
  4. 전체 애니메이션 이징(Easing)… Element.animate
  5. 애니메이션 속도 정의… Element.getAnimations
  6. Transition 중간점… Element.getAnimations
  7. 날기… offset-path
  8. 성능… Firefox Animation DevTools
  9. 두통… prefers-reduced-motion
  1. …너무 재밌습니다!

들어주셔서 감사합니다!