js中动画与特效的实现原理

软件技术前端应用开发就业前景
软件前端开发招工
软件前端开发的待遇
软件开发前端后端

一、JavaScript中的动画原理
动画效果可以通过两种方式来实现:一种是通过JavaScript间接的操作css,每隔几秒执行一次,另外一种是利用纯css实现,该方法在css3成熟后广泛应用.这里主要将js里面的动画:

  1. JavaScript动画用的最多的是setInterval()、setTimeout()和requestAnimationFrame():
  2. 1 setTimeout()和setInterval ()主要是自身会执行动画效果,它们在里面放入function和时间参数,然后既可以设置事件;
  3. 2 requestAnimationFrame(回调函数):像setTimeout、setInterval一样,requestAnimationFrame是一个全局函数。调用requestAnimationFrame后,它会要求浏览器根据自己的频率进行一次重绘,它接收一个回调函数作为参数,在即将开始的浏览器重绘时,会调用这个函数,并会给这个函数传入调用回调函数时的时间作为参数。由于requestAnimationFrame的功效只是一次性的,所以若想达到动画效果,则必须连续不断的调用requestAnimationFrame,就像我们使用setTimeout来实现动画所做的那样。
    requestAnimationFrame函数会返回一个资源标识符,可以把它作为参数传入cancelAnimationFrame函数来取消.requestAnimationFrame的回调,跟setTimeout的clearTimeout很相似。 可以这么说,requestAnimationFrame其实就是setTimeout的性能增强版。
    javascript 代码
<button id=”btn”>清除</button>
<script>
    var id
    var time = new Date()
    requestAnimationFrame(function step() {
        console.log(new Date() – time)
        time = new Date()
        id = requestAnimationFrame(step)
    })
    btn.onclick = function() {
        cancelAnimationFrame(id)
    }
</script>

1.3简单动画的问题
1.3.1 setTimeout和setInterval的深入理解
setTimeout():如下面这段代码,输出结果其实是1 2 3,而不是 1 3 2,因为setTimeout()其实在执行的时候会先存储这个结果但不会立即输出(即使时间间隔是0也这样)而是等待页面加载完成后再输出结果
javascript 代码

console.log(‘1’)
setTimeout(function() {
    console.log(‘3’)
}, 0)
console.log(‘2’)
//输出结果是什么?
//1 2 3
function fn() {
    setTimeout(function() {
        console.log(‘can you see me?’)
    }, 1000)
    while (true) {}
}
//输出结果是什么? 不输出结果

1.3.2 简单动画的变慢问题
当setTimeout、setInterval甚至是requestAnimationFrame()在循环里面要做很长的处理时,就会出现动画时间变慢的结果,使它本该在固定时间内结束而结果却是不尽人意的延迟
实例1:让滑块自动向右移动
javascript 代码

var left = 0
var id = setInterval(function() {
    left += 10
    box.style.left = left + ‘px’
    if (left >= 1000) {
        clearInterval(id)
    }
    for (var i = 0; i < 10000; i++) {
        console.log(‘a’)
    }
}, 20)

实例2:
javascript 代码

function step() {
    var temp = div.offsetLeft + 2
    div.style.left = temp + ‘px’
    window.requestAnimationFrame(step)
    for (var i = 0; i < 50000; i++) {
        console.log(‘再牛逼的定时器也得等到我执行完才能执行’)
    }
}
window.requestAnimationFrame(step)

1.4 使用动画的正确姿势
其实是 “位移”关于“时间”的函数:s=f(t)
动画变慢的结果其实是采用增量的方式来执行了动画,为了更精确的控制动画,更合适的方法是将动画与时间关联起来
javascript 代码

var box = document.querySelector(‘div’)
var dis = 1000
var duration = 5000
var statTime = new Date()
requestAnimationFrame(function step() {
    var time = new Date() – statTime
    time = time >= duration ? duration : time
    box.style.left = dis * (time / duration) + ‘px’
    if (time >= duration) {
        return
    }
    requestAnimationFrame(step)
    for (var i = 0; i < 100000; i++) console.log(‘a’)
})

动画通常情况下有终止时间,如果是循环动画,我们也可以看做特殊的——当动画达到终止时间之后,重新开始动画。因此,我们可以将动画时间归一(Normalize)表示:
javascript 代码

