TL;DR Like clocks? I built a clock made of clocks, click below to play with it.
Or read on for a more in depth look at building clocks out of clocks (with some React).
Inspiration from strange places
Have you something like this before?
Me too, it's cool huh! I really enjoy the way the time seems to gradually appear and take shape.
Actually, the more I looked at it the more I appreciated the way it was put together, they are not individual clocks in the traditional sense as one arm is not simply following the other. Both arms of the clock are moving freely allowing for the various interesting shapes and movements and, of course, the time.
I bet that wouldn't be too difficult to put together, a RaspberryPi to control the "clocks" with a mechanism to specify an angle for the first and second hands...
Creativity intensifies
Ok, so I don't have the motors to build the physical clocks and I'm not sure what I need to buy or how to wire them into a RaspberryPi. However, what I can do to prototype the idea is build a web app.
The quickest way to get started is to use create-react-app
, this provides the basic setup for a React app.
The first thing I wanted to do was to create a simple clock, requirements were simple; two hands that can move independently of each other and a face. Throwing together some div
's, a bit of CSS and voila, I had the makings of a clock, I can specify the angle in degrees, of each hand using the a CSS transform rotate
and there's a white face.
Making things move
From here I wanted to see what the best way to animating things could be. I spent a bit of time looking into the various ways I could animate components in React. Then I thought, nah, why not just see what I can I achieve without a library, surely I should be able to prototype something just using React and a bit of CSS knowhow.
Using create-react-app
meant that I get a modern version of React out of the box, which means hooks! I've been looking for an excuse to try out hooks and this seemed a good a time as any to try.
I imagined a basic rendering loop like this:
- inital state sets start / end positions
- render clock hands at initial position
-
setTimeout
orrequestAnimationFrame
to increment and set new position - render clock hands at new position
- repeat
Keeping the hand positions in state meant I could "animate" the hands by updating the state, incrementally, and causing a re-render which would update the hands to their new position.
const [angle, setAngle] = useState({
hour: getRandomStartingAngle(),
minute: getRandomStartingAngle()
});
In the new world of React hooks there's a hook perfect for the job of triggering the increment: useEffect
which, amongst other things, is run after every render (for more details check out the docs).
To increment I needed to create an effect with would trigger at a reasonable rate, for this I used requestAnimationFrame
which is a suitable API to schedule an update on as it's usually called 60 times per second (generally considered the threshold for smooth animation).
useEffect(()=> {
requestAnimationFrame(()=>{
const { hour, minute } = angle;
setAngle({
hour: hour + 1,
minute: minute + 1
});
});
}, [angle]);
Putting it all together and I had a clock that animated around and around, and around, and around, and never stop.
It seemed to work pretty well. However, I made a couple of mistakes which won't become apparent until I start trying to create numbers from the clocks.
Drawing numbers, with clocks
Next was to put a bunch of these little clocks onto the screen and see how they animate, using a small bit of flexbox
to define rows / columns and create 2x3 grids for a single number.
So it's starting to look a lot more like it could resemble a number. In order to animate to the number I needed to work out all the various positions that could go into a clock number and then tell the smaller clocks to animate to those positions.
The approach to drawing a number was to pass a targetAngle
to each of the smaller clocks. The basic idea was that for a given target angle the clock would continue increment the position of the hands until they'd reached it, then stop.
function getIncrementValue(angle, targetAngle) {
if(angle === targetAngle){
return angle;
} else {
return resetAngle(angle + 1);
}
}
Incremental by 1 each time means the target angle would eventually be achieved, however the first mistake I made in the sub clock logic rears its head.
As the hands increment around they can reach an angle above 360deg
which breaks for situations where the hand has to travel around the whole clock to reach the target angle. This would mean some of the sub clocks would stop at the right place but others would continue rotating, an awkward bug.
To solve the endless rotation bug I added a resetAngle
function which keeps the numbers between 0 < 359
allowing the target angle to always be reached.
Next was the job of actually figuring out these angles. The approach initially was to write, by hand, each angle, for each number, for each clock in the 2x3 grid... I quickly grew tired of this. Instead it's easier to specify a a number of set positions which are the building blocks of the number.
const createAngleSet = (hour, minute) => ({hour, minute});
const bothLeft = createAngleSet(270, 90);
const bothRight = createAngleSet(90, 270);
const bothTop = createAngleSet(0, 180);
const bothBottom = createAngleSet(180, 0);
const topAndBottom = createAngleSet(0, 0);
const rightAndLeft = createAngleSet(90, 90);
const topLeftCorner = createAngleSet(90, 0);
const topRightCorner = createAngleSet(270, 0);
const bottomLeftCorner = createAngleSet(0, 270);
const bottomRightCorner = createAngleSet(0, 90);
const emptySpace = createAngleSet(225, 45);
Above is the list of all the positions needed to "draw" the numbers 0-9 and they're used in a number config that looks something like this:
TWO: {
a1: { ...bothRight },
a2: { ...topLeftCorner },
a3: { ...bottomLeftCorner },
b1: { ...topRightCorner },
b2: { ...bottomRightCorner },
b3: { ...bothLeft }
}
The result of all this work was the realisation of the numbers. The effect was captured almost exactly as I wanted it, with the number appearing out of the randomness of the single clock faces.
The full NumberGrid is available for preview and illustrates the whole number set that is used in building the clock.
Animations, animations, animations
Earlier I mentioned I had made a mistake when creating the mini-clock, did you catch it?
Well, my first go with useEffect
was more on feeling than careful study of the documentation. As a result when I first attempted to draw the numbers 0-9, at the same time, there was pretty terrible performance.
Turns out useEffect
is expected to be triggered and then torn down, as a result it's supposed to provide a cleanup
function to do any bits of cleanup that are needed if an in progress effect needs to be cancelled. This caused a subtle issue as everything seems to animate smoothly however it slowed way down as I scaled up from the 1 mini-clock to the 54 that I needed in order to display the full 0-9 numbers.
useEffect(()=> {
const increment = requestAnimationFrame(()=> {
const { hour, minute } = angle;
const { hour: targetHour, minute: targetMinute } = targetAngle;
setAngle({
hour: getIncrementValue(hour, targetHour, speed),
minute: getIncrementValue(minute, targetMinute, speed)
});
}
return () => cancelAnimationFrame(increment);
}, [angle, targetAngle, speed]);
I corrected this within my effect with cancelAnimationFrame
and added a speed value, via useContext
to give me some control over the mini-clock animations (necessary for building a stopwatch).
Clock
I now have all the pieces to build the clock. Using the Date
object and updating the target time every time the hours or seconds changed. The DigitalClock
would then work out the individual parts of the time string and pass them to the ClockNumbers
which would, in turn, pass the individual parts to each mini clock.
I am so happy with the result 🕑🕤🕜😬
Thanks for reading and check out the clock below 👇
First appeared on my blog, published 3rd November 2019
Top comments (11)
OMG, these clocks are so amazing. We should make this into a Twitter bot!
We could do some really cool things with displaying likes or follower counts?
Orrrr we could make a Twitter bot that Tweets out the time using the Twemoji clocks! 😁😬
Great work!
I think the original project is The ClockClock by Human since 1982
Thanks! Yeah I did a bit of research later and found clockclock.com, it’s very cool. I hope they don’t mind my little project, though imitation being a form of flattery 😬
Similar: ashrafonline.github.io/AnalogDigit...
Oh wow, this is cool. Thanks for sharing!
This is amazing !!🔥
nice.
Amazing project
This is really cool!
This is just straight up awesome! I love it! Mad props to you, man.