add-a-cat-following-mouse
Background
I found a lovely cat on Internet, it's Github repo: https://github.com/adryd325/oneko.js. It's written in javascript. I create a react version.
Implement
First, let's add a cat on screen. You can use a sprite image. You can download it here: 
"use client"
export default function MyCat() {
return (
<div
className="fixed pointer-events-none z-50"
style={{
width: `32px`,
height: `32px`,
backgroundImage: "url(/cat.gif)",
}}
/>
)
}
We get a cat on screen now! Let's change it's position. We want to show it on top left corner.
<div
className="fixed pointer-events-none z-50"
style={{
width: `32px`,
height: `32px`,
backgroundImage: "url(/cat.gif)",
+ backgroundPosition: "-96px -96px",
+ left: "16px",
+ top: "16px",
+ zIndex: 2147483647 // 2 ** 31 - 1
}}
/>
We need to monitor mouse movement, so that cat can reach there.
const mousePosXRef = useRef(0) const mousePosYRef = useRef(0)
const handleMouseMove = (event: MouseEvent) => {
mousePosXRef.current = event.clientX
mousePosYRef.current = event.clientY
}
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove)
return () => {
window.removeEventListener("mousemove", handleMouseMove)
}
}, [])
Now let's image, we have a point: (x, y), we need to let cat reach there, how to implement it? Yes, we need to animation.
function frame() {
// cat run
}
function onAnimationFrame(timestamp) {
frame()
requestAnimationFrame(onAnimationFrame)
}
requestAnimationFrame(onAnimationFrame)
We need to bring the cat closer to the mouse position during each animation.
const catPostXRef = useRef(32)
const catPostYRef = useRef(32)
const diffX = mousePosXRef.current - catPostXRef.current
const diffY = mousePosYRef.current - catPostYRef.current
const distance = Math.sqrt(diffX ** 2 + diffY ** 2)
catPostXRef.current += (diffX / distance)
catPostYRef.current += (diffY / distance)
catPosXRef.current = Math.min(
Math.max(16, catPosXRef.current), // at least 16
window.innerWidth - 16 // make sure not large than window width - 16
)
catPosYRef.current = Math.min(
Math.max(16, catPosYRef.current),
window.innerHeight - 16
)
if (containerRef.current) {
// cat center(32px / 2 = 16px)
containerRef.current.style.left = `${catPosXRef.current - 16}px`
containerRef.current.style.top = `${catPosYRef.current - 16}px`
}
Now the cat can follow our mouse. We need to add a conditional statement to stop animation:
if (distance < 48) {
return
}
Also, we want to let cat move in segments, so we need an extra variable: CAT_SPEED.
const CAT_SPEED = 10
const FRAME_DELAY = 100
...
catPosXRef.current += (diffX / distance) * CAT_SPEED
catPosYRef.current += (diffY / distance) * CAT_SPEED
...
function onAnimationFrame(timestamp) {
if (!lastFrameTimestampRef.current) {
lastFrameTimestampRef.current = timestamp
}
if (timestamp - lastFrameTimestampRef.current > FRAME_DELAY) {
lastFrameTimestampRef.current = timestamp
frame()
}
requestAnimationFrame(onAnimationFrame)
}
- Images of different actions Now we can make cat follow our mouse. But it has one action. We want to let it have different actions, like running in different directions, sleeping, etc.
Let's say we have eight directions.
{
N: "North",
S: "South",
W: "West",
E: "East",
NE: "North-East",
SE: "South-East",
SW: "South-West",
NW: "North-West"
}