//duration 是动画执行时间 isLoop是否为循环执行。
function startAnimation(duration, isLoop) {
    var startTime = Date.now()
    requestAnimationFrame(function change() {
        // 动画已经用去的时间占总时间的比值
        var p = (Date.now() – startTime) / duration
        if (p >= 1.0) {
            if (isLoop) {
                // 如果是循环执行,则开启下一个循环周期。并且把开始时间改成上个周期的结束时间
                startTime += duration
                p -= 1.0 //动画进度初始化
            } else {
                p = 1.0 //如果不是循环,则把时间进度至为 1.0 表示动画执行结束
            }
        }
        console.log(‘动画已执行进度’, p)
        if (p < 1.0) {
            //如果小于1.0表示动画还诶有值完毕,继续执行动画。
            requestAnimationFrame(change)
        }
    })
}

示例1:用时间控制动画周期精确到2s中
javascript 代码

block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        duration = 2000
    setInterval(function() {
        var p = (Date.now() – startTime) / duration
        // 时间已经完成了2000的比例,则360度也是进行了这么个比例。
        self.style.transform = ‘rotate(‘ + 360 * p + ‘deg)’
    }, 100)
})

示例2:让滑块在2秒内向右匀速移动600px
javascript 代码

block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        distance = 600,
        duration = 2000
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        self.style.transform = ‘translateX(‘ + distance * p + ‘px)’
        if (p < 1.0) {
            requestAnimationFrame(step)
        }
    })
})

二、常见动画效果实现
2.1 匀速水平运动
用时间来控制进度 s=S∗p
2.2 匀加速(减速)运动
1)加速度恒定,速度从0开始随时间增加而均匀增加。
2)匀加速公式:大写S:要移动的总距离 p:归一化的时间进度 s=S∗p*p
javascript 代码

// 2s中内匀加速运动2000px
block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        distance = 1000,
        duration = 2000
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        self.style.transform = ‘translateX(‘ + distance * p * p + ‘px)’
        if (p < 1.0) requestAnimationFrame(step)
    })
})

3)匀减速运动公式:s=S∗p∗(2−p)
javascript 代码

//2s中使用速度从最大匀减速到0运动1000px
block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        distance = 1000,
        duration = 2000
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        self.style.transform = ‘translateX(‘ + distance * p * (2 – p) + ‘px)’
        if (p < 1.0) requestAnimationFrame(step)
    })
})

2.3 水平抛物运动
匀速水平运动和自由落体运动的组合。
javascript 代码

block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        disX = 1000,
        disY = 1000,
        duration = Math.sqrt((2 * disY) / 10 / 9.8) * 1000 // 落到地面需要的时间 单位ms
    //假设10px是1米,disY = 100米
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        var tx = disX * p //水平方向是匀速运动
        var ty = disY * p * p //垂直方向是匀加速运动
        self.style.transform = ‘translate(‘ + tx + ‘px’ + ‘,’ + ty + ‘px)’
        if (p < 1.0) requestAnimationFrame(step)
    })
})

2.4 正弦曲线运动
正弦运动:x方向匀速,垂直方向是时间t的正弦函数
javascript 代码

block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        distance = 800,
        duration = 2000
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        var ty = distance * Math.sin(2 * Math.PI * p)
        var tx = 2 * distance * p
        self.style.transform = ‘translate(‘ + tx + ‘px,’ + ty + ‘px)’
        if (p < 1.0) requestAnimationFrame(step)
    })
})

2.5 圆周运动
圆周运动公式:x=R.sin(2∗π∗p),y=R.cos(2∗π∗p)
javascript 代码

block.addEventListener(‘click’, function() {
    var self = this,
        startTime = Date.now(),
        r = 100,
        duration = 2000
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() – startTime) / duration)
        var tx = r * Math.sin(2 * Math.PI * p),
            ty = -r * Math.cos(2 * Math.PI * p)
        self.style.transform = ‘translate(‘ + tx + ‘px,’ + ty + ‘px)’
        requestAnimationFrame(step)
    })
})

