从0到1,开发一个动画库(代码片段)

jlfw jlfw     2022-12-25     593

关键词:

传送门:从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类下增加_endendrenderEndState方法,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>

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

  • 支持自定义路径动画
  • 动画间的链式调用

下一节再见啦^_^

从0到1!大话动静态代理(代码片段)

追溯学一个技术,要知道技术因何而产生,才能有学下去的目标和动力,才能更好的理解首先,要明确为什么要存在代理呢?存在一个常见的需求:怎样在不修改类A代码的情况下,在调用类A的方法时... 查看详情

41vue使用第三方动画库(代码片段)

...库Animate.css。它里边有很多已经写好的动画,可以给开发带来极大的方便。在学习第三方动画库使用前,我们要先学会一个知识,就是自定义Vue中固定的CSS动画选择器。自定义动画选择<htmllang="en"><head&g... 查看详情

推荐11个好用的js动画库(代码片段)

...验。该库于2010年4月首次推出,目前仍有近1000名贡献者在开发中。2.An 查看详情

velocity.js动画库使用

1、简介Velocity是一个简单易用、高性能、功能丰富的轻量级JS动画库。它能和jQuery完美协作,并和$.animate()有相同的API,但它不依赖jQuery,可单独使用。  2、兼容性可兼容到IE8和Android2.3。若需要兼容IE8,就必须引入jQuery1.x... 查看详情

text3d视差动画库(代码片段)

查看详情

flutter开发从0到1源码(代码片段)

Flutter开发从0到1明天开始又要上班了,你的假期任务完成如何啊?由于平时加班太多了,实在挤不出更多时间,从开始想用Flutter《Flutter开发从0到1(一)需求与准备》写一个完整的APP已经过去三个月了ÿ... 查看详情

学习vue第十一节,使用使用其他动画库完成vue动画效果(代码片段)

https://animate.style/  animate.css<!DOCTYPEhtml><html><head><metacharset="utf-8"><title></title><scriptsrc="lib/vue-2.4.0.js"type="text/javascript"cha 查看详情

kissui.scrollanim页面滚动动画库插件(代码片段)

简介kissui.scrollanim是一款实用的纯JS和CSS3页面滚动动画库插件。通过该插件可以使元素进入浏览器视口的时候,展示指定的CSS3动画效果。下载地址及演示在线演示在线下载安装可以通过bower来安装kissui.scrollanim插件。bowerinstallkissu... 查看详情

lottie,一个强大的移动端动画库

Lottie是一个针对移动端打造的动画库,其内部根据json解析出的路径在onDraw下绘制多个图层的每一帧动画实现高效流畅的效果,本文简单介绍其使用,以帮助读者更好的理解。通过Gradle添加依赖dependenciescompile'com.airb... 查看详情

用javascript实现三次贝塞尔动画库-前端组件化(代码片段)

这期我们来完善上一期的动画库。在Animation类中的constructor的参数,我们发现其他的参数都用上了。但是timingFunction我们是还没有使用上的。这里我们就来一起处理这个问题。timingFucntion这个逻辑主要是用在Animation的run方法中... 查看详情

qt炫酷动画6.qeasingcurve缓动曲线类(代码片段)

QEasingCurve描述缓动曲线描述了一个函数,该函数控制0和1之间的插值速度应该如何。缓动曲线允许从一个值到另一个值的过渡看起来比简单的恒定速度所允许的更自然。QEasingCurve类通常与QVariantAnimation和QPropertyAnimation类一起使用... 查看详情

qt炫酷动画6.qeasingcurve缓动曲线类(代码片段)

QEasingCurve描述缓动曲线描述了一个函数,该函数控制0和1之间的插值速度应该如何。缓动曲线允许从一个值到另一个值的过渡看起来比简单的恒定速度所允许的更自然。QEasingCurve类通常与QVariantAnimation和QPropertyAnimation类一起使用... 查看详情

facebookrebound弹性动画库源码分析

...验一下rebound的效果,又懒得clone和编译代码的,这里提供一个demoapk。今天看到了tumblr发布了基于rebound的Backboard,本想直接分析一下Backboard对rebound做了些什么,不过考虑到rebound还没有仔细分析过,所以这里 查看详情

three.js案例从0到1创建组合对象并让它们动起来(代码片段)

...创建对象,创建材质,并建立几何形状//首先定义一个大海对象Sea=function()//创建一个圆柱几何体//参数为:顶面半径,底面半径,高度,半径分段,高度分段vargeom=newTHREE.CylinderGeometry(600,600,800,40,10... 查看详情

一个人开发一个产品,小程序从0到1,第4章页面文件(代码片段)

一个小程序应用,由index,logs等多个页面组成。一个页面,包含4个同名,不同后缀的文件,它们分别是负责配置的json,布局的wxml,样式的wxss和业务逻辑的js。其中,wxml和js是不可删除,是必须有的文件。4.1index.json每一个小程... 查看详情

一个人开发一个产品,小程序从0到1,第3章应用文件(代码片段)

一个小程序项目,在根目录下会有3个应用文件,一个是全局业务逻辑文件app.js,一个是公共配置文件app.json,还有一个是公共样式表文件app.wxss。在这3个文件中,app.js和app.json是不可删除,是必须有的文件。3.1app.js打开文件的那... 查看详情

从0到1实现一个android路由——初探路由(代码片段)

从0到1实现一个Android路由系列文章从0到1实现一个Android路由(1)——初探路由从0到1实现一个Android路由(2)——URL解析器从0到1实现一个Android路由(3)——APT收集路由从0到1实现一个Android路由(4)——多模块的APT收集路由从0到1实现一个A... 查看详情

一个人开发一个产品,小程序从0到1,第6章常量变量(代码片段)

程序总得要处理数据,处理数据就要用到内存,至于内存的大小和地址,由变量指定。变量,就是在程序运行过程中它的值是允许改变的量。常量被视为与常规变量一样,不同的是常量的值在定义之后就不能进行改变。6.1常量在... 查看详情