如丝般顺滑:使用 CSS3 实现 60 帧的动画

hubuke 8年前
   <p>在移动端上实现动画很简单。</p>    <p>在移动端上<strong>正确地</strong>实现动画也比较简单...如果你采纳我们的建议的话。</p>    <p>虽然现在每个人都会使用 CSS3 实现动画,但许多人用的都不够恰当。很多应加以考虑的最佳实践常常被忽略,因为仍然有人不明白这些最佳实践的真正意义。</p>    <p>如今有这么多的设备规范,如果还不有针对性地优化你的代码,糟糕的用户体验将让你死无葬身之地。</p>    <p>记住:虽然市场上始终有一些高端的旗舰机在挑战性能极限,但你面对的仍将是和这些性能怪兽相比只是玩具一样的低端设备。</p>    <p>我们想帮助你<strong>正确地</strong>驾驭 CSS3。首先先要了解几件事。</p>    <h3>了解时间轴</h3>    <p>当渲染和处理 HTML 元素时,浏览器做了什么?这个时间轴叫做<strong>关键渲染路径</strong>。</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/16a6fdb59cf3ea48da88ff9372c2d180.png"></p>    <p>想达到流畅的动画效果我们需要关注修改属性对<strong>合成(Composite)</strong>阶段的影响,而不是其它阶段。</p>    <p>1. 样式</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/0e52862832d70bf302b4823460fd7c1f.png"></p>    <p>浏览器开始计算应用于元素的样式 —— 重计算<strong>样式</strong>。</p>    <p>2. 布局</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/b787987b7d71ae0910d1ff6298f3e6f4.png"></p>    <p>下一步,浏览器开始为每个元素生成形状和位置 —— <strong>布局</strong>。这是浏览器设置页面属性的地方,如 <strong>width</strong> 和 <strong>height</strong>,以及 <strong>margin</strong> 或者 <strong>left</strong>、<strong>top</strong>、<strong>right</strong>、<strong>bottom</strong>。</p>    <p>3. 渲染</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/fb67ee56fa55edebeccb3e0f2e1e2f15.png"></p>    <p>浏览器开始向层中填充像素。要使用的属性有 <strong>box-shadow</strong>,<strong>border-radius</strong>,<strong>color</strong>,<strong>background-color</strong> 等等。</p>    <p>4. 合成</p>    <p>这就是你动手脚的地方了,因为浏览器开始把所有层画到屏幕上。</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/c29aa5e95687b655f9f8ba6260e554f4.png"></p>    <p>现代浏览器能够通过使用 <strong>transform</strong> 和 <strong>opacity</strong> 完美运行 4 种样式。</p>    <ul>     <li><strong>位置</strong> — transform: translateX(<em>n</em>) translateY(<em>n</em>) translateZ(<em>n</em>);</li>     <li><strong>缩放</strong> — transform: scale(<em>n</em>);</li>     <li><strong>旋转</strong> — transform: rotate(<em>n</em> deg);</li>     <li><strong>透明</strong> — opacity: <em>n</em>;</li>    </ul>    <h3>如何达到 60 帧每秒</h3>    <p>想法有了,让我们撸起袖子开始干活吧。</p>    <p>我们从 HTML 开始,创建一个非常简单的结构,并把类名 app-menu 的元素放入类名 layout 的元素中。</p>    <pre>  <code class="language-css"><div class="layout">      <div class=”app-menu”></div>      <div class=”header”></div>  </div></code></pre>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/37872b908bb0161290a08bfd387e8202.gif"></p>    <h3>错误做法</h3>    <pre>  <code class="language-css">.app-menu {    left: -300px;    transition: left 300ms linear;  }    .app-menu-open .app-menu {    left: 0px;    transition: left 300ms linear;  }</code></pre>    <p>看到我们更改的这些属性了吗?你应该避免在 <strong>transitions</strong> 中使用 <strong>left</strong>、<strong>top</strong>、<strong>right</strong>、<strong>bottom</strong> 属性。它们的更新让浏览器每次都要创建布局,影响所有它们的子元素,进而难以实现流畅的动画。</p>    <p>结果是这样的:</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/9756be8ca8319b3f21b473596d7972fb.gif"></p>    <p>这个动画一点也不流畅。我们通过<a href="/misc/goto?guid=4959677110935502235">开发者工具</a>检查背后到底发生了什么,请看:</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/979ea55bcfcb9e8570ffe20961c55253.png"></p>    <p>能够清晰地看到 FPS 非常不规则,性能也就比较糟糕。</p>    <h3>使用 Transform</h3>    <pre>  <code class="language-css">.app-menu {    -webkit-transform: translateX(-100%);    transform: translateX(-100%);    transition: transform 300ms linear;  }    .app-menu-open .app-menu {    -webkit-transform: none;    transform: none;    transition: transform 300ms linear;  }</code></pre>    <p><strong>transform</strong> 属性会影响<strong>合成</strong>阶段。在这里浏览器被告知层马上要被渲染,做好准备执行动画,因此动画渲染时卡顿更少。</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/38f793823e1b1d20258e170326367304.gif"></p>    <p>时间轴:</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/41557e241bc7f7f9b58e756de9b2df79.png"></p>    <p>开始有效果了,FPS 变得有规律了,此外,动画变得更流畅了。</p>    <p>FPS 开始优化,并且更加稳定,动画也更流畅。</p>    <h3>使用 GPU 执行动画</h3>    <p>百尺竿头更进一步。为了让动画“如丝般顺滑”,我们接下来使用 GPU 来渲染。</p>    <pre>  <code class="language-css">.app-menu {    -webkit-transform: translateX(-100%);            transform: translateX(-100%);    transition: transform 300ms linear;    will-change: transform;  }</code></pre>    <p>虽然一些浏览器仍然需要 <strong>translateZ()</strong> 或 <strong>translate3d()</strong> 作为备选方案,<strong>will-change</strong> 被广泛支持已经是势不可挡了。它的功能是把元素提升到另一个层中,这样浏览器就不必关心布局渲染或者绘制了。</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/63bb71b1ae39778bcb355c288e293c40.gif"></p>    <p>这样动画能流畅到什么程度?看时间轴:</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/f7852039882dcd10197a78945ac615aa.png"></p>    <p>动画的 FPS 更稳定了,渲染也更快了。但是有一帧仍然渲染得很久。在开始处还有一点点瓶颈。</p>    <p>记住刚开始创建的 HTML 结构吗?我们用 JavaScript 控制 <strong>app-menu</strong> div。</p>    <pre>  <code class="language-css">function toggleClassMenu() {    var layout = document.querySelector(".layout");    if(!layout.classList.contains("app-menu-open")) {      layout.classList.add("app-menu-open");    } else {      layout.classList.remove("app-menu-open");    }  }  var oppMenu = document.querySelector(".menu-icon");  oppMenu.addEventListener("click", toggleClassMenu, false);</code></pre>    <p>问题出在对 <strong>layout</strong> 元素增加类名上,我们使浏览器计算了不止一次样式 —— 这影响了渲染效果。</p>    <h3>如丝般顺滑的 60 帧 FPS</h3>    <p>如果我们在视窗之外创建菜单会如何?这种分离化的区域能够保证只有需要做动画的元素才会被影响。</p>    <p>因此,我们改进下面的 HTML 结构:</p>    <pre>  <code class="language-css"><div class="menu">      <div class="app-menu"></div>  </div>  <div class="layout">      <div class="header"></div>  </div></code></pre>    <p>此外你也可以通过一种略有不同的方式去控制菜单的状态。我们通过 JavaScript 的 <strong>transitionend</strong> 方法,在监控到动画结束时移除这个控制动画的 CSS 类。</p>    <pre>  <code class="language-css">function toggleClassMenu() {    myMenu.classList.add("menu--animatable");    myMenu.classList.add("menu--visible");    myMenu.addEventListener("transitionend", OnTransitionEnd, false);  }  function OnTransitionEnd() {    myMenu.classList.remove("menu--animatable");  }  var myMenu = document.querySelector(".menu");  var oppMenu = document.querySelector(".menu-icon");  oppMenu.addEventListener("click", toggleClassMenu, false);</code></pre>    <p>现在把它们放到一起,然后看效果。</p>    <p>下面是完全正确的使用 CSS3 实现动画的例子:</p>    <pre>  <code class="language-css">.menu {    position: fixed;    left: 0;    top: 0;    width: 100%;    height: 100%;    overflow: hidden;    pointer-events: none;    z-index: 150;  }    .menu—visible {    pointer-events: auto;  }    .app-menu {    background-color: #fff;    color: #fff;    position: relative;    max-width: 400px;    width: 90%;    height: 100%;    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);    -webkit-transform: translateX(-103%);    transform: translateX(-103%);    display: flex;    flex-direction: column;    will-change: transform;    z-index: 160;    pointer-events: auto;  }    .menu—-visible.app-menu {    -webkit-transform: none;    transform: none;  }    .menu-—animatable.app-menu {    transition: all 130ms ease-in;  }    .menu--visible.menu—-animatable.app-menu {    transition: all 330ms ease-out;  }    .menu:after {    content: ‘’;    display: block;    position: absolute;    left: 0;    top: 0;    width: 100%;    height: 100%;    background: rgba(0,0,0,0.4);    opacity: 0;    will-change: opacity;    pointer-events: none;    transition: opacity 0.3s cubic-bezier(0,0,0.3,1);  }    .menu.menu--visible:after{    opacity: 1;    pointer-events: auto;  }</code></pre>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/135435001a7d1354889405da61eb13d9.gif"></p>    <p>时间轴是怎么样的?</p>    <p><img alt="如丝般顺滑:使用 CSS3 实现 60 帧的动画" src="https://simg.open-open.com/show/af6e9bc2d9ca276480dfbe39e7b077a7.png"></p>    <p>如丝般地顺滑,是吧?</p>    <p> </p>    <p>来自:http://www.zcfy.cc/article/smooth-as-butter-achieving-60-fps-animations-with-css3-1054.html</p>    <p><span style="color:rgb(255, 255, 255)">Save</span></p>