说一说基于JavaScript的动画

需求

借助JavaScript,实现网页动画效果html

代码

简易Demo:es/js_animation_test.html前端

Anikyu源代码仓库:anikyugit

先说几句

本人不才,大学期间搞过各类各样的设计类软件,包括作动画的Flash、After Effects、Premiere,作3D的3ds Max、Cinema 4D;终于,在大学的第三年,专业开了网页设计课(大二时其实我也有水过一个作网页的校选修),我又不慎接触了作网页的Dreamweaver。Adobe全家桶齐了。程序员

要学作网页,就得学HTML;要把网页写好看,就得学CSS;要让网页有点生气,就得学JavaScript;这不就蹚入了前端这潭浑水。github

// 其实本人并不承认本身是程序员,最可能是会写网页的设计师数据库

大学期间,本人学的是数字媒体技术专业,计算机学院里的专业,C、C++、数据结构、数据库概论、计算机图形学等课程都有上过,外加游戏原画、Flash、三维设计一类的偏向设计类的课程,而后本身又稍稍接触了一点点PHP。曾经我想,要不将来作UI/视频/3D设计师吧;但最终找工做的时候,认真思考了一下,专业课设计类课程彷佛支持不了个人一些野心,外加我比较菜,大学期间全部软件都接触很浅;到后来一直不知道3D模型贴图咋贴,就试着经过学Three.js来学3D。因此就暂时先试着用Dreamweaver这个技能来找工做吧。npm

至于设计,我认为总有一天我会回来的~segmentfault

论网页与传统动画的联系

本人认为,网页与传统动画同样,都是可以使人产生愉悦的能够用于观赏的产品;但比传统动画更强大的是,网页可以与用户直接产生交互。浏览器

专业动画设计软件里,动画的实现通常是逐帧动画或是关键帧动画。数据结构

  • 逐帧动画

创做者每一帧绘制一次,最终将多帧连续播放,实现动画。

  • 关键帧动画

创做者在每一个关键点绘制一帧,最终由程序在两个关键帧之间进行插值,获得中间帧。插值能够是线性插值,也能够是包含缓动的插值。

对于网页动画,在早期,若动画效果很复杂,甚是精美,通常都直接使用Flash等插件来制做;简单的动画则使用JavaScript中的setInterval()来实现,即每隔若干毫秒(保持性能的同时不让用户感到卡顿)对某一对象某一数值进行一次增长,请求一次动画帧,直到该对象该值的量到达预想值,由此实现动画。jQuery中的animate()对其进行了封装。同时还有SVG动画,使用animate标签来进行实现。随着CSS3的出现,DOM元素动画/过渡效果可使用CSS实现。

固然,CSS动画的使用也有一些局限性:

  1. 不可以灵活控制动画播放进度
  2. 仅能够支持DOM元素动画

最近在学习Three.js,其核心库彷佛不包含动画。其官方示例有使用Tween.js这个动画库,但因为我比较想试着本身实现一个相似的动画库(实际上是由于它的用法和个人想法不太相符)。

所以,我本身封装了一个基于JavaScript实现的动画库 —— Anikyu 。

固然本文先并不着重介绍我所封装的库。对于其使用方法,请直接看anikyu的仓库。

网页的维度

咱们都知道,网页有两个维度:宽度和高度。
若是要用到动画,那就再加个时间。
(好吧,若是非要说WebGL或是CSS 3D等等三维技术,那再加个深度;此处不讨论)。

要让一个对象会动,就必须引入时间这个概念;由于时间点是一个点,其中包含的是对象在在当前时间点的状态;既然要让对象会动,就得找下一个时间点,最终连点成线,就产生了一个时间轴,这样就有了时间这个维度。

如何在两个时间点之间进行基本的补间?

如今咱们把两个时间点之间的变化过程当作一个总体,开始时间点动画进度为0,结束时间点动画进度为1。

计算过程

设开始时间为64,结束时间为1024,当前时间为256,那么:

总时间差 = 结束时间 - 开始时间 = 1024 - 64 = 960

当前动画进度 = (当前时间 - 开始时间)/总时间差 = (256 - 64)/960 = 0.2 ;

设开始值为222,结束值为666,那么:

当前时间点(相对于开始值)的增量
= (结束值 - 开始值)*当前动画进度
= (666 - 222)*0.2
= 88.8

所以在该时间点下:

当前对象的值 = 开始值 + 当前时间的增量 = 222 + 88.8 = 310.8

以后使用定时器执行上方的步骤,每次时间改变,从新计算一次进度和时间点增量,并赋值给目标对象,直到当前动画进度达到1,此时动画完成。

加入缓动效果

在上面的代码中,咱们已经实现了最简单的补间,即匀速补间。但在不少状况下,仅有匀速补间是不够的,例如咱们可能要实现一个动画运行先快(慢)后慢(快)/中间快(慢)两头慢(快)/稍微出去一点再回来/弹跳等诸如此类的小效果。在After Effects、Cinema 4D等设计类软件时间轴面板上,咱们能够对缓动函数进行可视化的编辑,从而咱们能够直观地看到缓动函数的曲线。

image

与此相似,借助CSS3所提供的transition-timing-function属性,咱们也能够直接在CSS中实现缓动动画,其属性值包括:linear、ease、ease-in、ease-out、ease-in-out,外加这五个属性值所基于的cubic-bezier(n,n,n,n) —— 三次贝塞尔曲线。

