一項(xiàng)新技術(shù)新的技術(shù)方案的提出,一定是為了解決某個(gè)問(wèn)題的,或者是對(duì)某種方案的優(yōu)化,比如window.requestAnimationFrame
這個(gè)api
...
requestAnimationFrame官方介紹
requestAnimationFrame用處概述
window.requestAnimationFrame()
告訴瀏覽器——你希望執(zhí)行一個(gè)動(dòng)畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行...
官方文檔對(duì)應(yīng)截圖
官方文檔:developer.mozilla.org/zh-CN/docs/…
大致看了以后,我們可以知道:
requestAnimationFrame
這個(gè)api
主要是用來(lái)做動(dòng)畫的。
requestAnimationFrame
這個(gè)api
主要是用來(lái)做動(dòng)畫的。
requestAnimationFrame
這個(gè)api
主要是用來(lái)做動(dòng)畫的。
其實(shí)顧名思義,我們翻譯這個(gè)英文單詞,也能大致明白。request(請(qǐng)求)Animation(動(dòng)畫)Frame(幀)
關(guān)于前端動(dòng)畫的兩個(gè)問(wèn)題:
1.前端動(dòng)畫方案有哪些?
2.為何偏偏要使用這個(gè)新的api來(lái)做動(dòng)畫(或者說(shuō)這個(gè)api較之前做動(dòng)畫的方式優(yōu)點(diǎn)有哪些)?
1.前端動(dòng)畫方案有哪些?
主要分類為css動(dòng)畫
和js動(dòng)畫
,如下細(xì)分:
css
動(dòng)畫
js
動(dòng)畫
setInterval
或setTimeout
定時(shí)器(比如不停地更改dom元素
的位置,使其運(yùn)動(dòng)起來(lái))
canvas
動(dòng)畫,搭配js
中的定時(shí)器去運(yùn)動(dòng)起來(lái)(canvas
只是一個(gè)畫筆,然后我們通過(guò)定時(shí)器會(huì)使用這個(gè)畫筆去畫畫-動(dòng)畫)
requestAnimationFrame動(dòng)畫(js動(dòng)畫中的較好方案)
另有svg動(dòng)畫標(biāo)簽
,不過(guò)工作中這種方式是比較少的,這里不贅述
2.為何偏偏要使用這個(gè)新的api來(lái)做動(dòng)畫(或者說(shuō)這個(gè)api較之前做動(dòng)畫的方式優(yōu)點(diǎn)有哪些)?
在工作中,做動(dòng)畫最優(yōu)的方案無(wú)疑是css動(dòng)畫
,但是某些特定場(chǎng)景下,css動(dòng)畫
無(wú)法實(shí)現(xiàn)我們所需要的需求,此時(shí),我們就要考慮使用js去做動(dòng)畫了
canvas動(dòng)畫
的本質(zhì)
也是定時(shí)器動(dòng)畫
使用定時(shí)器動(dòng)畫干活,實(shí)際上是可以的,但是存在一個(gè)最大的問(wèn)題,就是動(dòng)畫會(huì)抖動(dòng)
、動(dòng)畫會(huì)抖動(dòng)
、動(dòng)畫會(huì)抖動(dòng)
,體驗(yàn)效果不是非常好。
而,使用requestAnimationFrame
去做動(dòng)畫,就不會(huì)抖動(dòng)
、就不會(huì)抖動(dòng)
、就不會(huì)抖動(dòng)
這里筆者寫一個(gè)demo動(dòng)畫(分別是上述兩種方式實(shí)現(xiàn)dom元素向右平移)
給大家看一下,就知道具體的區(qū)別。我們先看一下效果圖:(紅色dom
是定時(shí)器實(shí)現(xiàn)、綠色dom
是requestAnimationFrame
實(shí)現(xiàn))
因?yàn)楣P者的gif錄制軟件的問(wèn)題,看著都有點(diǎn)卡,實(shí)際上,大家把下方代碼復(fù)制一份跑起來(lái)看的話,會(huì)發(fā)現(xiàn)定時(shí)器動(dòng)畫在微微顫抖,而requestAnimationFrame
動(dòng)畫卻穩(wěn)如老狗
<!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>定時(shí)器動(dòng)畫</div>
<div>請(qǐng)求動(dòng)畫幀</div>
<script>
// 動(dòng)畫思路:不斷修改dom元素的left值,使其運(yùn)動(dòng)起來(lái)(動(dòng)畫)
let box1 = document.querySelector('.box1')
let box2 = document.querySelector('.box2')
// setInterval定時(shí)器方式
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請(qǐng)求動(dòng)畫幀方式
function requestAnimationFrameFn() {
let timer = null // 可注掉
box2.style.left = '0px'
function callbackFn() {
let leftVal = parseInt(box2.style.left)
if (leftVal >= 720) {
// 不再繼續(xù)遞歸調(diào)用即可,就不會(huì)繼續(xù)執(zhí)行了,下面這個(gè)加不加都無(wú)所謂,因?yàn)橛绊懖坏?/span>
// cancelAnimationFrame取消請(qǐng)求動(dòng)畫幀,用的極少,看下,下文中的回到頂部組件
// 大家會(huì)發(fā)現(xiàn)并沒(méi)有使用到這個(gè)api(這樣寫只是和clearInterval做一個(gè)對(duì)比)
// 畢竟,正常情況下,requestAnimationFrame會(huì)自動(dòng)停下來(lái)
cancelAnimationFrame(timer) // 可注掉(很少用到)
} else {
box2.style.left = leftVal + 1 + 'px'
window.requestAnimationFrame(callbackFn)
}
}
window.requestAnimationFrame(callbackFn)
}
// 動(dòng)畫綁定
let btn = document.querySelector('.btn')
btn.addEventListener('click', () => {
setIntervalFn()
requestAnimationFrameFn()
})
</script>
</body>
</html>
Chrome瀏覽器查看當(dāng)前幀數(shù)命令:1. F12打開(kāi)控制臺(tái)
、2. command + shift + p調(diào)出輸入面板
、3. 在Run輸入框中輸入:Show frames per second(FPS) meter回車即可
通過(guò)上述的例子,我們可以回答這個(gè)問(wèn)題了:
所以在這里,我們還要順帶延伸一下,為什么定時(shí)器會(huì)卡,而requestAnimationFrame
不會(huì)卡。在說(shuō)這個(gè)問(wèn)題之前,這里再提一下,requestAnimationFrame
的語(yǔ)法規(guī)則
requestAnimationFrame的語(yǔ)法規(guī)則
一言以蔽之:requestAnimationFrame
和js
中的setTimeout
定時(shí)器函數(shù)基本一致
,不過(guò)setTimeout
可以自由設(shè)置間隔時(shí)間,而requestAnimationFrame
的間隔時(shí)間是由瀏覽器自身決定的,大約是17毫秒
左右
1.requestAnimationFrame
我們可以在控制臺(tái)輸入window
,然后展開(kāi)查看其身上的屬性,就能找到了,如下圖:
2.由上圖我們可以看到,requestAnimationFrame
本質(zhì)上是一個(gè)全局window
對(duì)象上的一個(gè)屬性函數(shù),函數(shù)是要被執(zhí)行的,要被調(diào)用的。所以我們使時(shí),直接:window.requestAnimationFrame(callBack)
即可。
3.和定時(shí)器一樣其接收的參數(shù)callback
也是一個(gè)函數(shù),即下一次重繪之前更新動(dòng)畫幀所調(diào)用的函數(shù),即在這個(gè)函數(shù)體中,我們可以寫對(duì)應(yīng)的邏輯代碼(和定時(shí)器類似)
4.requestAnimationFrame也有返回值,返回值是一個(gè)整數(shù),主要是定時(shí)器的身份證標(biāo)識(shí),可以使用 window.cancelAnimationFrame()來(lái)取消回調(diào)函數(shù)執(zhí)行
,相當(dāng)于定時(shí)器中的clearTimeout()
。
5.二者也都是只執(zhí)行一次,想要繼續(xù)執(zhí)行,做到類似setInterval
的效果,需要寫成遞歸的形式(上述案例中也提到了)
為什么定時(shí)器會(huì)卡,而requestAnimationFrame
不會(huì)卡
為什么定時(shí)器會(huì)卡
我們?cè)谑謾C(jī)或者電腦顯示屏上看東西時(shí),顯示屏?xí)牟煌5馗苫睿ㄋ⑿庐嬅妫?/p>
這個(gè)刷新值得是每秒鐘刷新次數(shù),普通顯示器的刷新率約為60Hz(每秒刷新60次),高檔的有75Hz、90Hz、120Hz、144Hz等等
刷新率次數(shù)越高,顯示器顯示的圖像越清晰、越流暢、越絲滑
不刷新就是靜態(tài)的畫面,刷新比較低就是卡了
,PPT
的感覺(jué)
動(dòng)畫想要絲滑流暢,需要卡住時(shí)間點(diǎn)進(jìn)行代碼操作(代碼語(yǔ)句賦值、瀏覽器重繪)
所以只需要每隔1000毫秒的60分之一(60HZ)即約為17毫秒,進(jìn)行一次動(dòng)畫操作即可
只要卡住這個(gè)17毫秒,每隔17毫秒進(jìn)行操作,就能確保動(dòng)畫絲滑
但是定時(shí)器的回調(diào)函數(shù),會(huì)受到j(luò)s的事件隊(duì)列宏任務(wù)、微任務(wù)影響,可能設(shè)定的是17毫秒執(zhí)行一次,但是實(shí)際上這次是17毫秒、下次21毫秒、再下次13毫秒執(zhí)行,所以并不是嚴(yán)格的卡住了這個(gè)60HZ的時(shí)間
沒(méi)有在合適的時(shí)間點(diǎn)操作,就會(huì)出現(xiàn):類似這樣的情況:變
、不變
、不變
、變
、不變
...
于是就出現(xiàn)了,繪制不及時(shí)的情況,就會(huì)有抖動(dòng)的出現(xiàn)(以上述案例,位置和時(shí)間沒(méi)有線性對(duì)應(yīng)更新變化導(dǎo)致看起來(lái)抖動(dòng))
js執(zhí)行代碼是很快的,可能不到一毫秒,大家可以使用相應(yīng)console的api去測(cè)試,如下:
console.time()
let box1 = document.querySelector('.box1')
box1.style.left = '100px'
console.timeEnd()
// js執(zhí)行耗時(shí)結(jié)果:default: 0.044189453125 ms
為何requestAnimationFrame
不會(huì)卡
requestAnimationFrame
能夠做到,精準(zhǔn)嚴(yán)格的卡住顯示器刷新的時(shí)間,比如普通顯示器60HZ
它會(huì)自動(dòng)對(duì)應(yīng)17ms
執(zhí)行一次,比如高級(jí)顯示器120HZ
,它會(huì)自動(dòng)對(duì)應(yīng)9ms
執(zhí)行一次。
當(dāng)然requestAnimationFrame
只會(huì)執(zhí)行一次,想要使其多次執(zhí)行,要寫成遞歸的形式。上述案例也給出了遞歸寫法
至于為何requestAnimationFrame
能夠卡住時(shí)間,其底層原理又是啥?本文暫且按下不表。
所以,這就是requestAnimationFrame
的好處。
所以,上述內(nèi)容驗(yàn)證了:一項(xiàng)新技術(shù)新的技術(shù)方案的提出,一定是為了解決相關(guān)的問(wèn)題的。
所以,window.requestAnimationFrame
這個(gè)api
就是解決了定時(shí)器不精準(zhǔn)的問(wèn)題的。
這就是其產(chǎn)生的原因。
requestAnimationFrame應(yīng)用場(chǎng)景舉例-回到頂部組件
比如:回到頂部組件,就是使用requestAnimationFrame
實(shí)現(xiàn)的。
下面是筆者封裝的回到頂部組件效果圖和代碼
效果圖:
也可以去筆者的網(wǎng)站上去看效果哦: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,
},
// 回到頂部出現(xiàn)的滾動(dòng)高度位置
showHeight: {
type: Number,
default: 240,
},
// 擁有滾動(dòng)條的那個(gè)dom元素的id或者class,用于下方選中操作更改滾動(dòng)條滾動(dòng)距離
scrollBarDom: String,
},
data() {
return {
visible: false,
scrollDom: null,
};
},
mounted() {
if (document.querySelector(this.scrollBarDom)) {
this.scrollDom = document.querySelector(this.scrollBarDom);
// 不用給window綁定監(jiān)聽(tīng)滾動(dòng)事件,給對(duì)應(yīng)滾動(dòng)條元素綁定即可
this.scrollDom.addEventListener("scroll", this.isShowGoToTop, true);
}
},
beforeDestroy() {
// 最后要解除監(jiān)聽(tīng)滾動(dòng)事件
this.scrollDom.removeEventListener("scroll", this.isShowGoToTop, true);
},
methods: {
isShowGoToTop() {
// 獲取滾動(dòng)的元素,即有滾動(dòng)條的那個(gè)元素
if (this.scrollDom.scrollTop > 20) {
this.visible = true;
} else {
this.visible = false;
}
},
goToTop() {
// 獲取滾動(dòng)的元素,即有滾動(dòng)條的那個(gè)元素
let scrollDom = document.querySelector(this.scrollBarDom);
// 獲取垂直滾動(dòng)的距離,看看滾動(dòng)了多少了,然后不斷地修改滾動(dòng)距離直至為0
let scrollDistance = scrollDom.scrollTop;
/**
* window.requestAnimationFrame兼容性已經(jīng)可以了,正常都有的
* */
if (window.requestAnimationFrame) {
let fun = () => {
scrollDom.scrollTop = scrollDistance -= 36;
if (scrollDistance > 0) {
window.requestAnimationFrame(fun); // 只執(zhí)行一次,想多次執(zhí)行需要再調(diào)用
} else {
scrollDom.scrollTop = 0;
}
};
window.requestAnimationFrame(fun);
return;
}
/**
* 沒(méi)有requestAnimationFrame的話,就用定時(shí)器去更改滾動(dòng)條距離,使之滾動(dòng)
* */
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;
}
// 過(guò)渡效果
.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倉(cāng)庫(kù)地址:github.com/shuirongshu…
另外,有一個(gè)做滾動(dòng)的插件庫(kù),叫做vue-seamless-scroll
其內(nèi)部實(shí)現(xiàn)原理也是基于requestAnimationFrame
實(shí)現(xiàn)的。感興趣的道友可以去看看
類比學(xué)習(xí)reduce循環(huán)解決了forEach循環(huán)可能需要一個(gè)初始變量的問(wèn)題
我們類比一下學(xué)習(xí),比如既然有了forEach
循環(huán),為啥還又新推出一個(gè)reduce
循環(huán)呢?
原因:某些場(chǎng)景下,reduce循環(huán)解決了forEach循環(huán)還需要再定義一個(gè)變量的問(wèn)題。
似曾相識(shí)的感覺(jué)...
比如我們有一個(gè)需求,給一個(gè)數(shù)組求和。
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);
通過(guò)上述兩段代碼,我們可以看到,reduce
函數(shù)比forEach
少寫了一個(gè)total
變量,千萬(wàn)不要小看這少寫的東西,某些情況下,會(huì)節(jié)省很多的工作量呢!
作者:水冗水孚
鏈接:https://juejin.cn/post/7190728064458817591
來(lái)源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
該文章在 2024/8/16 10:26:10 編輯過(guò)