三、动画算子(easing)
对于一些比较复杂的变化,算法也比较复杂,就要用到动画算子。动画算子 是一个函数,可以把进度转化成另外一个值。其实也就是一种算法。
我们总结一下上面的各类动画,发现它们是非常相似的,匀速运动、匀加速运动、匀减速运动、圆周运动唯一的区别仅仅在于位移方程:
1.匀速运动:s=S∗p
2.匀加速运动:s=S∗p*p
3.匀减速运动:s=S∗p∗(2−p)
4.圆周运动x轴:x=R∗sin(2∗PI∗p)
5.圆周运动y轴:y=R∗cos(2∗PI∗p)
我们把共同的部分 S 或R 去掉,得到一个关于 p 的方程 ,这个方程我们称为动画的算子(easing),它决定了动画的性质。

1.匀速算子:e=p
2.匀加速算子:e = p*p=p^2
3.匀减速算子:e=p∗(2−p)
4.圆周算子x轴:e=sin(2∗PI∗p)
5.圆周算子y轴:e=cos(2∗PI∗p)

一些常用的动画算子
javascript 代码

//easing.js库封装
var pow = Math.pow,
    BACK_CONST = 1.70158
// t指的的是动画进度 归一化的时间 前面的p
Easing = {
    // 匀速运动
    linear: function(t) {
        return t
    },
    // 匀加速运动
    easeIn: function(t) {
        return t * t
    },
    // 减速运动
    easeOut: function(t) {
        return (2 – t) * t
    },
    //先加速后减速
    easeBoth: function(t) {
        return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 – –t * (t – 2))
    },
    // 4次方加速
    easeInStrong: function(t) {
        return t * t * t * t
    },
    // 4次方法的减速
    easeOutStrong: function(t) {
        return 1 – –t * t * t * t
    },
    // 先加速后减速,加速和减速的都比较剧烈
    easeBothStrong: function(t) {
        return (t *= 2) < 1
            ? 0.5 * t * t * t * t
            : 0.5 * (2 – (t -= 2) * t * t * t)
    },
    //
    easeOutQuart: function(t) {
        return -(Math.pow(t – 1, 4) – 1)
    },
    // 指数变化 加减速
    easeInOutExpo: function(t) {
        if (t === 0) return 0
        if (t === 1) return 1
        if ((t /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * (t – 1))
        return 0.5 * (-Math.pow(2, -10 * –t) + 2)
    },
    //指数式减速
    easeOutExpo: function(t) {
        return t === 1 ? 1 : -Math.pow(2, -10 * t) + 1
    },
    // 先回弹,再加速
    swingFrom: function(t) {
        return t * t * ((BACK_CONST + 1) * t – BACK_CONST)
    },
    // 多走一段,再慢慢的回弹
    swingTo: function(t) {
        return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1
    },
    //弹跳
    bounce: function(t) {
        var s = 7.5625,
            r
        if (t < 1 / 2.75) {
            r = s * t * t
        } else if (t < 2 / 2.75) {
            r = s * (t -= 1.5 / 2.75) * t + 0.75
        } else if (t < 2.5 / 2.75) {
            r = s * (t -= 2.25 / 2.75) * t + 0.9375
        } else {
            r = s * (t -= 2.625 / 2.75) * t + 0.984375
        }
        return r
    },
}

四、使用面向对象封装动画
为了实现更加复杂的动画,我们可以将动画进行 简易 的封装,要进行封装,我们先要抽象出动画相关的要素:

动画生命周期:开始、进程中、结束
javascript 代码

/*my_animator01.js框架
参数1:动画的执行时间
参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
参数3:动画执行的时候的回调函数(动画执行的要干的事情)
*/
function Animator(duration, easing, doSomething) {
    this.duration = duration;
    this.easing = easing;
    this.doSomething = doSomething;
}
Animator.prototype = {
/*开始动画的方法,
参数:一个布尔值
true表示动画不循环执行。
*/
start: function (count) { // 参数表示动画播放的周期的个数
    if (count 0) {//如果还有周期未结束则继续执行
        startTime = new Date();
    }else {
        return;//完成,动画结束
    }
}
self.id = requestAnimationFrame(step);//执行下一帧动画
});
},
stop: function () {//动画结束
    cancelAnimationFrame(this.id);
}
}