CSS3 所提供的缓动函数已经基本知足咱们平常的一些动画需求,即上文所说起的先快(慢)后慢(快)/中间快(慢)两头慢(快)/稍微出去一点再回来;但对于弹跳效果,因为CSS3 缓动动画仅支持两点之间的简单缓动,而弹跳效果在动画过程当中没法使用CSS3 所支持的三次贝塞尔曲线来表达(弹跳动画包含多个转折点),所以没法使用纯CSS来实现弹跳。

但在JavaScript中,咱们能够本身编写缓动函数。

缓动函数参见 ECharts 示例页面

In Out InOut
quadraticIn quadraticOut quadraticInOut
cubicIn cubicOut cubicInOut
quarticIn quarticOut quarticInOut
quinticIn quinticOut quinticInOut
sinusoidalIn sinusoidalOut sinusoidalInOut
exponentialIn exponentialOut exponentialInOut
circularIn circularOut circularInOut
elasticIn elasticOut elasticInOut
backIn backOut backInOut
bounceIn bounceOut bounceInOut

函数曲线示例:

image

若是自行百度,能够发现这些补间函数基本都是大同小异的。它们都接受一个值 当前动画播放实际进度k,返回一个通过处理的进度值 k2 用以供计算函数算出当前时间点的变化量。

接上一步的计算过程

假设当前动画状态以及所给定值和上一步同样,缓动函数使用的是bounceIn,则:

当前时间点(相对于开始值)的增量
= (结束值 - 开始值) * 缓动函数(当前动画进度)
= (666 - 222)*bounceOut(0.2)
= 134.31

所以在该时间点下:

当前对象的值 = 开始值 + 当前时间的增量 = 222 + 134.31 = 356.31

加入定时器以实现动画

正如上文所说起,动画的存在依赖于时间这个维度。一个时间点表示一个时间点的状态,那如何获得动画播放期间每个时间点的状态呢?

答案就是定时器。定时器可让浏览器每间隔一段时间来执行一段函数。在早期浏览器(IE9或更低版本)中仅可以使用setInterval()来实现不断地请求动画,新版本浏览器中则引入了专门用于建立动画的requestAnimationFrame()。

requestAnimationFrame与setInterval区别在于:

  1. requestAnimationFrame无需手动指定更新间隔时间,其更新跟随屏幕的更新而更新(由屏幕刷新率来肯定),这种更新机制相似CSS3 动画的更新;setInterval须要手动指定更新间隔时间,其更新由定时器来控制。这使得使用setInterval请求动画时可能会因来不及绘制而发生丢帧或过分绘制而产生性能问题,而requestAnimationFrame可以更加精准控制动画的绘制。
  2. 在页面不可见时,requestAnimationFrame会暂停执行,而setInterval只要不被清除将会一直执行。这致使setInterval的运行将十分耗电。

Anikyu包含了requestAnimationFrame的 Polyfill,以用于支持IE9浏览器。

经过定时器不断执行下列操做:

获取到当前时间 - 将当前时间和开始时间进行一系列计算获得当前时间点状态 - 将状态赋值给原始对象

便可实现动画。

代码

先定义两个函数

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

clamp函数用于钳制数值范围。因为当前动画进度的值一定是一个[0,1]区间的值,所以咱们必须让动画进度限制在该范围内,不然可能会致使最终效果值小于/大于预期值

function bounceOut(k) {
    if (k < (1 / 2.75)) { return 7.5625 * k * k; }
    else if (k < (2 / 2.75)) { return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; }
    else if (k < (2.5 / 2.75)) { return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; }
    else { return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; }
}

bounceOut函数是上文所说起的30个缓动函数之一,可在动画进度快要结束时产生弹回的效果。将当前动画进度传入后,能够得到一个通过处理的进度,在实际计算时根据该进度获得当前时间点状态,实现变速运动。若当前动画进度不通过该函数处理,则动画为匀速运动。

获取动画对象,并定义初始值、结束值,获取动画起始时间

let el = document.getElementById('el')

let init = 222;
let end = 666;
let timeDelta = 960;

let startTime = Date.now()

编写动画函数

function animate() {
    interval = requestAnimationFrame(animate)
    let loop = () => {
        let currentProgress = (Date.now() - startTime) / timeDelta

        currentProgress = clamp(currentProgress, 0, 1)

        let sumNumber = (end - init) * bounceOut(currentProgress)

        // let sumNumber = (end - init) * currentProgress

        let currentStatus = init + sumNumber

        if (currentProgress === 1) {
            cancelAnimationFrame(interval)
            el.style.transform = `translate(${end}px)`
        } else {
            el.style.transform = `translate(${currentStatus}px)`
        }
    }
    loop()
}
animate()

以上就是我对JavaScript动画的理解。借此机会,我也封装了一个上文所说起的动画库,Anikyu。

Anikyu是本人春节期间在家,正好也是疫情期间,作这个动画demo时忽然想封装的一个库。正巧借此机会也学习了一下ES6没用过的特性、Webpack、Babel,同时也将该库发布到了 npm

各位要是以为靠谱不妨来用一下,支持IE 9+浏览器,支持Node.js环境。

我也尝试将Anikyu代码结构、开发过程写一篇新的博客:
尝试经过封装一个库来学习JavaScript(ES6)相关特性以及相关构建工具]

#7

可能有点多,我想起来的时候慢慢补充。

相关文章
相关标签/搜索