我正在尝试构建一个模拟时钟,使秒针每秒旋转,分针每分钟旋转6deg,时针每12分钟旋转6deg。
以下是代码框:https://codesandbox.io/s/react-example-d7mes?file=/Clock.js
只要分针的角度是[72, 144, 216, 288, 360]
(12分钟度)之一,我都会将时针旋转6度。
这就是我在做什么:
let twelveMinDegrees = [72, 144, 216, 288, 360];
setInterval(() => {
this.setState(prev => ({
sec: prev.sec == 360 ? 6 : prev.sec + 6, //degrees
min: prev.sec == 354 ? (prev.min == 360 ? 6 : prev.min + 6) : prev.min, //degrees
hrs: (function(){ //degrees
const indx = twelveMinDegrees.findIndex(el => el == prev.min)
if(!minChanged && indx >=0){ //only change once for every 12min
minChanged = true;
let incHrs = prev.hrs + (6*indx);
console.log(incHrs);
return incHrs;
}else{
if(!twelveMinDegrees.includes(prev.min)){
minChanged = false;
}
return prev.hrs;
}
})()
}))
}, 1000)
但是时针并没有改变,并且在其他地方第二次时将其重新设置为先前的值,并且忽略了返回的incHrs
值,因为在状态更新之前, else
被称为下一秒,返回的prev.hrs
仍然是旧值(不是if(!minChanged && indx >=0)
中返回的值)
我该如何解决?
答案 0 :(得分:0)
这里存在一个基本的设计问题,即setInterval
是错误的节省时间的工具。 setInterval
仅保证回调不会在至少 1000毫秒内运行,而不能保证在确切的 1000毫秒内运行,因此您将随着漂移和跳跃时间而结束。
我建议使用requestAnimationFrame
和Date
库来确定何时出现滴答声。
通过此设置,您可以使用以下方法按比例调整时针:小时剩余的分钟数。
(hours + minutes / 60) * 30 + 180
如果您希望对时针进行更细粒度的调整,请将分钟数分成6个不同的块:
(hours + floor(minutes / 10) * 10 / 60) * 30 + 180
在数学上进行此操作比在硬编码数组中查找增量点要少得多。
这是一个最小的示例,您可以使用它来保持准确的时间(我将样式留给您):
.hand {
width: 2px;
height: 40%;
background-color: black;
transform-origin: top center;
position: absolute;
border-radius: 3px;
top: 50%;
left: 50%;
}
.analog-clock {
position: relative;
border-radius: 50%;
border: 1px solid #aaa;
height: 120px;
width: 120px;
}
<script type="text/babel" defer>
const {Fragment, useEffect, useState, useRef} = React;
const Clock = () => {
const [date, setDate] = useState(new Date());
const requestRef = useRef();
const prevDateRef = useRef();
const tick = () => {
const now = new Date();
if (prevDateRef.current &&
now.getSeconds() !== prevDateRef.current.getSeconds()) {
setDate(now);
}
prevDateRef.current = now;
requestRef.current = requestAnimationFrame(tick);
};
useEffect(() => {
requestRef.current = requestAnimationFrame(tick);
return () => cancelAnimationFrame(requestRef.current);
}, []);
const pad = n => n.toString().padStart(2, 0);
const computeHourDeg = date =>
(date.getHours() + ~~(date.getMinutes() / 10) * 10 / 60) * 30 + 180;
return (
<Fragment>
<div className="analog-clock">
<div
className="hand"
style={{transform: `rotate(${6 * date.getSeconds() + 180}deg)`}}
></div>
<div
className="hand"
style={{transform: `rotate(${6 * date.getMinutes() + 180}deg)`}}
></div>
<div
className="hand"
style={{background: "red",
height: "30%",
transform: `rotate(${computeHourDeg(date)}deg)`}}
></div>
</div>
<h3>
{pad(date.getHours())}:
{pad(date.getMinutes())}:
{pad(date.getSeconds())}
</h3>
</Fragment>
);
};
ReactDOM.render(<Clock />, document.body);
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
这是一个带有模拟Date
对象的加速版本,以说明其工作正常:
.hand {
width: 2px;
height: 40%;
background-color: black;
transform-origin: top center;
position: absolute;
border-radius: 3px;
top: 50%;
left: 50%;
}
.analog-clock {
position: relative;
border-radius: 50%;
border: 1px solid #aaa;
height: 120px;
width: 120px;
}
<script type="text/babel" defer>
const {Fragment, useEffect, useState, useRef} = React;
const speedMS = 5;
class MockDate {
static second = 0;
static minute = 0;
static hour = 0;
constructor() {
this.second = MockDate.second;
this.minute = MockDate.minute;
this.hour = MockDate.hour;
}
getSeconds() {
return this.second;
}
getMinutes() {
return this.minute;
}
getHours() {
return this.hour || 12;
}
}
setInterval(() => {
if (++MockDate.second === 60) {
MockDate.second = 0;
if (++MockDate.minute === 60) {
MockDate.minute = 0;
MockDate.hour = (MockDate.hour + 1) % 12;
}
}
}, speedMS);
const Clock = () => {
const [date, setDate] = useState(new MockDate());
const requestRef = useRef();
const prevDateRef = useRef();
const tick = () => {
const now = new MockDate();
if (prevDateRef.current &&
now.getSeconds() !== prevDateRef.current.getSeconds()) {
setDate(now);
}
prevDateRef.current = now;
requestRef.current = requestAnimationFrame(tick);
};
useEffect(() => {
requestRef.current = requestAnimationFrame(tick);
return () => cancelAnimationFrame(requestRef.current);
}, []);
const pad = n => n.toString().padStart(2, 0);
const computeHourDeg = date =>
(date.getHours() + ~~(date.getMinutes() / 10) * 10 / 60) * 30 + 180;
return (
<Fragment>
<div className="analog-clock">
<div
className="hand"
style={{transform: `rotate(${6 * date.getSeconds() + 180}deg)`}}
></div>
<div
className="hand"
style={{transform: `rotate(${6 * date.getMinutes() + 180}deg)`}}
></div>
<div
className="hand"
style={{background: "red",
height: "30%",
transform: `rotate(${computeHourDeg(date)}deg)`}}
></div>
</div>
<h3>
{pad(date.getHours())}:
{pad(date.getMinutes())}:
{pad(date.getSeconds())}
</h3>
</Fragment>
);
};
ReactDOM.render(<Clock />, document.body);
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>