JavaScript 动画原理浅析

FerBorrie 5年前
   <h2>动画</h2>    <p>通常,框架会为你处理动画。但是,你可能想知道仅仅用javascript怎么来实现动画,和可能出现的一些问题。理解这项技术对于创建复杂的动画是很有帮助的,即使在在框架的帮助下。</p>    <h2>动画基础</h2>    <p>javascript的动画是通过周期性的改变DOM元素的style 或者 canvas 的对象。</p>    <p>这整个的动画过程被分成了很小的碎片,每一个碎片被定时器调用。因为定时器的周期非常短,所以动画看起来是连续的。</p>    <p>伪代码:</p>    <pre>  <code class="language-javascript">var id = setInterval(function(){      /*当前显示帧*/      if(/*完成*/) clearInterval(id)  }, 10)</code></pre>    <p>上面代码每一帧的间隔是 10ms ,意味着每秒钟有100帧。</p>    <p>在大多数的javascript框架中 10-15ms的delay是默认的。越短的延时让动画看起来更加流畅,但是只有在浏览器足够快的时候,每一步的动画才会准时运行。</p>    <p>如果动画需要许多计算,CPU可能会100%的负载,动画就会变得迟缓。这种情况下,delay就应该被增加。例如,delay 40ms 就是每秒25帧,接近24帧的电影标准。</p>    <h3>setInterval 而不是用 setTimout</h3>    <p>我们使用 <em>setInterval</em> ,而不是递归的使用 <em>setTimeout</em> ,是因为我们想要一帧一个delay,而不是所有帧之间有一个固定的delay.查阅 <a href="/misc/goto?guid=4959740152441512355" rel="nofollow,noindex">Understanding timers: setTimeout and setInterval</a> 来了解 <em>setInterval</em> 和递归的 <em>setTimeout</em> 之间到底有什么不同。(译者:经测试最新的chrome > 56中,setInterval的行为跟本文中描述的不同。当函数执行时间超过了delay时间,下一个函数不会马上运行,仍然会等一个delay的间隔,再执行。但本文仍有参考价值)</p>    <h3>例子</h3>    <p>例如,一个元素移动通过改变 <em>element.stye.left</em> 从0到100px。每10ms改变1px。</p>    <pre>  <code class="language-xml"><html>  <head>  <link type="text/css" rel="stylesheet" href="/files/tutorial/browser/animatio/animate.css">  <script>      function move(elem) {          var left = 0;          function frame(){              left++ // 更新参数              elem.style.left = left + 'px' // 显示帧              if(left === 100) //检查结束条件              clearInterval(id)          }          var id = setInterval(frame, 10) //没10ms绘制一次      }  </script>  </head>  <body>  <div onclick="move(this.children[0])" class="example_path">  <div class="example_block"></div>  </div>  </body>  </html></code></pre>    <p><a href="/misc/goto?guid=4959740152537623912" rel="nofollow,noindex">在新的窗口打开</a></p>    <h2>动画重构</h2>    <p>为了让动画更加通用,我们介绍下面的一些参数:</p>    <ul>     <li> <p>delay</p> <p>每一帧之间的间隔(ms).例如,10ms</p> </li>     <li> <p>duration</p> <p>整个动画完成需要的时间(ms)。例如:1000ms</p> </li>    </ul>    <p>当动画开始的时候,我们也可以用:</p>    <ul>     <li> <p>start 动画开始的时间, start = new Date<br> 动画过程的核心,每一帧我们需要计算:</p> </li>     <li> <p>timePassed</p> <p>从动画开始所经过的时间(ms)。</p> </li>    </ul>    <p>从0到动画(duration)结束.但是偶尔可能会超过结束时间,因为浏览器的计时器并不准确。</p>    <ul>     <li> <p>progress</p> <p>已经过去的动画时间作为分子,计算每一帧通过公式 timePassed/duration 。值得范围通常是0到1。</p> </li>    </ul>    <p>例如,progress的值为0.5就是说动画时间(duration)已经过去了一半。</p>    <ul>     <li> <p>delta(progress)</p> <p>一个返回当前动画进度的函数。</p> </li>    </ul>    <p>例如,我们让高度属性从0%变化到100%。</p>    <p>我们可以让动画均匀的显示,这样动画进度看起来就是线性的。</p>    <p>Mapping:</p>    <p>->progress = 0 -> height = 0%</p>    <p>->progress = 0.2 -> height = 20%</p>    <p>->progress = 0.5->height = 50%</p>    <p>->progress = 0.8 -> height = 80%</p>    <p>->progress = 1 -> height = 100%</p>    <p><img src="https://simg.open-open.com/show/5d68e6e12ee26959b5df367a2c88493b.png"></p>    <p>但是我们可能想让动画缓慢的开始然后再加速。这样的话经过一半的动画时间高度可能只有25%</p>    <p>,然后逐渐加速到100%。</p>    <p>Mapping:</p>    <p>->progress = 0 -> height = 0%</p>    <p>->progress = 0.2 -> height = 4%</p>    <p>->progress = 0.5->height = 25%</p>    <p>->progress = 0.8 -> height = 64%</p>    <p>->progress = 1 -> height = 100%</p>    <p><img src="https://simg.open-open.com/show/8c849114264dac51b5d39a51c8f16765.png"></p>    <p>delta(progress) 是一个映射动画进度增量的函数</p>    <p>动画进度不是一个高度,而是一个数字,通常在0到1之间。</p>    <p>这篇文章会用一些例子进一步讨论几种增量函数</p>    <ul>     <li> <p>step(delta)</p> <p>这个函数是实际上用来做这件事的函数。</p> </li>    </ul>    <p>它计算出增量的结果并且应用它。</p>    <p>对于这个高度的例子,他们可能是:</p>    <pre>  <code class="language-javascript">function state(delta) {      elem.style.height = 100*delta + "%"  }</code></pre>    <p>到现在为止几个重要的参数是:</p>    <p>->delay是 setInterval 的第二个参数。</p>    <p>->duration是动画完成需要的时间。</p>    <p>->progress是动画已经经过的时间,除以duration使它的值在0到1之间。</p>    <p>->delta通过当前的时间,计算当前的动画进度。</p>    <p>->step做了视觉上(?)的工作。它获得当前的动画进度,并且把它应用在元素上。</p>    <h3>通用的动画</h3>    <p>让我们把上面讨论的简单的写成一个可扩展的动画核心。</p>    <p>下面的动画函数执行时间管理并且把工作分配给delta和step:</p>    <pre>  <code class="language-javascript">function animate(opts) {      var start = new Date;      var id = setInterval(function(){          var timePassed = new Date - start;          var progress = timePassed / opts.duration;          if(progress > 1) progress = 1;          var delta = opts.delta(progress)          opts.step(delta)          if(progress == 1) {              clearInterval(id)          }      }, opts.delay || 10)  }</code></pre>    <p>参数对象应该包含以下的一些动画属性:</p>    <p>->delay</p>    <p>->duration</p>    <p>->function delta</p>    <p>->function step</p>    <p>这个算法完全遵循上面的描述</p>    <h3>Example</h3>    <p>让我们基于这个来创建一个移动的动画</p>    <pre>  <code class="language-javascript">function move(element, delta, duration){      var to = 500;      animate({          delay: 10,          duration: duration || 1000,          delta: delta,          step: function(delta){              element.style.left = to * delta + 'px'          }      })  }</code></pre>    <p>它把工作指派给 animate ,给animate传入了delay,用户提供的duration, delta, 和 step。</p>    <p>delta = function(p) { return p}</p>    <p>意味着动画进程一直是均匀的</p>    <p>step</p>    <p>用一个简单的公式映射0..1,delta返回一个进度值 0..to。把这些结果应用到element.style.left。</p>    <p>用法:</p>    <pre>  <code class="language-javascript"><div onclick="move(this.children[0], function(p) {return p})"     class="example_path">      <div class="example_block"></div>  </div></code></pre>    <h2>数学,进度增量函数</h2>    <p>动画就是是根据给定的规则,一直改变属性。在javascript动画中,这个规则就是delta函数来实现的。</p>    <p>不同的deltas使动画的速度,加速度和其他的参数表现出各种各样的方式。</p>    <p>数学公式通常被用在这里。但是它们对于只做web编程和忘记学校里的数学的人来说,可能很陌生。在这个章节,我们将浏览很多的受欢迎的公式并看一下它们是如何工作的。</p>    <p>动画运动的例子,提供不同的delta.</p>    <p>线性 delta</p>    <pre>  <code class="language-javascript">function linear(progress){      return progress;  }</code></pre>    <p><img src="https://simg.open-open.com/show/fff3b659c44cb7aa92b4516a2e3f3888.png"></p>    <p>水平方向指的是时间进度,垂直方向指的是动画进程。</p>    <p>我们已经能看见了。线性的delta使动画以固定的速度进行。</p>    <p>Power of n</p>    <p>也是一个简单的例子。delta是progress的n次方。例如4次,3次函数等。</p>    <p>例如2次函数</p>    <pre>  <code class="language-javascript">function quad(progress) {      return Math.pow(progress, 2)  }</code></pre>    <p>图形</p>    <p><img src="https://simg.open-open.com/show/519b45505a72c6a4ca11dbe294cf3366.png"></p>    <p>增加加速度的影响。例如,下面这个图片是5次方。</p>    <p><img src="https://simg.open-open.com/show/f0869effc45bbe35ff5137c4ab788dfa.png"></p>    <p>Circ: 圆的一部分</p>    <p>函数:</p>    <pre>  <code class="language-javascript">function circ(progress) {      return 1 - Math.sin(Math.acos(progress))  }</code></pre>    <p>图像</p>    <p><img src="https://simg.open-open.com/show/4fa83464f812ec25c3dee387e7db718c.png"></p>    <p>Back: the bow function</p>    <p>这个函数向弓一样工作:首先我们"拉开工,然后发射出去"。</p>    <p>不像先前的函数,它会依赖于一个附加的参数 <em>x</em> ,这就是“弹性系数”。</p>    <p>它定义了“拉弓”的距离。</p>    <p>代码是:</p>    <pre>  <code class="language-javascript">function back(progress, x) {      return Math.pow(progress, 2) * ((x + 1) * progress - x)  }</code></pre>    <p>图像x=1.5</p>    <p><img src="https://simg.open-open.com/show/554c2796928ca3a329da4e4ace6c8a39.png"></p>    <p>bounce(弹跳)</p>    <p>想象一下我们释放一个球,它掉在地上,然后弹跳几次,最后停止。</p>    <p>bounce事实上做了相反的事情。属性将会一直改变知道它达到目标点。</p>    <p>这个函数比之前要复杂一些,也没有简单的数学公式。</p>    <pre>  <code class="language-javascript">function bounce(progress) {      for(var a = 0, b=1, result; 1; a+=b, b /= 2) {          if (progress >= (7-4*a) / 11){              return -Math.pow(11 - 6 * a - 11 * progress) / 4, 2) + Math.pow(b, 2)          }      }  }</code></pre>    <p>*这段代码曲子 MooTools.FX.Transitions.据我所知,仍然有其他实现bounce的算法。</p>    <p>Elastic 弹性</p>    <p>这个函数也依赖于额外的参数x,x定义了初始范围。</p>    <pre>  <code class="language-javascript">function elastic(progress, x){      return Math.pow(2, 10 * (progress - 1)) * Math.cos(20 * Math.PI * x/3 *progress)  }</code></pre>    <p><em>图像 x=1.5</em></p>    <p><img src="https://simg.open-open.com/show/fc257d4d948431b021fd024bb7fa5a12.png"></p>    <p>在这个例子中,为了让动画更加平滑,时间是2s.</p>    <p>反向函数(Reverse functions)</p>    <p>一个javascript框架经常会提供的一种delta函数。</p>    <p>它们的直接使用被称作 easeIn.</p>    <p>偶尔的时候,需要以时间倒退的方式来展示动画。这就叫做 <em>easeout</em> 被'time-reversing'delta来实现。</p>    <p>第一次翻译点东西,质量不好,主要是做记录用。里面有很多比较'术语'的,不好翻,最好看原文。后面我也会继续修改。</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959740152618724826" rel="nofollow,noindex">http://javascript.info/tutori...</a></p>    <p> </p>