跟做的原文链接# 😈当一个摆子前端太闲的时候会做什么:
今天在掘金上溜达的时候,无意间看到这篇文章,觉得还挺有意思的,就打算用在项目中跟做一下,因为项目使用的框架为React,所以就用React跟做一下;
具体的跟做步骤参考原文;
首先,跟做的第一部分休眠状态就暴漏了一个问题,关于在react中使用定时器:setInterval();
react中使用定时器setInterval
错误的第1版:
useEffect(() => {
const sleepingTimer = setInterval(() => {
if (eyeLength > 0) {
console.log("眼球的长度", eyeLength);
setEyeLength(+(eyeLength - 0.1).toFixed(2));
setRouteAngle(+(routeAngle + 0.1).toFixed(2));
handleUpEcharts();
} else {
clearInterval(sleepingTimer);
}
}, 10);
return () => {
clearInterval(sleepingTimer);
};
}, []);
这一版的表现效果就是我的定时器一直在执行,但是眼睛的长度eyeLength根本没有变化,一直都是初始值;后来在这篇文章中了解到是因为词法作用域导致的,这篇文章的图画的比较清晰,然后我就采用了第一种方案试了下;
错误的第2版:
useEffect(() => {
const sleepingTimer = setInterval(() => {
if (eyeLength > 0) {
console.log("眼球的长度", eyeLength);
setEyeLength(preValue => +(preValue - 0.1).toFixed(2));
// setRouteAngle(+(routeAngle + 0.1).toFixed(2));
handleUpEcharts();
} else {
clearInterval(sleepingTimer);
}
}, 100);
return () => {
clearInterval(sleepingTimer);
};
}, []);
但是我发现我的定时器里面有判断,判断的时候取得长度eyeLength还是存在问题,还是直接取最外层初始值,导致我的定时器管不了,且我的眼睛长度没有变化但是呼吸的动效是正常的;
所以如果你需要根据状态值进行判断,那就必须依赖状态值的变化才可以,方案一不适合;
正确的版本1:
useEffect(() => {
const sleepingTimer = setInterval(() => {
if (eyeLength > 0) {
console.log("眼球的长度", eyeLength);
setEyeLength(+(eyeLength - 0.1).toFixed(2));
setRouteAngle(+(routeAngle + 0.1).toFixed(2));
handleUpEcharts();
} else {
clearInterval(sleepingTimer);
}
}, 10);
return () => {
clearInterval(sleepingTimer);
};
}, [eyeLength]);
显示的效果:
正确的版本2:
其实到这,想要的效果已经实现了,但是看完### 如果我的 effect 的依赖频繁变化,我该怎么办?这一块之后,
首先定时器每次都会被重置且在清除前会调用一次,那其实最好不要去让定时器重置,所以最好还是利用setState的函数式更新:
-
- 定时器不想被重置;
-
- 利用setState的函数式更新;
-
- 更新echarts的属性值,它主要依赖于眼睛长度的状态值;
所以最后又改了一版:
useEffect(() => {
const sleepingTimer = setInterval(() => {
setEyeLength(preEyeLength => {
console.log("眼球的长度", preEyeLength);
if (preEyeLength <= 0) {
clearInterval(sleepingTimer);
}
return +(preEyeLength - 0.1).toFixed(2);
});
setRouteAngle(preRouteAngle => +(preRouteAngle + 0.1).toFixed(2));
}, 20);
return () => {
clearInterval(sleepingTimer);
};
}, []);
useEffect(() => {
handleUpEcharts();
}, [eyeLength]);
但是这样写,总感觉不优雅了;
完成代码
除了最后的懒惰状态,以下是实现的全部代码:
import { useEffect, useRef, useState } from "react";
import * as echarts from "echarts";
import "./index.less";
const Eye = () => {
const eyeBallRef = useRef<HTMLDivElement>(null);
const eyeRef = useRef<HTMLDivElement>(null);
// 声明Echarts实例
const eyeEcharts = useRef<echarts.EChartsType>();
// 眼睛的长度
const [eyeLength, setEyeLength] = useState(20);
// 眼睛旋转的角度
const [routeAngle, setRouteAngle] = useState(0);
// 眼球色系
const [eyeBallColor, setEyeBallColor] = useState("#69c2d9");
const [eyeThemeStyle, setEyeThemeStyle] = useState<any>({});
const [angryClass, setAngryClass] = useState<string>("");
// 蓝色系
const setNormal = () => {
setEyeThemeStyle({
"--eye-out-color": "#69c2d9",
"--eye-mid-color": "#133abc",
"--eye-in-color": "#13256b",
"--eye-bg-color": "#d7d7d7"
});
setEyeBallColor("#69c2d9");
setAngryClass("");
};
// 紫色系
const setAngry = () => {
setEyeThemeStyle({
"--eye-out-color": "#ea25e8",
"--eye-mid-color": "#4e0b85",
"--eye-in-color": "#270343",
"--eye-bg-color": "#fff5ff"
});
setEyeBallColor("#ea25e8");
setAngryClass("angry");
};
const handleUpEcharts = () => {
eyeEcharts?.current?.setOption({
series: [
{
type: "gauge", // 仪表盘类型
name: "bling bling eye",
radius: "-10%", // 仪表盘的半径,设置成负的,分割线从内向外延伸
center: ["50%", "50%"],
clockwise: false, // 是否是顺时针
startAngle: `${0 + routeAngle * 5}`, // 起始角度
endAngle: `${270 + routeAngle * 5}`, // 结束角度
splitNumber: 3, // 分割数量
detail: {
// 仪表盘详情
show: false
},
axisLine: {
// 仪表盘轴线
show: true
},
axisTick: false, //刻度样式
splitLine: {
show: true,
length: eyeLength, // 分割线长度
lineStyle: {
shadowBlur: 20, // 阴影渐变
shadowColor: eyeBallColor, // 阴影颜色
shadowOffsetY: "0",
color: eyeBallColor, // 分割线颜色
width: 4 // 分割线宽度
}
},
axisLabel: false
},
{
type: "gauge", // 仪表盘类型
name: "bling bling eye",
radius: "-10%", // 仪表盘的半径,设置成负的,分割线从内向外延伸
center: ["50%", "50%"],
clockwise: false, // 是否是顺时针
startAngle: `${-45 + routeAngle * 5}`, // 起始角度
endAngle: `${225 + routeAngle * 5}`, // 结束角度
splitNumber: 3, // 分割数量
detail: {
// 仪表盘详情
show: false
},
axisLine: {
// 仪表盘轴线
show: true
},
axisTick: false, //刻度样式
splitLine: {
show: true,
length: eyeLength, // 分割线长度
lineStyle: {
shadowBlur: 20, // 阴影渐变
shadowColor: eyeBallColor, // 阴影颜色
shadowOffsetY: "0",
color: eyeBallColor, // 分割线颜色
width: 4 // 分割线宽度
}
},
axisLabel: false
}
]
});
};
useEffect(() => {
if (eyeBallRef?.current) {
eyeEcharts.current = echarts.init(eyeBallRef.current);
handleUpEcharts();
}
}, [eyeBallRef]);
const foucusMouse = (e: MouseEvent) => {
let bodyW = document.body.clientWidth;
let bodyH = document.body.clientHeight;
let origin = [bodyW / 2, bodyH / 2];
// 鼠标坐标
let mouseLocation = [e.clientX - origin[0], e.clientY - origin[1]];
// 旋转角度
let eyeXDeg = (mouseLocation[1] / bodyW) * 90; // 这里的80代表的是最上下边缘大眼X轴旋转角度
let eyeYDeg = (mouseLocation[0] / bodyH) * 70;
if (eyeRef?.current && eyeBallRef?.current) {
eyeRef.current.style.transform = `rotateY(${eyeYDeg}deg) rotateX(${eyeXDeg}deg)`;
eyeBallRef.current.style.transform = `translate(${eyeYDeg / 1.5}px, ${-eyeXDeg / 1.5}px)`;
}
};
/*
闭眼主要逻辑就是:
1. 想让眼球的长度慢慢减小直到0;并且添加旋转的效果
2. 眼球减小为0的时候,添加呼吸的效果;
*/
useEffect(() => {
setNormal();
const sleepingTimer = setInterval(() => {
setEyeLength(preEyeLength => {
// console.log("眼球的长度", preEyeLength);
if (preEyeLength <= 0) {
clearInterval(sleepingTimer);
}
return +(preEyeLength - 0.1).toFixed(2);
});
setRouteAngle(preRouteAngle => +(preRouteAngle + 0.1).toFixed(2));
}, 20);
document.addEventListener("mousemove", foucusMouse);
return () => {
clearInterval(sleepingTimer);
document.removeEventListener("mousemove", foucusMouse);
};
}, []);
useEffect(() => {
handleUpEcharts();
}, [eyeLength]);
const handleWakeup = () => {
if (eyeLength >= 0) return;
setAngry();
const wakeTimer = setInterval(() => {
setEyeLength(preEyeLength => {
// console.log("眼球的长度", preEyeLength);
if (preEyeLength >= 50) {
clearInterval(wakeTimer);
handleNormal(preEyeLength);
}
const length = +(preEyeLength + 0.2).toFixed(2);
return length > 50 ? 50 : length;
});
setRouteAngle(preRouteAngle => +(preRouteAngle + 0.2).toFixed(2));
}, 20);
};
const handleNormal = (length: number) => {
console.log("handleNormal", length);
if (length <= 20) return;
setNormal();
const normalTimer = setInterval(() => {
setEyeLength(preEyeLength => {
// console.log("眼球的长度", preEyeLength);
if (preEyeLength <= 20) {
clearInterval(normalTimer);
}
const length = +(preEyeLength - 0.2).toFixed(2);
return length <= 20 ? 20 : length;
});
setRouteAngle(preRouteAngle => +(preRouteAngle - 0.2).toFixed(2));
}, 20);
};
return (
<div className="eye-wrap" style={eyeThemeStyle}>
<div
className={["eye-container", eyeLength > 0 ? "" : "eye-sleeping", angryClass].join(" ")}
ref={eyeRef}
onClick={handleWakeup}
>
<div className="eye-ball" ref={eyeBallRef}></div>
<div className="filter">
<div className="eye-container" id="eye-filter"></div>
</div>
</div>
{/* Svg滤镜*/}
<svg width="0">
<filter id="filter">
<feTurbulence baseFrequency="1">
<animate id="animate1" attributeName="baseFrequency" dur="1s" from="0.5" to="0.55" begin="0s;animate1.end"></animate>
<animate id="animate2" attributeName="baseFrequency" dur="1s" from="0.55" to="0.5" begin="animate2.end"></animate>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" scale="50" xChannelSelector="R" yChannelSelector="B" />
</filter>
</svg>
</div>
);
};
export default Eye;
css:
.eye-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
overflow: hidden;
background-color: #faeee5;
/* 蓝色系 */
--eye-out-color: #69c2d9;
--eye-mid-color: #133abc;
--eye-in-color: #13256b;
--eye-bg-color: #d7d7d7;
/* 紫色系 */
/* @eyeOutColor: #ea25e8;
@eyeMidColor: #4e0b85;
@eyeInColor: #270343;
@eyeBgColor: #fff5ff; */
.filter {
width: 100%;
height: 100%;
filter: url("#filter");
opacity: 0;
}
.angry {
animation: looking 2.5s;
.filter {
opacity: 1;
}
}
.filter .eye-container {
top: calc(50% - 120px);
left: calc(50% - 120px);
}
.eye-container,
.filter .eye-container {
position: relative;
width: 200px;
/* height: 150px; */
aspect-ratio: 1; /* 纵横比 */
cursor: pointer;
border: 4px solid var(--eye-mid-color);
border-radius: 50%;
}
.eye-container {
/* 眼球 */
.eye-ball {
position: absolute;
width: 100%;
height: 100%;
background-color: var(--eye-bg-color);
border-radius: 50%;
}
}
.eye-container::after,
.eye-container::before {
position: absolute;
top: 50%;
left: 50%;
box-sizing: border-box;
content: "";
border-radius: 50%;
transform: translate(-50%, -50%);
}
.eye-container::before {
width: calc(100% + 20px);
height: calc(100% + 20px);
border: 6px solid var(--eye-out-color);
}
.eye-container::after {
width: 100%;
height: 100%;
border: 4px solid var(--eye-in-color);
box-shadow: inset 0 0 30px var(--eye-in-color);
}
/* 休眠状态 */
.eye-sleeping {
animation: sleeping 6s infinite;
}
/* 有种呼吸的感觉 */
@keyframes sleeping {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
/* 寻找 */
@keyframes looking {
0% {
transform: translateX(0) rotateY(0);
}
10% {
transform: translateX(0) rotateY(0);
}
40% {
transform: translateX(70px) rotateY(30deg);
}
80% {
transform: translateX(-70px) rotateY(-30deg);
}
100% {
transform: translateX(0) rotateY(0);
}
}
}