一项新技术新的技术方案的提出,一定是为了解决某个问题的,或者是对某种方案的优化,比如window.requestAnimationFrame
这个api
...
requestAnimationFrame官方介绍
requestAnimationFrame用处概述
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行...
官方文档对应截图
官方文档:developer.mozilla.org/zh-CN/docs/…
大致看了以后,我们可以知道:
requestAnimationFrame
这个api
主要是用来做动画的。
requestAnimationFrame
这个api
主要是用来做动画的。
requestAnimationFrame
这个api
主要是用来做动画的。
其实顾名思义,我们翻译这个英文单词,也能大致明白。request(请求)Animation(动画)Frame(帧)
关于前端动画的两个问题:
1.前端动画方案有哪些?
2.为何偏偏要使用这个新的api来做动画(或者说这个api较之前做动画的方式优点有哪些)?
1.前端动画方案有哪些?
主要分类为css动画
和js动画
,如下细分:
css
动画
js
动画
setInterval
或setTimeout
定时器(比如不停地更改dom元素
的位置,使其运动起来)
canvas
动画,搭配js
中的定时器去运动起来(canvas
只是一个画笔,然后我们通过定时器会使用这个画笔去画画-动画)
requestAnimationFrame动画(js动画中的较好方案)
另有svg动画标签
,不过工作中这种方式是比较少的,这里不赘述
2.为何偏偏要使用这个新的api来做动画(或者说这个api较之前做动画的方式优点有哪些)?
在工作中,做动画最优的方案无疑是css动画
,但是某些特定场景下,css动画
无法实现我们所需要的需求,此时,我们就要考虑使用js去做动画了
canvas动画
的本质
也是定时器动画
使用定时器动画干活,实际上是可以的,但是存在一个最大的问题,就是动画会抖动
、动画会抖动
、动画会抖动
,体验效果不是非常好。
而,使用requestAnimationFrame
去做动画,就不会抖动
、就不会抖动
、就不会抖动
这里笔者写一个demo动画(分别是上述两种方式实现dom元素向右平移)
给大家看一下,就知道具体的区别。我们先看一下效果图:(红色dom
是定时器实现、绿色dom
是requestAnimationFrame
实现)
因为笔者的gif录制软件的问题,看着都有点卡,实际上,大家把下方代码复制一份跑起来看的话,会发现定时器动画在微微颤抖,而requestAnimationFrame
动画却稳如老狗
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>requestAnimationFrame_yyds</title>
<style>
body {
box-sizing: border-box;
background-color: #ccc;
}
.box1,
.box2 {
position: absolute;
width: 160px;
height: 160px;
line-height: 160px;
text-align: center;
color: #fff;
font-size: 13px;
}
.box1 {
top: 40px;
background: red;
}
.box2 {
top: 210px;
background: green;
}
</style>
</style>
</head>
<body>
<button>let's go!</button>
<div>定时器动画</div>
<div>请求动画帧</div>
<script>
// 动画思路:不断修改dom元素的left值,使其运动起来(动画)
let box1 = document.querySelector('.box1')
let box2 = document.querySelector('.box2')
// setInterval定时器方式
function setIntervalFn() {
let timer = null
box1.style.left = '0px'
timer = setInterval(() => {
let leftVal = parseInt(box1.style.left)
if (leftVal >= 720) {
clearInterval(timer)
} else {
box1.style.left = leftVal + 1 + 'px'
}
}, 17)
}
// requestAnimationFrame请求动画帧方式
function requestAnimationFrameFn() {
let timer = null // 可注掉
box2.style.left = '0px'
function callbackFn() {
let leftVal = parseInt(box2.style.left)
if (leftVal >= 720) {
// 不再继续递归调用即可,就不会继续执行了,下面这个加不加都无所谓,因为影响不到
// cancelAnimationFrame取消请求动画帧,用的极少,看下,下文中的回到顶部组件
// 大家会发现并没有使用到这个api(这样写只是和clearInterval做一个对比)
// 毕竟,正常情况下,requestAnimationFrame会自动停下来
cancelAnimationFrame(timer) // 可注掉(很少用到)
} else {
box2.style.left = leftVal + 1 + 'px'
window.requestAnimationFrame(callbackFn)
}
}
window.requestAnimationFrame(callbackFn)
}
// 动画绑定
let btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
setIntervalFn()
requestAnimationFrameFn()
})
</script>
</body>
</html>
Chrome浏览器查看当前帧数命令:1. F12打开控制台
、2. command + shift + p调出输入面板
、3. 在Run输入框中输入:Show frames per second(FPS) meter回车即可
通过上述的例子,我们可以回答这个问题了:
所以在这里,我们还要顺带延伸一下,为什么定时器会卡,而requestAnimationFrame
不会卡。在说这个问题之前,这里再提一下,requestAnimationFrame
的语法规则
requestAnimationFrame的语法规则
一言以蔽之:requestAnimationFrame
和js
中的setTimeout
定时器函数基本一致
,不过setTimeout
可以自由设置间隔时间,而requestAnimationFrame
的间隔时间是由浏览器自身决定的,大约是17毫秒
左右
1.requestAnimationFrame
我们可以在控制台输入window
,然后展开查看其身上的属性,就能找到了,如下图:
2.由上图我们可以看到,requestAnimationFrame
本质上是一个全局window
对象上的一个属性函数,函数是要被执行的,要被调用的。所以我们使时,直接:window.requestAnimationFrame(callBack)
即可。
3.和定时器一样其接收的参数callback
也是一个函数,即下一次重绘之前更新动画帧所调用的函数,即在这个函数体中,我们可以写对应的逻辑代码(和定时器类似)
4.requestAnimationFrame也有返回值,返回值是一个整数,主要是定时器的身份证标识,可以使用 window.cancelAnimationFrame()来取消回调函数执行
,相当于定时器中的clearTimeout()
。
5.二者也都是只执行一次,想要继续执行,做到类似setInterval
的效果,需要写成递归的形式(上述案例中也提到了)
为什么定时器会卡,而requestAnimationFrame
不会卡
为什么定时器会卡
我们在手机或者电脑显示屏上看东西时,显示屏会默默的不停地干活(刷新画面)
这个刷新值得是每秒钟刷新次数,普通显示器的刷新率约为60Hz(每秒刷新60次),高档的有75Hz、90Hz、120Hz、144Hz等等
刷新率次数越高,显示器显示的图像越清晰、越流畅、越丝滑
不刷新就是静态的画面,刷新比较低就是卡了
,PPT
的感觉
动画想要丝滑流畅,需要卡住时间点进行代码操作(代码语句赋值、浏览器重绘)
所以只需要每隔1000毫秒的60分之一(60HZ)即约为17毫秒,进行一次动画操作即可
只要卡住这个17毫秒,每隔17毫秒进行操作,就能确保动画丝滑
但是定时器的回调函数,会受到js的事件队列宏任务、微任务影响,可能设定的是17毫秒执行一次,但是实际上这次是17毫秒、下次21毫秒、再下次13毫秒执行,所以并不是严格的卡住了这个60HZ的时间
没有在合适的时间点操作,就会出现:类似这样的情况:变
、不变
、不变
、变
、不变
...
于是就出现了,绘制不及时的情况,就会有抖动的出现(以上述案例,位置和时间没有线性对应更新变化导致看起来抖动)
js执行代码是很快的,可能不到一毫秒,大家可以使用相应console的api去测试,如下:
console.time()
let box1 = document.querySelector('.box1')
box1.style.left = '100px'
console.timeEnd()
// js执行耗时结果:default: 0.044189453125 ms
为何requestAnimationFrame
不会卡
requestAnimationFrame
能够做到,精准严格的卡住显示器刷新的时间,比如普通显示器60HZ
它会自动对应17ms
执行一次,比如高级显示器120HZ
,它会自动对应9ms
执行一次。
当然requestAnimationFrame
只会执行一次,想要使其多次执行,要写成递归的形式。上述案例也给出了递归写法
至于为何requestAnimationFrame
能够卡住时间,其底层原理又是啥?本文暂且按下不表。
所以,这就是requestAnimationFrame
的好处。
所以,上述内容验证了:一项新技术新的技术方案的提出,一定是为了解决相关的问题的。
所以,window.requestAnimationFrame
这个api
就是解决了定时器不精准的问题的。
这就是其产生的原因。
requestAnimationFrame应用场景举例-回到顶部组件
比如:回到顶部组件,就是使用requestAnimationFrame
实现的。
下面是笔者封装的回到顶部组件效果图和代码
效果图:
也可以去笔者的网站上去看效果哦:ashuai.work:8888/#/myBack
代码:
<template>
<transition name="fade-transform">
<div
v-show="visible"
:style="{
bottom: bottom + 'px',
right: right + 'px',
}"
@click="goToTop"
>
<slot></slot>
</div>
</transition>
</template>
<script>
export default {
name: "myBack",
props: {
bottom: {
type: Number,
default: 72,
},
right: {
type: Number,
default: 72,
},
// 回到顶部出现的滚动高度位置
showHeight: {
type: Number,
default: 240,
},
// 拥有滚动条的那个dom元素的id或者class,用于下方选中操作更改滚动条滚动距离
scrollBarDom: String,
},
data() {
return {
visible: false,
scrollDom: null,
};
},
mounted() {
if (document.querySelector(this.scrollBarDom)) {
this.scrollDom = document.querySelector(this.scrollBarDom);
// 不用给window绑定监听滚动事件,给对应滚动条元素绑定即可
this.scrollDom.addEventListener("scroll", this.isShowGoToTop, true);
}
},
beforeDestroy() {
// 最后要解除监听滚动事件
this.scrollDom.removeEventListener("scroll", this.isShowGoToTop, true);
},
methods: {
isShowGoToTop() {
// 获取滚动的元素,即有滚动条的那个元素
if (this.scrollDom.scrollTop > 20) {
this.visible = true;
} else {
this.visible = false;
}
},
goToTop() {
// 获取滚动的元素,即有滚动条的那个元素
let scrollDom = document.querySelector(this.scrollBarDom);
// 获取垂直滚动的距离,看看滚动了多少了,然后不断地修改滚动距离直至为0
let scrollDistance = scrollDom.scrollTop;
/**
* window.requestAnimationFrame兼容性已经可以了,正常都有的
* */
if (window.requestAnimationFrame) {
let fun = () => {
scrollDom.scrollTop = scrollDistance -= 36;
if (scrollDistance > 0) {
window.requestAnimationFrame(fun); // 只执行一次,想多次执行需要再调用
} else {
scrollDom.scrollTop = 0;
}
};
window.requestAnimationFrame(fun);
return;
}
/**
* 没有requestAnimationFrame的话,就用定时器去更改滚动条距离,使之滚动
* */
let timer2 = setInterval(() => {
scrollDom.scrollTop = scrollDistance -= 36;
if (scrollDistance <= 0) {
clearInterval(timer2);
scrollDom.scrollTop = 0;
}
}, 17);
},
},
};
</script>
<style scoped>
.backWrap {
position: fixed;
cursor: pointer;
width: 42px;
height: 42px;
background: #9cc2e5;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.5s;
}
// 过渡效果
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all 0.36s;
}
.fade-transform-enter {
opacity: 0;
transform: translateY(-5px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateY(5px);
}
</style>
GitHub仓库地址:github.com/shuirongshu…
另外,有一个做滚动的插件库,叫做vue-seamless-scroll
其内部实现原理也是基于requestAnimationFrame
实现的。感兴趣的道友可以去看看
类比学习reduce循环解决了forEach循环可能需要一个初始变量的问题
我们类比一下学习,比如既然有了forEach
循环,为啥还又新推出一个reduce
循环呢?
原因:某些场景下,reduce循环解决了forEach循环还需要再定义一个变量的问题。
似曾相识的感觉...
比如我们有一个需求,给一个数组求和。
forEach写法
let arr = [1, 3, 5, 7, 9]
function forEachFn(params) {
let total = 0
params.forEach((num) => {
total = total + num
})
return total
}
let res1 = forEachFn(arr)
console.log(res1);
reduce写法
let arr = [1, 3, 5, 7, 9]
function reduceFn(params) {
return params.reduce((temp, num) => {
temp = temp + num
return temp
}, 0)
}
let res2 = reduceFn(arr)
console.log(res2);
通过上述两段代码,我们可以看到,reduce
函数比forEach
少写了一个total
变量,千万不要小看这少写的东西,某些情况下,会节省很多的工作量呢!
作者:水冗水孚
链接:https://juejin.cn/post/7190728064458817591
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
该文章在 2024/8/16 10:26:10 编辑过