Spring physics in practice
What we learned shipping it to a few million phones, and what we would do differently next time.
Essays, process write-ups and the occasional strong opinion about easing curves. A long page, on purpose.
We started drawing whole pages on a GPU canvas for a boring reason: we kept wanting effects that the browser could not do, and kept rebuilding the same layer of glue to fake them. At some point it was easier to draw everything ourselves and keep the DOM for what it is good at.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
What we learned shipping it to a few million phones, and what we would do differently next time.
A walk through the decisions, the dead ends, and the one constraint that ended up shaping everything.
Notes from a workshop: the exercises, the arguments, and the prototype we built by the end of the day.
Why the details nobody asks for are the ones everybody notices, with examples from three recent projects.
The checklist we run before anything moves on screen, and the reasons behind every line of it.
A short history of how we got here, a few charts, and a strong opinion about default easing curves.
Every interface makes promises with how it moves. A button that springs back says it was pressed; a page that eases to a stop says the content has weight. Break those promises a few times and people stop trusting the thing, even when they could not tell you why.
We started drawing whole pages on a GPU canvas for a boring reason: we kept wanting effects that the browser could not do, and kept rebuilding the same layer of glue to fake them. At some point it was easier to draw everything ourselves and keep the DOM for what it is good at.
That turned out to be the right split. The DOM still lays everything out, handles focus, selection and screen readers, and nobody has to learn a new way to build a page. The canvas just draws what the DOM describes, a frame at a time, and adds the things CSS cannot.
The hard part was never the drawing. It was making sure the canvas and the page never disagree: about where something is, how big it is, whether it is hovered, or which one is on top. Every bug we hit in the first months was some version of that disagreement.
“A page that scrolls natively is the one thing users never forgive you for breaking.”
Tomás Reyes
Scrolling taught us the most. A page that scrolls natively is the one thing users never forgive you for breaking. So the canvas follows the real scroll, smooths it a little, and gets out of the way the moment you grab the page with a finger.
Text was the second lesson. Rendering type yourself is a deep hole, so we do not: the browser lays out every line, and we copy its positions exactly. Selection, carets and copy and paste all still work, because underneath it is still just text.
Performance is a budget, not a feature. On a 90 Hz phone a frame has eleven milliseconds, and the browser wants some of them. We measure every change on a real device, in milliseconds per frame, and we throw away ideas that look clever and cost more than they save.
Some of those ideas hurt to throw away. Rendering a page at lower resolution while it flies past sounded perfect on paper. On the phone it made things slower, because the page was already being drawn smaller while it moved, and resizing buffers is not free.
Designers on the team now prototype in the real thing. A hover state is a few lines of CSS and a transition, exactly as it would be anywhere else, and it moves the way it will in production, because it is production.
“We measure every change on a real device, in milliseconds per frame.”
Mira Lindqvist
The best compliment we get is that nobody notices. People scroll, tap and read, and the page feels a little more alive than they expected. Then they go back to whatever they were doing, which is exactly how it should be.
There is still plenty we have not solved. Very long pages with hundreds of images push memory hard on older phones, and we are not yet happy with how text looks mid-transition. This page exists partly to keep us honest about both.
If you are thinking about doing something similar, start smaller than you think. Draw one card. Make it scroll perfectly with the page. Then make it hoverable, then selectable, then fast. Everything else is the same problem again, many times.
Further reading: how the text is copied, the pixel grid
and why underlines skip descenders. We tried a DOM overlay first, wrote it with a
tpyo, and kept an DPR budget.