从0到1,开发一个动画库(2)

2018 年 1 月 31 日 前端大全

(点击上方公众号,可快速关注)

作者:jshao

https://segmentfault.com/a/1190000013010057


传送门:从0到1,开发一个动画库(1)


上一节讲到了最基础的内容,为动画构建“帧-值”对应的函数关系,完成“由帧到值”的计算过程。这一节将在上节代码的基础上谈谈如何给一个完整的动画添加各类事件。


在添加各类事件之前,我们先对_loop循环函数进行一些改进:


_loop() {

  const t = Date.now() - this.beginTime,

        d = this.duration,

        func = Tween[this.timingFunction] || Tween['linear'];

 

  if (this.state === 'end' || t >= d) {

    this._end();

  } else if (this.state === 'stop') {

    this._stop(t);

  } else if (this.state === 'init') {

    this._reset();

  } else {

    this._renderFunction(t, d, func)

    window.requestAnimationFrame(this._loop.bind(this));

  }

}


可以清晰地看到,我们在循环中增加了很多类型的判断,根据state当前不同的状态执行相应的处理函数:我们新增了_end、_stop、_reset方法分别去处理结束、暂停和重置这三种状态,接下来我们依次讲解这些状态的处理。


End


我们在Core类下增加_end、end和renderEndState方法,end方法用于主动结束动画:


end() {

  this.state === 'play' ? (this.state = 'end') : this._end();

}

 

_end() {

  this.state = 'end';

  this._renderEndState();

  this.onEnd && this.onEnd();

}

 

_renderEndState() {

  const d = this.duration,

        func = Tween[this.timingFunction] || Tween['linear'];

  this._renderFunction(d, d, func);

}


通过执行end方法,我们可以主动结束动画:如果当前目标处于运动状态,则将其设置为end,因此下一个_loop函数被执行的时候,程序就被流向了_end处理函数;若为其他状态,意味着循环没有被打开,我们就直接调用_end方法,使其直接到终止状态。


_end函数的作用有三个:


  • 将当前状态设置为end(为何要重复设置一次状态呢?这不是多余的吗?其实,倘若我们主动触发end去结束动画,这的确是多余的,但如果是动画自己进行到了末尾,也就是t >= d的时刻,则必须得在_end中去设置状态,以确保它处于结束状态)

  • 通过_renderEndState方法,将目标变成结束状态

  • 若有回调函数则执行回调


Reset


重置动画的方式也是大同小异,与上面一样


reset() {

  this.state === 'play' ? (this.state = 'init') : this._reset();

}

 

_reset() {

  this.state = 'init';

  this._renderInitState();

  this.onReset && this.onReset();

}

 

_renderInitState() {

  const d = this.duration,

        func = Tween[this.timingFunction] || Tween['linear'];

  this._renderFunction(0, d, func);

}


Stop


让动画暂停也是与上述两者一样,但唯一的区别是,需要给_renderStopState方法传入当前时间进度:


stop() {

  if (this.state === 'play') {

    this.state = 'stop';

  } else {

    // 使目标暂停,无需像end或reset那样将目标变成结束/起始状态,保持当前状态即可

    this.state = 'stop';

    this.onStop && this.onStop();

  }

}

 

_stop(t) {

  this.state = 'stop';

  this._renderStopState(t);

  this.onStop && this.onStop();

}

 

_renderStopState(t) {

  const d = this.duration,

        func = Tween[this.timingFunction] || Tween['linear'];

  this._renderFunction(t, d, func);

}


play


我们要在动画开始执行的时候触发onPlay事件,只需在_play方法内增加一行代码即可:


_play() {

      this.state = 'play';

  

      // 新增部分

    this.onPlay && this.onPlay();

  

      this.beginTime = Date.now();

      const loop = this._loop.bind(this);

    window.requestAnimationFrame(loop);

}


完整代码如下:


import Tween from './tween';

 

class Core {

    constructor(opt) {

        this._init(opt);

        this.state = 'init';

    }

 

    _init(opt) {

    this._initValue(opt.value);

    this.duration = opt.duration || 1000;

    this.timingFunction = opt.timingFunction || 'linear';

    this.renderFunction = opt.render || this._defaultFunc;

 

    /* Events */

    this.onPlay = opt.onPlay;

    this.onEnd = opt.onEnd;

    this.onStop = opt.onStop;

    this.onReset = opt.onReset;

  }

 

  _initValue(value) {

      this.value = [];

      value.forEach(item => {

          this.value.push({

              start: parseFloat(item[0]),

              end: parseFloat(item[1]),

          });

      })

  }

 

  _loop() {

    const t = Date.now() - this.beginTime,

      d = this.duration,

      func = Tween[this.timingFunction] || Tween['linear'];

 

    if (this.state === 'end' || t >= d) {

      this._end();

    } else if (this.state === 'stop') {

      this._stop(t);

    } else if (this.state === 'init') {

      this._reset();

    } else {

      this._renderFunction(t, d, func)

      window.requestAnimationFrame(this._loop.bind(this));

    }

  }

 

  _renderFunction(t, d, func) {

      const values = this.value.map(value => func(t, value.start, value.end - value.start, d));

      this.renderFunction.apply(this, values);

  }

  

