SpriteMotion Comics
Note: I thought generative AI would make this unnecessary. (And maybe it will...in a few months. 🤣) But for now it's super fun.
IMO the evolution of comics is not motion comics. Have you ever tried making one?
You need specialized training in animation and tools---meaning it's not likely something you can do these on a daily basis. But daily comics, namely the daily comics strip, are about.
The title of biggest contender for new comics goes to Runway Gen 2.
But what if we had a simple, low tech, manual way to bring sprites to life?
Hello, SpriteMotion Comics
The idea is pretty simple. Run DOM elements on a physics engine. Give DOM elements position and velocity props.
<World>
<MotionImg src="running-man.png" x={0} y={0} vx={3} vy={0} />
<MotionDiv x={0} y={0} width={25} height={5} />
</World>
Boom!
And since this is JSX, we can do cool stuff like this:
const CryingPortrait = () => {
return (
<World>
<img src="portrait.png" />
{Array.from({ length: 1000 }).map((_, key) => (
<MotionImg
key={key}
src="tear.png"
vx={Math.random()}
vy={Math.random()}
/>
))}
</World>
);
};
The World
component implementation is less than 100 lines. And it's nothing
too suprising. Ditto MotionImg
. You can see the code
here.
It uses matter-js for the physics engine.
A live example
This is the library in action:

An explanation is in order.
- The main thing is that we do the React technique of resetting state by changing the key. In this case, we change the key every 700ms, effectively, re-creating the tears.
- (Note: you may notice a flicker because the images are redownloaded each state reset!. This is obviously bad. In prod code, you'll want to use more targetted state resetting.)
- The "tears" are just a
MotionDiv
with random velocity, all originating from the location of the eyes.
const random = (scale: number) =>
Math.random() * scale * (Math.random() > 0.5 ? -1 : 1);
const rightEye = { startX: 125, startY: 125 };
const leftEye = { startX: 55, startY: 125 };
export const TearsInTheRainInner = () => {
return (
<World>
<img src="./tears-in-the-rain.png" />
{Array.from({ length: 100 }).map((_, index) => {
const eye = index % 2 ? rightEye : leftEye;
return (
<MotionDiv
key={Math.random()}
height={5}
width={5}
{...eye}
vx={random(3)}
vy={random(3)}
style={{
borderRadius: "5px",
borderColor: "blue",
background: "blue",
}}
/>
);
})}
</World>
);
};
export const TearsInTheRainFixture = () => {
const [key, setKey] = useState(Math.random());
useEffect(() => {
const tears = () => setKey(Math.random());
const unsubscribe = setInterval(tears, 800);
return () => {
clearInterval(unsubscribe);
};
});
return <TearsInTheRainInner key={key} />;
};