小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
看到掘金的签到抽奖,就想着如何实现这个动画的交互呢,今天就自己尝试了一下,这个只是一个初级版本的,动画是线性的,所以属于匀速的动画效果。
看一下效果:
准备
主要是使用了 window.requestAnimationFrame 来执行动画。
语法:
window.requestAnimationFrame(callback)
对比setTimeout/setInterval的优势
setTimeout 是以 n 毫秒后执行回调函数,回调函数中可以递归 调用 setTimeout 来实现动画。 setInterval 以 n 毫秒的间隔时间调用回调函数。
动画的实现就是不连续的运动通过每一帧的画面呈现出来的效果。
由于 js 是单线程的,浏览器是基于事件循环机制去执行代码的。setTimeout属于 JS 引擎,存在时间队列,所以即使是设置了定时时间,但是如果有任务没有执行完成,它的执行也是会有延迟的。setTimeout 的执行时间并不是确定的。
requestAnimationFrame 属于 GUI 引擎,发生在渲 染过程的中重绘重排部分,与电脑分辨路保持一致。
setTimeout是 JS 引擎调用 GUI 引擎渲染。而requestAnimationFrame直属 GUI 引擎。
requestAnimationFrame 由浏览器专门为动画提供 的 API,在运行时浏览器会自动优化方法的调用,在特定性环境下可以有效节省了 CPU 开销。
代码实现
import React, { useState, useRef } from 'react';
import './index.less';
import { message } from 'antd';
const sudokuArray = [ { index: 0, dataIndex: 0 }, { index: 1, dataIndex: 1 }, { index: 2, dataIndex: 2 }, { index: 3, dataIndex: 7 }, { index: 4, center: true }, { index: 5, dataIndex: 3 }, { index: 6, dataIndex: 6 }, { index: 7, dataIndex: 5 }, { index: 8, dataIndex: 4 },];
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
function animate({
timing = f => f,
draw,
duration = 5000,
}) {
let start = performance.now();
let requestId;
requestId = requestAnimationFrame(function step(time) {
// timeFraction 从 0 增加到 1
let timeFraction = (time - start) / duration;
if (timeFraction > 1) {
timeFraction = 1;
}
// 计算当前动画状态
let progress = timing(timeFraction);
draw(progress); // 绘制
if (timeFraction < 1) {
requestId = requestAnimationFrame(step);
}
});
return () => {
cancelAnimationFrame(requestId);
}
}
const Sudoku = () => {
const [active, setActive] = useState();
// 防止重复点击
const lockRef = useRef(false);
const onStart = () => {
if(lockRef.current) {
return;
}
lockRef.current = true;
try {
new Promise((resolve) => {
setTimeout(() => {
// 获取抽奖结果
resolve(getRandomIntInclusive(0, 7))
})
}).then((res) => {
const target = 8 * 4 + res;
console.log('抽中了 '+ res, '==>', target, )
animate({
draw: (progress) => {
const cur = Math.floor(target * progress);
if(cur < target) {
setActive(cur % 8);
return;
}
setActive(cur % 8);
lockRef.current = false;
message.info(`抽中了 ${res}`)
}
})
})
} catch(e) {
lockRef.current = false;
console.log(e)
}
}
const renderItem = (item) => {
if(item.center) {
return <div onClick={onStart} key={item.index} className={`item${item.index}`}>start</div>
}
return(
<div key={item.index} className={`item${item.index} ${active === item.dataIndex && 'activeItem'}` }>{item.dataIndex}</div>
)
}
return (
<div className="container">
{sudokuArray.map((ele) => renderItem(ele))}
</div>
);
};
.container {
display: grid;
grid-template-columns: 100px 100px 100px;
grid-template-rows: 100px 100px 100px;
row-gap: 10px;
column-gap: 10px;
> div {
font-size: 24px;
display: flex;
justify-content: center;
align-items: center;
}
.item0 {
background-color: aqua;
}
.item1 {
background-color: aquamarine;
}
.item2 {
background-color: bisque;
}
.item3 {
background-color: burlywood;
}
.item4 {
background-color: chocolate;
&:hover {
cursor: pointer;
}
}
.item5 {
background-color: cornflowerblue;
}
.item6 {
background-color: darkcyan;
}
.item7 {
background-color: darkgreen;
}
.item8 {
background-color: darkorchid;
}
.activeItem {
opacity: 0.6;
}
}
结语
如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。
文章如有错误之处,希望在评论区指正🙏🙏。