  _renderEndState() {

    const d = this.duration,

      func = Tween[this.timingFunction] || Tween['linear'];

    this._renderFunction(d, d, func);

  }

 

  _renderInitState() {

    const d = this.duration,

      func = Tween[this.timingFunction] || Tween['linear'];

    this._renderFunction(0, d, func);

  }

 

  _renderStopState(t) {

    const d = this.duration,

      func = Tween[this.timingFunction] || Tween['linear'];

    this._renderFunction(t, d, func);

  }

 

  _stop(t) {

    this.state = 'stop';

    this._renderStopState(t);

    this.onStop && this.onStop();

  }

 

  _play() {

      this.state = 'play';

    this.onPlay && this.onPlay();

    

      this.beginTime = Date.now();

      const loop = this._loop.bind(this);

    window.requestAnimationFrame(loop);

  }

 

  _end() {

    this.state = 'end';

    this._renderEndState();

    this.onEnd && this.onEnd.call(this);

  }

 

  _reset() {

    this.state = 'init';

    this._renderInitState();

    this.onReset && this.onReset();

  }

 

  play() {

      this._play();

  }

 

  end() {

    this.state === 'play' ? (this.state = 'end') : this._end();

  }

 

  reset() {

    this.state === 'play' ? (this.state = 'init') : this._reset();

  }

 

  stop() {

    if (this.state === 'play') {

      this.state = 'stop';

    } else {

      this.state = 'stop';

      this.onStop && this.onStop();

    }

  }

}

 

window.Timeline = Core;


相应地,html的代码也更新如下,添加了各类按钮,主动触发目标的各类事件:


<!DOCTYPE html>

<html>

<head>

    <title></title>

    <style type="text/css">

        #box {

            width: 100px;

            height: 100px;

            background: green;

        }

    </style>

</head>

<body>

<div id="box"></div>

<button id="start">START</button>

<button id="end">END</button>

<button id="stop">STOP</button>

<button id="reset">RESET</button>

<script type="text/javascript" src="timeline.min.js"></script>

<script type="text/javascript">

    const el = (name) => document.querySelector(name);

    const box = el('#box');

    const timeline = new Timeline({

        duration: 3000,

        value: [[0, 400], [0, 600]],

        render: function(value1, value2) {

            box.style.transform = `translate(${ value1 }px, ${ value2 }px)`;

        },

        timingFunction: 'easeOut',

        onPlay: () => console.log('play'),

        onEnd: () => console.log('end'),

        onReset: () =>  console.log('reset'),

        onStop: () => console.log('stop')

    })

 

    el('#start').onclick = () => timeline.play();

    el('#end').onclick = () => timeline.end();

    el('#stop').onclick = () => timeline.stop()

    el('#reset').onclick = () => timeline.reset()

</script>

</body>

</html>


看到这里,我们第二节的内容就结束啦,下一节,我们将介绍:


  • 支持自定义路径动画

  • 动画间的链式调用


下一节再见啦^_^



觉得本文对你有帮助?请分享给更多人

关注「前端大全」,提升前端技能

登录查看更多
1

相关内容

【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
专知会员服务
80+阅读 · 2020年6月20日
【干货书】现代数据平台架构,636页pdf
专知会员服务
253+阅读 · 2020年6月15日
【天津大学】风格线条画生成技术综述
专知会员服务
31+阅读 · 2020年4月26日
专知会员服务
123+阅读 · 2020年3月26日
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
Python用于NLP :处理文本和PDF文件
Python程序员
4+阅读 · 2019年3月27日
免费|机器学习算法Python实现
全球人工智能
5+阅读 · 2018年1月2日
【强烈推荐】浅谈将Pytorch模型从CPU转换成GPU
机器学习研究会
7+阅读 · 2017年12月24日
开发 | 机器学习之确定最佳聚类数目的10种方法
AI科技评论
3+阅读 · 2017年10月11日
python pandas 数据处理
Python技术博文
4+阅读 · 2017年8月30日
Arxiv
7+阅读 · 2018年12月26日
Music Transformer
Arxiv
5+阅读 · 2018年12月12日
dynnode2vec: Scalable Dynamic Network Embedding
Arxiv
14+阅读 · 2018年12月6日
Arxiv
3+阅读 · 2018年5月20日
Arxiv
5+阅读 · 2018年5月1日
VIP会员
相关VIP内容
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
专知会员服务
80+阅读 · 2020年6月20日
【干货书】现代数据平台架构,636页pdf
专知会员服务
253+阅读 · 2020年6月15日
【天津大学】风格线条画生成技术综述
专知会员服务
31+阅读 · 2020年4月26日
专知会员服务
123+阅读 · 2020年3月26日
相关资讯
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
Python用于NLP :处理文本和PDF文件
Python程序员
4+阅读 · 2019年3月27日
免费|机器学习算法Python实现
全球人工智能
5+阅读 · 2018年1月2日
【强烈推荐】浅谈将Pytorch模型从CPU转换成GPU
机器学习研究会
7+阅读 · 2017年12月24日
开发 | 机器学习之确定最佳聚类数目的10种方法
AI科技评论
3+阅读 · 2017年10月11日
python pandas 数据处理
Python技术博文
4+阅读 · 2017年8月30日
Top
微信扫码咨询专知VIP会员