运动(上)

71 阅读2分钟

运动

概述

运动其实就是指定JavaScript制作的动画,也就是使用定时器控制对应的dom的样式不断的变化,呈现一个运动的状态。(JavaScript制作的动画 其实就是不断的重绘回流,JavaScript制作动画它是使用cpu进行渲染的,渲染效率低(css3的动画使用GPU进行渲染的,渲染效率更高,效果更优))

运动三要素
  • 当前值
  • 目标值
  • 步长 (迭代量)
主要过程
  • 获取当前的值,定义步长
  • 使用定时器不断更改当前值(使用当前值加上步长),更改当前的样式,使用其到达目标值
  • 到达目标值,清除对应的定时器
运动的分类
  • 匀速运动 (步长不变)
  • 缓冲运动 (步长不断变化)
  • 链式运动 (执行完当前动画可以调用下一次动画(链式调用的行为))

匀速运动

主要是步长不变,控制其始终保持一个步长变化

示例(盒子移动)
<div></div>
<script>
	//获取div
    var box = document.querySelector('div')
	//获取当前的定位
    // var current = parseFloat(getStyle(box).left)
    var current = box.offsetLeft
    //目标值
    var target = 100
    //定义步长 步长为正还是负 (如果目标值大于当前值 步长为正 否则为负)
    var step = target > current ? 10 : -10
    //使用定时器不断更改对应的当前值
    var timer = setInterval(()=>{
        //控制当前值的变化
        current += step
        //更改当前样式
        box.style.left = current + 'px'
        //判断当前值是否到达目标值
        if(current == target){
            //清除定时器
            clearInterval(timer)
        }
    },100)
   	//封装对应的获取样式的方法
	function getStyle(element){
        return getComputedStyle ? getComputedStyle(element) : element.currentStyle
    }
</script>
简单的匀速移动封装
//封装一个匀速移动的方法 传入移动的元素 传入对应的目标对象 {left:200,top:200}
export const uniform = function(element, target){
  if (arguments.length < 2) {
    throw new Error('最少需要传入俩个参数')
  }
  //判断当前传入的是否为元素
  if (!(element instanceof HTMLElement)) {
    throw new TypeError(`${element} is not element`)
  }
  //进来的时候先将定时器清除 防止上次定时器影响这次
  clearInterval(element.timer)
  //给元素对象的timer属性赋值
  element.timer = setInterval(() => {
    let flag = true
    //遍历对象 拿出里面的key
    for (let key in target) {
      //获取当前值 
      var current = parseFloat(getStyle(element)[key])
      //获取步长
      var step = target[key] > current ? 1 : -1
      //变化对应的样式
      element.style[key] = current + step +'px'
      //判断是否走完
      if(current + step != target[key]){
        flag = false
      }
    }
    //判断是否走完
    if(flag){
      clearInterval(element.timer)
    }
  },20)
}
//封装对应的获取样式的方法
export function getStyle(element) {
  return getComputedStyle ? getComputedStyle(element) : element.currentStyle
}

缓冲运动

步长不断变小,逐渐到达目标位置的一个运动动画称为缓冲运动。

示例
<div></div>
<script>
	//获取元素
   var div = document.querySelector('div')
   //声明对应的当前值 及 目标值
   var current = parseFloat(getStyle(div).left)
   var target = 100
   //使用定时器不断的设置div的位置
   var timer = setInterval(()=>{
       //获取对应的步长 ( target - current ) / 10 
       //防止小数除不尽  取整操作  大于0向上取整   小于0的向下取整
       var step =  target > current ? Math.ceil(( target - current ) / 10) : Math.floor(( target - current ) / 10)
       //变化对应的当前值
       current += step
       //设置样式
       div.style.left = current + 'px'
       //到达目标位置清除定时器
       if(current == target){
           clearInterval(timer)
       }
   },100)
   //获取样式的方法
   function getStyle(element) {
      return getComputedStyle ? getComputedStyle(element) : element.currentStyle
   }
</script>
缓冲运动封装
//传入对应的需要运动的元素  传入目标对象
export function animation(element, target) {
  if (arguments.length < 2) {
    throw new Error('最少需要传入俩个参数')
  }
  //判断当前传入的是否为元素
  if (!(element instanceof HTMLElement)) {
    throw new TypeError(`${element} is not element`)
  }
  //进来的时候先将定时器清除 防止上次定时器影响这次
  clearInterval(element.timer)
  //给元素对象的timer属性赋值
  element.timer = setInterval(() => {
    let flag = true
    //遍历对象 拿出里面的key
    for (let key in target) {
      //取出当前的样式值 进行转换
      var current = parseFloat(getStyle(element)[key])
      //定义步长
      var step = target[key] > current ? Math.ceil((target[key] - current) / 10) : Math.floor((target[key] - current) / 10)
      //针对传入的目标对象的key进行判断
      //如果是透明度设置
      if(key == "opacity"){
        step = target[key] > current ? Math.ceil((target[key] - current) * 1000 / 10) / 1000 : Math.floor((target[key] - current) * 1000 / 10) / 1000
        element.style[key] = current + step
      }else if(key == "zIndex" || key == "backgroundColor"){
        //直接设置值
        element.style[key] = target[key]
      }else{
        // left top width...
        element.style[key] = current + step  + 'px'
      }
      //判断到达目标位置 排除对应的key里面color的
      if (parseFloat(getStyle(element)[key]) != target[key] && !key.includes('Color')) {
        flag = false
      }
    }
    //判断是否走完
    if (flag) {
      clearInterval(element.timer)
    }
  }, 20)
}

链式调用

在一次动画执行完里面可以开始执行下次一个动画。

解决方案及问题

动画是使用定时器来完成的,它里面的代码是异步的。主要要解决的问题是异步代码同步执行。利用回调函数来解决对应的异步代码同步执行的问题。

示例
animation(div,{
    left:300,
    opacity:0.8
},()=>{
    animation(div,{
        paddingLeft:100,
        opacity:0.5
    })
})