原生JS实现帧动画


所谓帧动画就是在连续的关键帧中分解动画动作,也就是在时间轴的每一帧上面逐帧绘制出不同的内容,使得其能够连续播放而成为动画

常见的帧动画

  • Gif
  • CSS animation
  • Javascript

前两者实现帧动画存在着不足:

  • 不能灵活控制动画的暂停⏸️和播放
  • 不能对帧动画做更加灵活的扩展
  • Gif不能捕捉到动画完成事件

实现原理

使用JS实现帧动画的原理主要有两个:

  • 多图:借助<imgae>标签承载图片,定时改变src属性( 需发送多个http请求💔)
  • 雪碧图:把关键帧绘制在同一张图片,作为元素的background-image,定时改变元素的background-position属性(推荐🙆)

运动框架实现思路

  1. 速度(left/right/width/height/opacity)
  2. 缓冲运动
  3. 多物体运动(所有的东西都不能公用,如timer和opacity)
  4. 任意值变化
  5. 链式运动
  6. 同时运动

设计通用的帧动画库

  • 支持图片预加载
  • 支持动画播放方式,以及自定义每一帧动画
  • 支持单组动画控制循环次数(可支持无限次)
  • 支持一组动画完成,再进行下一组动画
  • 支持每个动画完成后有等待时间
  • 支持动画暂停和继续播放
  • 支持动画完成后执行回调

任务链: 图像预加载 ➡️ 动画执行 ➡️ 动画结束

任务链有两种类型的任务:同时执行完毕;异步定时执行

getStyle方法——可以获取到某些样式的属性值
currentStyle针对IE浏览器,getComputedStyle针对firefox浏览器

function getStyle(obj, attr){
  if(obj.currenrStyle){
    return obj.currentStyle[attr];
  }
  return getComputedStyle(obj,false)[attr];
}

实现原生js动画的方法

  • DOM对象
  • 目标属性,目标值(json文件形式)
  • 回调函数(可选)

支持传入多个属性值
支持同时运动的动画
支持链式动画(回调)

function startMove(obj, attrs, fn){
  clearInterval(obj.timer);
  obj.timer = setInterval(function(){
    let flag = true; // 是否清空计时器的标志
    for(let a in attrs){
      // 1. 获取当前值
      let iTarget = attrs[a];
      let curStyle = 0;
      if(a === 'opacity'){
        curStyle = Math.round(parseFloat(getStyle(obj, a))*100);
      } else{
        curStyle = parseInt(getStyle(obj, a));
      }
      // 2. 计算速度
      let speed = (iTarget - curStyle) / 8;
      speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
      // 3. 检测停止(只要有属性未达到目标值,就继续运动)
      if(curStyle !== iTarget){
        flag = false;
      }
      if(a === 'opacity'){
        obj.style.filter = 'opacity(' + (curStyle+speed) + ')';
        obj.style.opacity = (curStyle + speed) / 100
      } else {
        obj.style[a] = curStyle + speed + 'px';
      }
    }
    if(flag){
      clearInterval(obj.timer);
      if(fn){
        fn();
      }
    }
  }, 30)
}

设计一个通用的帧动画库

  • loadImage(imglist) //预加载图片
  • changePosition(ele,positions,imageUrl) //通过改变元素的backgroun-position实现动画
  • changeSrc(ele,imglist) //通过改变image元素的src
  • enterFrame(callback) //每一帧动画执行的函数,相当于用户可以自定义,每一帧动画的callback
  • repeat(times) //动画重复执行的次数,times为空时表示无限次
  • repeatForever() //无限重复上一次动画,相当于repeat(),更友好的一个接口吧
  • wait(time) //每个动画执行完成等待的时间
  • then(callback) //动画执行完成后的回调函数
  • start(interval) //动画开始执行,interval表示动画执行的间隔
  • pause() //动画暂停
  • restart() //动画从上一次暂停处重新执行
  • dispose() //释放资源

帧动画类库设计在Animation.js

对比jquery

使用原生的js实现动画需要挂在window.onload()

window.onload = function(){
            var oDiv = document.getElementById('move');
            var aList = oDiv.getElementsByTagName('a');
            for(let i in aList){
                aList[i].onmouseover = function(){
                    var _this = this.getElementsByTagName('i')[0];
                    startMove(_this, {top: -25, opacity: 0 }, function(){
                        _this.style.top = 30 + 'px';
                        startMove(_this, {top: 20, opacity: 100});
                    });
                }
            }
        }

使用jquery也可以实现以上,并且非常方便

$(function(){
            $('#move a').mouseenter(function(){
                $(this).find('i').animate({top:"-25px", opacity: "0"}, 300, function(){
                    $(this).css({top: "30px"});
                    $(this).css({top: "20px", opacity: "1"}, 200)
                })
            })
        })

更多完整的原生的js实现动画的实例放到了个人仓库上 👉 JS Animation


Author: Casey Lu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Casey Lu !
评论
 Previous
有关登录认证机制的探究 有关登录认证机制的探究
从普通的登录认证机制延伸到同域单点登录、跨域单点登录 登录认证机制 普通情形下,浏览器中访问应用时登录,需要我们输入用户名和密码,完成登录认证。这时,我们用户的session信息中标记的登录状态是yes(已登录),同时在浏览器中写入用户的唯
2020-07-16
Next 
Lottie——复杂动画的实现工具 Lottie——复杂动画的实现工具
最近学习动画,了解到Android的Lottie,GitHub源码 常用方法 监听动画进度 lottieAnimationView.addAnimatorUpdateListener(new ValueAnimator.Animato
2020-06-22
  TOC