The idea
The holy grail.
Tracking the state
I started with a handful of state variables and refs. React state controls whether the screensaver is active, while refs hold the values that change continuously during the animation.
const logoRef = useRef<HTMLDivElement>(null);
const currentColor = useRef(colors[0]);
const x = useRef(100);
const y = useRef(100);
const vx = useRef(3);
const vy = useRef(3); The position and velocity values don’t need to trigger a React render on every frame. Using refs lets the animation update them directly, leaving React to handle the larger state change of activating or dismissing the screensaver.
Changing colors
The other recognizable detail is the color changing whenever it hits an edge.
const changeColor = () => {
let nextColor = currentColor.current;
while (nextColor === currentColor.current) {
nextColor = colors[
Math.floor(Math.random() * colors.length)
];
}
currentColor.current = nextColor;
if (logoRef.current && nextColor) {
logoRef.current.style.color = nextColor;
}
}; The color is chosen from an array. Also making sure the next color differs from the current one, so a collision always produces a visible change.
Rather than updating React state for every collision, update the logo’s color directly through its ref.
Making the logo bounce
The animation itself runs through requestAnimationFrame. Each frame advances the logo’s position, checks whether it has reached an edge, and reverses its velocity when necessary.
x.current += vx.current;
y.current += vy.current;
let bouncedX = false;
let bouncedY = false;
if (x.current <= 0) {
x.current = 0;
vx.current *= -1;
bouncedX = true;
}
if (x.current >= maxX) {
x.current = maxX;
vx.current *= -1;
bouncedX = true;
}
if (y.current <= 0) {
y.current = 0;
vy.current *= -1;
bouncedY = true;
}
if (y.current >= maxY) {
y.current = maxY;
vy.current *= -1;
bouncedY = true;
}
if (bouncedX || bouncedY) {
changeColor();
} The available movement area is calculated from the viewport dimensions and the logo’s size. This keeps it inside the visible screen rather than letting it bounce based on the logo’s top-left corner alone.
The corner hit
Track horizontal and vertical collisions separately. When both happen in the same frame, this triggers a quick scale animation to give the corner hit a little extra emphasis.
if (bouncedX && bouncedY) {
logo.animate(
[
{
transform: `translate3d(${x.current}px, ${y.current}px, 0) scale(1)`
},
{
transform: `translate3d(${x.current}px, ${y.current}px, 0) scale(1.15)`
},
{
transform: `translate3d(${x.current}px, ${y.current}px, 0) scale(1)`
}
],
{
duration: 250,
easing: "ease-out"
}
);
} Putting it together
The final piece is the fullscreen overlay and the logo itself.
<div
className="fixed inset-0 z-9999 bg-theme-overlay backdrop-blur-xs"
>
<div
ref={logoRef}
className="absolute w-80 text-brand-w1 transition-colors"
style={{
color: currentColor.current
}}
>
<Logo />
</div>
</div>