计时器一直是javascript动画的核心技术,而编写动画循环的关键是要知道延迟时间多长合适,一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化
大多数电脑显示器的刷新频率是60Hz,大概相当于重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳间隔是1000ms/60,约等于16.6ms
而setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其它任务,那动画代码就要等前面的任务完成后再执行
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
requestAnimationFram 介绍
requestAnimationFram()和定时器 setTimeout()、setInterval() 是相关的,因为requestAnimationFrame 是H5新增的API,
是为了解决定时器时间间隔不稳定的问题,
屏刷新频率是 60HZ ==> 也就是每秒60次. ==> 相当于1000毫秒60次 = 16.67ms一次。也就是说每16.67毫秒刷新一次
是浏览器显示的最大刷新频率。我们一般设置16或者17 接近这个频率。
setInterval(()=>{
},17)
requestAnimationFrame 的调用不是由JS来控制的,而且由系统的时间间隔来解决的。用法和setTimeout是类似的。
不一样的是当前的时间间隔是定死的,你不能控制而是有系统控制。
var timer = requestAnimationFrame(()=>{
console.log(timer )
})
cancelAnimationFrame(timer)
requestAnimationFram的兼容性
requestAnimationFrame 是H5新增的特性,遇到不兼容的情况如下解决
if(!window.requestAnimationFrame){
requestAnimationFrame = function(fn){
setTimeout(fn,17)
}
}
requestAnimationFram的应用
- 创建LoadingBar.vue文件
<template>
<div class="wraps">
<div ref="bar" class="bar"></div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
let speed = ref<number>(1);
let bar = ref<HTMLElement>();
let timer = ref<number>(0);
const startLoading = () => {
let dom = bar.value as HTMLElement;
window.requestAnimationFrame(function fn() {
if (speed.value < 90) {
speed.value += 1;
dom.style.width = speed.value + "%";
window.requestAnimationFrame(fn);
} else {
speed.value = 1;
window.cancelAnimationFrame(timer.value);
}
});
};
const endLoading = () => {
let dom = bar.value as HTMLElement;
setTimeout(() => {
window.requestAnimationFrame(() => {
speed.value = 100;
dom.style.width = speed.value + "%";
});
}, 5000);
};
defineExpose({
startLoading,
endLoading,
});
</script>
<style scoped lang="less">
.wraps {
position: fixed;
top: 0;
width: 100%;
height: 2px;
.bar {
height: inherit;
width: 0;
background-color: red;
}
}
</style>
- 修改man.ts 文件
import { createApp, createVNode, render } from 'vue'
import App from './App.vue'
import router from './router'
import LoadingBar from './components/LoadingBar.vue'
const Vnode = createVNode(LoadingBar)
render(Vnode, document.body)
const whileList = ['/']
router.beforeEach((to, from, next) => {
Vnode.component?.exposed?.startLoading()
if (whileList.includes(to.path) || localStorage.getItem('token')) {
next()
} else {
next('/')
}
})
router.afterEach((to, from) => {
Vnode.component?.exposed?.endLoading()
})
createApp(App).use(router).mount('#app')