初步修改版:
javascript 代码

function Animator(durations, easings, callbacks) {
    this._init(durations, easings, callbacks);
}
Animator.prototype = {
_init: function (durations, easings, callbacks){
this.durations= durations;
        this.easings = easings;
        this.callbacks = callbacks;
        },
        start: function (count){
if (!count ||typeof count !=”number”|| count 0){
startTime = new Date();
        index = 0;
        }else{
return ;
        }
        }
        }
        that.id = requestAnimationFrame(step);
        });
        },
        stop: function (){
cancelAnimationFrame(this.id);
        }
        }

终极版:
javascript 代码

function Animator(opts) {
    this._init(opts)
}
Animator.prototype = {
    _init: function(opts) {
        this.opts = opts
    },
    start: function(count) {
        if (!count || count <= 0 || typeof count != ‘number’) return
        var opts = this.opts
        var startTime = new Date()
        var index = 0,
            that = this
        this.id = requestAnimationFrame(function step() {
            var p = Math.min(1, (new Date() – startTime) / opts[index].duration)
            var e = opts[index].easing(p)
            opts[index].callback(e)
            if (p == 1) {
                index++
                if (index < opts.length) {
                    startTime = new Date()
                } else {
                    count–
                    if (count > 0) {
                        startTime = new Date()
                        index = 0
                    } else {
                        return
                    }
                }
            }
            that.id = requestAnimationFrame(step)
        })
    },
    stop: function() {
        cancelAnimationFrame(this.id)
    },
}

五、逐帧动画
有时候,我们不但要支持元素的运动,还需要改变元素的外观,比如飞翔的小鸟需要扇动翅膀,这类动画我们可以用逐帧动画来实现:
html 代码

<!DOCTYPE html>
<html lang=”en”>
    <head>
        <meta charset=”UTF-8″ />
        <title>Title</title>
        <style>
            div {
                width: 250px;
                height: 142px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div><img src=”bird.png” alt=”” /></div>
        <script src=”easing.js”></script>
        <script src=”my_animator03.js”></script>
        <script>
            var img = document.querySelector(‘img’)
            var x = 0 //初始化img位置
            var y = 0
            //原地的移动
            setInterval(function() {
                img.style.transform =
                    ‘translate(‘ +
                    -244 * (x % 4) +
                    ‘px,’ +
                    -146 * (y % 2) +
                    ‘px)’
                x++ //先从上面图片走,走完在从下面回来
                if (x % 4 == 0) {
                    y++
                }
            }, 100)
            /*
var animator=new Animator(5000,Easing.linear,function (e) {
img.parentNode.style.transform=”translate(“+document.body.offsetWidth*e+”px,”+(-142*(y%2))+”px)”;
})
animator.start(Number.POSITIVE_INFINITY);*/
            //水平垂直的移动
            new Animator([
                {
                    duration: 5000,
                    easing: Easing.linear,
                    callback: function(e) {
                        img.parentNode.style.transform =
                            ‘translate(‘ +
                            (document.body.offsetWidth – 200) * e +
                            ‘px,’ +
                            600 * e +
                            ‘px)’ +
                            ‘ rotate(0deg)’
                    },
                },
                {
                    duration: 5000,
                    easing: Easing.linear,
                    callback: function(e) {
                        img.parentNode.style.transform =
                            ‘translate(‘ +
                            (document.body.offsetWidth – 200) * (1 – e) +
                            ‘px,’ +
                            600 * (1 – e) +
                            ‘px)’ +
                            ‘ rotateY(180deg)’ //到达右下角反转
                    },
                },
            ]).start(Number.POSITIVE_INFINITY)
            /*var x=0;
setInterval(function () {
x+=10;
img.parentNode.style.transform=”translate(“+document.body.offsetWidth*e+”px,”+(-142*(y%2))+”px)”;
},20)*/
        </script>
    </body>
</html>
前端页面开发软件
超图软件前端开发面试
dw前端开发软件下载
前端开发专业软件
» 本文来自:前端开发者 » 《js中动画与特效的实现原理》
» 本文链接地址:https://www.rokub.com/6697.html
» 您也可以订阅本站:https://www.rokub.com
赞(0)
64K

评论 抢沙发

评论前必须登录!