I just lately needed to create a easy analog clock element for my React undertaking. I now wish to share how I approached it.
I constructed a React element drawing a clock face and three palms for hours, minutes and seconds. I used HTML with Styled Elements to fashion the clock. After all it could possibly be achieved with SVG or drawing on a canvas. Possibly I am going to discover these choices later. For now I needed to maintain it easy and since we do solely have 3 shifting elements this won’t be an excessive amount of of a efficiency hog.
Let’s begin.
1. Create a clock face and elegance it
First we have to have the bottom element. It would do nothing but besides of drawing the clock face.
import React from 'react'
import styled from 'styled-components'
const DemoClock: React.FC = () => {
return <Clock />
}
const Clock = styled.div`
background-color: white;
border-radius: 50%;
border: black 1px stable;
top: 100px;
margin-bottom: 0.5rem;
place: relative;
width: 100px;
`
export default DemoClock
This can create a spherical white background with a black border with a diameter of 100 pixels.
2. Add the palms
Now we add the palms. Let’s simply take the hour hand for instance.
const Hours = styled.div`
background-color: black;
border-radius: 2.5px;
top: 30px;
left: calc(50% - 2.5px);
place: absolute;
prime: 25px;
width: 5px;
`
This can create a 30 pixels lengthy black hand with rounded edges and a small overlap of 5 px within the heart.
Let’s check out some particulars right here:
-
left: calc(50% - 2.5px);
: This can transfer the hand to the middle of the clock. The offset of-2.5px
is as a result of the hand is 5px extensive, so we’ve got to maneuver it to the left by the half of its width. -
prime: 25px;
: This can transfer the hand down by 25 pixels. 25 pixels as a result of the radius is 50px and we wish an overlap of 5 pixels. Soradius - size of hand + overlap = 50 - 30 + 5 = 25
.
Then we add the hand to the clock.
const DemoClock: React.FC = () => {
return (
<Clock>
<Hours />
</Clock>
)
}
Repeat this for the minutes and seconds hand. My clock has now all three palms.
const DemoClock: React.FC = () => {
return (
<Clock>
<Hours />
<Minutes />
<Seconds />
</Clock>
)
}
const Clock = styled.div`
background-color: white;
border-radius: 50%;
border: black 1px stable;
top: 100px;
margin-bottom: 0.5rem;
place: relative;
width: 100px;
`
const Hours = styled.div`
background-color: black;
border-radius: 2.5px;
top: 30px;
left: calc(50% - 2.5px);
place: absolute;
prime: 25px;
width: 5px;
`
const Minutes = styled(Hours)`
top: 45px;
prime: 10px;
`
const Seconds = styled(Hours)`
background-color: pink;
top: 50px;
prime: 5px;
width: 1px;
`
3. Make the hours hand shows the present time
Let’s begin with displaying the present hour. For this we add a time
prop to the styled element to have the ability to feed it with any Date
object.
We all know that we’ve got 12 hours on a clock, so we will calculate the angle of the hand for every hour by dividing 360 levels by 12. This can give us 30 levels per hour. There’s a small caveat: getHours()
returns as much as 24 hours per day. So we’ve got to ensure we solely get 12 hours by utilizing a modulo of 12.
interface DateProps {
time: Date
}
const Hours = styled.div<DateProps>`
...
transform-origin: heart calc(100% - 5px);
remodel: rotateZ(${({ time }) => ((time.getHours() % 12) * 30}deg);
`
We additionally needed to set the pivot level of the rotation to the middle of the clock. We do that by setting the remodel origin. Through the use of calc(100% - 5px)
we deal with the 5 pixel overlap of the hand.
Possibly you understand the hand is now leaping from one hour to the however doesn’t transfer step by step. To realize a smoother motion we’ve got to perform a little bit extra maths.
We multiply the hours by 60 and add the present minutes to it. This manner the worth will replicate the present time in minutes. However now the angle of every unit is totally different. We do have 12 * 60 = 720
minutes in 12 hours, so we will calculate the angle of every minute by dividing 360 levels by 720. This can give us 0.5 levels per minute.
const Hours = styled.div<DateProps>`
...
remodel: rotateZ(${({ time }) => ((time.getHours() % 12) * 60 + time.getMinutes()) * 0.5}deg);
`
4. Repeat for the minutes and seconds
We add the rotation of the minutes and seconds hand in an analogous method.
const Minutes = styled(Hours)`
...
remodel: rotateZ(${({ time }) => (time.getMinutes() * 60 + time.getSeconds()) * 0.1}deg);
`
const Seconds = styled(Hours)`
...
remodel: rotateZ(${({ time }) => time.getSeconds() * 6}deg);
`
5. Replace the time
Now we simply have so as to add the time to the clock element. We are able to do that utilizing a state containing the present time and a timer to replace the time each second. Be sure that the interval is cleared when the element is unmounted.
const DemoClock: React.FC = () => {
const [time, setTime] = useState(() => new Date())
useEffect(() => {
const interval = setInterval(() => {
const now = new Date()
setTime(now)
}, 1000)
return () => clearInterval(interval)
}, [])
return (
<Clock>
<Hours time={time} />
<Minutes time={time} />
<Seconds time={time} />
</Clock>
)
}
One small factor to contemplate. When doing these updates every second, within the worst case the timer could possibly be off by virtually one second. Take into consideration this. When the timer runs round 990 milliseconds after the complete second, it might look like being off by one second. More often than not that is most likely not a problem. However you need to take into consideration the wanted precision when coping with time. Let’s assume you are engaged on an auctions platform, then the timing could be fairly vital and even a second off would possibly annoy some prospects.
So, we’d wish to enhance the decision of the clock to 250 milliseconds and even decrease (relying in your wants), however solely replace the state if the second has modified.
useEffect(() => {
const interval = setInterval(() => {
const now = new Date()
if (now.getSeconds() !== time.getSeconds())) {
setTime(now)
}
}, 250)
return () => clearInterval(interval)
}, [time])
6. Yet one more factor
Whereas this works we created a possible downside. An issue that’s moderately particular to styled parts. Styled parts create a brand new class for every distinctive mixture of props. Which means should you change the props of a element, the category can be recreated. This can be a downside for efficiency. The answer is to make use of the attr()
methodology.
const Hours = styled.div.attrs<DateProps>(({ time }) => ({
fashion:{
remodel: `rotateZ(${((time.getHours() % 12) * 60 + time.getMinutes()) * 0.5}deg)`,
},
})).<DateProps>`
...
`
Conclusion
We found that coping with time brings sure challenges (we solely scratched the floor although – factor’s get fairly sophisticated as quickly as you need to synchronize with a server, want precision and/or must cope with timezones). However there it’s: a working clock.
Check out the completed implementation on this gist.
You’ll be able to go on and enhance the clock: Strive including a day of month discipline, add indicators for the hours and check out totally different hand designs utilizing pure css or svg. The stage is yours.
That is it. I hope you loved your time.