10 thingsI wish peopleknew aboutWeb animation

一些关于我的事……

  • 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
  • 性能(和开发者工具)
  • 信息无障碍

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)';
  };
  

要点 1要创建一个 transition,浏览器需要看到样式的变化

提示

  • 注意以下要点:
    • createElement
    • 使用框架(如React等)来“渲染”
    • display: none
  • getComputedStyle(elem) 不会更新样式
    • getComputedStyle(elem).<property>
  • 使用 getComputedStyle 的代价很高
  • 请考虑使用 CSS Animations 或 Web Animations

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();
  });
};
  

但是如果……

  • 元素变成了 display:none
  • 元素被重新创建了?被重新“渲染”了?
  • transition-property 属性变了?
  • 元素被移除了?

transition会被取消
transitionend 事件不会触发


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

处理取消的transitions

  • setTimeout
  • transitioncancel 事件
    • 53+
    • 74+ (四月)
    • 技术预览版
    • x
  • 还有 transitionrun 事件
    • 需确保一开始是有 transition 的

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
  );
};
  

要点 2Transitions 可以被取消

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; }
}
  

要点 3animation-timing-function只在关键帧之间 适用

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)',
  }
);
  

为什么?

  • 和其他 JS 方案相比
    • 性能
    • 无需添加库
    • 可以和 CSS 共同使用
  • 和 CSS 相比
    • 面向过程
    • (在将来)使用 CSS 创建,使用 JS 控制
https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/

浏览器支持

  • 48+
  • 36+
  • 技术预览版
  • x

谁在用?

来源: Chrome use counter for Element.animate()

2018年JS状态调查中第5受欢迎的API

特性检测


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

你也许不需要 polyfill (web-animations-js)

要点 4你现在已经可以使用Web Animations

CSS transitions/animations Web Animations
animation-iteration-count iterations
transition-timing-function, animation-timing-function easing
默认过渡为 ease 默认过渡为 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',
  }
);
  

要点 5Web Animations 与CSS 有不同之处,尤其是过渡(easing)

性能

transform 和 opacity

移动
大小变化
转动
3D
淡入淡出
大小变化 + 转动 + 淡入淡出
颜色变化
阴影效果
裂开

要点 6尽量只动画化transform 和 opacity

要点 7你可以使用动画检查器来检查动画性能

单页 Web 应用


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

单页 Web 应用


<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>

当你点击时……

  1. 处理点击事件
  2. 创建 DOM 元素
  3. 解析样式
    • (<CSSTransition> 多次解析样式和布局)
  4. 计算布局
  5. 绘制每个新元素
  6. 运行动画

如果……


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

当你点击时……

  1. 处理点击事件
  2. 创建 DOM 元素
  3. 解析样式
    • (<CSSTransition> 多次解析样式和布局)
  4. 计算布局
  5. 绘制每个新元素
  6. 运行动画
之前 (~100ms)
之后 (~30ms)

要点 8在页面完全准备好之前试着先准备需要动画化的部分

信息无障碍

对动画感到不舒服

头痛、头晕、癫痫等疾病
Windows
Mac
prefers-reduced-motion: reduce | no-preference

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

@media (prefers-reduced-motion: reduce) {
  animation-name: none;
  transition-property: none;
}
  • 64+
  • 74+ (四月)
  • 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...

要点 9你可以(可能也应该)检查用户是否更喜欢减少动画

10件事

Transitions

  1. transition 仅在样式发生变化时才会运行
  2. transition 可以被取消

CSS Animations

  1. 对于 CSS 动画,过渡(easing)始终适用于关键帧之间

Web Animations

  1. 你现在已经可以使用 Web Animations
  2. … 但和 CSS 动画有不同之处

性能

  1. 尽量只动画化 transformopacity
  2. 你可以在开发者工具中检查动画性能
  3. 在页面完全准备好之前试着先准备好需要动画化的部分

信息无障碍

  1. 根据用户的偏好,禁用复杂动画

不要忘记……

  1. 玩得开心!

Thank you!