绘制动画,一般有两个选择:
- js脚本 setTimeout和setInterval
- css3 transition和animation属性
这两个方法有各自的局限性:
- js脚本实现动画的局限性:
- 即使向 setTimeout和setInterval 传递ms级的参数,由于js单线程的关系,可能会引发堵塞,不能达到高精度的准确性
- 没有对动画的循环机制进行优化,只是以一个大致的时间间隔循环,而且“最优帧速率”和“选择绘制下一帧的最优时机”都需要开发者自己来控制;ms设置过小,比如小于16.67ms(60帧)那么就会引起掉帧(一般显示器显示最高频率是60帧);设置过大,帧数小,动画效果不流畅
- css3实现动画的局限性
- 兼容性问题,对于PC浏览器,IE8, IE9之流,你想兼容实现某些动画效果,比如说淡入淡出,就无能为力了
- 不能应用所有属性问题,CSS3动画可以改变高宽,方位,角度,透明度等等,但是对于scrollTop,就无能无力了
- CSS3支持的动画效果有限
于是,我们引入requestAnimationFrame
- requestAnimationFrame会把每一帧中的dom操作集中起来,再一次重绘或者重排中就完成,并且刷新频率紧跟浏览器的刷新频率
- 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
- requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
requestAnimationFrame的用法与setTimeout相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行
timerID = requestAnimationFrame(callback);
//控制台输出1和0
var timer = requestAnimationFrame(function(){
console.log(0);
});
console.log(timer);//1
cancelAnimationFrame方法用于取消定时器
//控制台什么都不输出
var timer = requestAnimationFrame(function(){
console.log(0);
});
cancelAnimationFrame(timer);
也可以直接使用返回值进行取消
var timer = requestAnimationFrame(function(){
console.log(0);
});
cancelAnimationFrame(1);
其实我们在《vue 页面加载进度条组件》中使用了setInterval作为动画脚本,现在我们可以用requestAnimationFrame重写(主要是一个递归调用,看mounted和watch中的代码就可以了)
<template>
<transition name="fade">
<div class="progress-bar" v-if="isShow">
</div>
</transition>
</template>
<script type="text/babel">
export default {
data() {
return {
isShow: true, // 是否显示进度条
val: 0, // 进度
}
},
props: {
/**
* 每10毫秒自增幅度
*/
step: {
type: Number,
default: 5,
},
/**
* 初始值
*/
initVal: {
type: Number,
default: 0,
},
/**
* 到一定进度停止
*/
stopVal: {
type: Number,
default: 80,
},
/**
* 进度条继续到成功
*/
isOk: {
type: Boolean,
default: false,
},
},
mounted() {
this.val = this.initVal
let step = this.step
let loop = () => {
this.val = this.val + step
this.$el.style.width = this.val + '%'
if (this.val >= this.stopVal) {
cancelAnimationFrame(timer)
return
}
requestAnimationFrame(loop)
}
let timer = requestAnimationFrame(loop)
},
watch: {
isOk() {
let val = this.val
let step = this.step
let loop = () => {
val = val + step
this.$el.style.width = val + '%'
if (val >= 100) {
cancelAnimationFrame(timer)
this.isShow = false
this.$emit('callback', 'load success')
return
}
requestAnimationFrame(loop)
}
let timer = requestAnimationFrame(loop)
},
},
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.progress-bar {
position fixed
top 0
height 6px
width 0
background-color #999
}
.fade {
&-enter-active, &-leave-active {
transition: all .3s
}
&-enter, &-leave-active {
opacity: 0
}
}
</style>