Reputation: 5058
I'm new to React, trying to create analog watch as exercise and all was good until I set update interval to 1000 / 30 (to have ~30fps). Its extremely slow - CPU is always 100, memory leaks, event listeners number easily grows to 30k. Devtools stops it in a while to prevent out-of-memory-crash. What am I doing wrong here? Should I use requestAnimationFrame()
instead of interval?
Tried to make snippet below via instruction, but no luck - working example is only on stackblitz https://stackblitz.com/edit/react-roszta?file=index.js
const {useState} = React;
function DigitalDisplay(props) {
const time = new Date(props.time).toLocaleString();
const style = {
width: '200px',
height: '30px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: '#444',
boxShadow: '0 0 0 3px cyan',
borderRadius: '3px',
}
return (<div style={style}>{time}</div>);
}
function AnalogDisplay(props) {
const style = {
width: '200px',
height: '200px',
position: 'relative',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: '#444',
borderRadius: '50%',
transform: 'rotate(-90deg)',
boxShadow: '0 0 0 3px cyan'
}
return (<div style={style}>
<Arrows time={props.time} />
</div>);
}
function Arrows(props) {
const style = {
display: 'block',
background: 'white',
height: '0',
transformOrigin: '0 0',
boxShadow: 'cyan 0px 0px 0px 2px, rgba(255,255,255,0.8) 0px 0px 7px 1px',
borderRadius: '3px',
position: 'absolute',
left: '50%',
top: '50%',
}
const styleHours = {
...style,
width: '20%',
transform: `rotate(${new Date(props.time).getHours() * 360 / 12}deg)`
};
const styleMinutes = {
...style,
width: '30%',
transform: `rotate(${new Date(props.time).getMinutes() * 360 / 60}deg)`
};
const styleSeconds = {
...style,
width: '45%',
transform: `rotate(${new Date(props.time).getSeconds() * 360 / 60}deg)`
};
const styleMilliseconds = {
...style,
width: '2000%',
boxShadow: '0px 0px 1px 1px yellow',
opacity: 0.8,
transform: `rotate(${new Date(props.time).getMilliseconds() * 360 / 1000}deg)`
};
return (<React.Fragment>
<div className="arrow-milliseconds" style={styleMilliseconds}></div>
<div className="arrow-seconds" style={styleSeconds}></div>
<div className="arrow-minutes" style={styleMinutes}></div>
<div className="arrow-hours" style={styleHours}></div>
</React.Fragment>)
}
function Clock() {
const [time, setTime] = useState(new Date().getTime());
setInterval(() => {
setTime(new Date().getTime())
}, 1000 / 12);
return (<React.Fragment>
<AnalogDisplay time={time} />
<br />
<DigitalDisplay time={time} />
</React.Fragment>);
}
ReactDOM.render(
<Clock />,
document.getElementById('root'),
)
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: #444;
color: cyan;
width: 100vw;
height: 100vh;
padding: 0;
justify-content: center;
align-items: center;
display: flex;
overflow: hidden;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Upvotes: 1
Views: 537
Reputation: 513
That's what I've got so far
This image is your current code as it is
I removed AnalogDisplay
and DigitalDisplay
, The JS event listeners had dropped drastically, meaning that it is not a problem with react itself but with the clock displays
Here I changed your fps to a frame every 5 seconds As you can see the CPU usage had dropped back to normal
The last one I changed your Clock component to a stateful component, and rather than using useState
I just edited the component's state
Frame rate is 30fps and CPU usage is 40% at its worst
At first I thought that you've got some poor CPU, but mine is i7-7700HQ, so CPU usage should never hit such numbers.
It appears that React's useState
's setter function does use a lot more CPU to render its component than the normal Stateful component.
Upvotes: 1
Reputation: 12637
You are creating quite a few objects, and that every 33ms. What you see there is GC in action. Have you considered letting CSS do the animation?
(function(clock, now) {
// setting the clock once.
clock.style.setProperty(
"--hours",
now.getHours() + now.getMinutes() / 60
);
clock.style.setProperty(
"--minutes",
now.getMinutes() + now.getSeconds() / 60
);
clock.style.setProperty(
"--seconds",
now.getSeconds() + now.getMilliseconds() / 1000
);
})(document.getElementById("clock"), new Date());
#clock {
position: relative;
width: 150px;
height: 150px;
border: 1px solid currentColor;
border-radius: 50%;
transform: rotate(180deg);
}
.hours,
.minutes,
.seconds {
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 0;
border: 1px solid currentColor;
transform-origin: 50% 0%;
animation-name: rotate;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.hours {
height: 20%;
animation-duration: 43200s;
animation-delay: calc(var(--hours) * -3600s);
}
.minutes {
height: 40%;
animation-duration: 3600s;
border-color: #666;
animation-delay: calc(var(--minutes) * -60s);
}
.seconds {
height: 45%;
animation-duration: 60s;
border-color: rgba(black, .25);
animation-delay: calc(var(--seconds) * -1000ms);
animation-timing-function: steps(60);
}
@keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(1turn);
}
}
<div id="clock">
<div class="hours"></div>
<div class="minutes"></div>
<div class="seconds"></div>
</div>
Upvotes: 0