Time-Based CSS Animations
241 points
13 days ago
| 13 comments
| yuanchuan.dev
| HN
nikisweeting
13 days ago
[-]
I also recommend using negative animation delay values if you want to control the progress via JS.

e.g. animation-delay: -1500ms will begin the animation immediately but jump to 1.5s into the animation. Controlling this value with JS lets you effectively scrub through CSS animations via JS, making every animation compatible with game-engine style compute-update-render tick loops.

https://developer.mozilla.org/en-US/docs/Web/CSS/animation-d...

reply
9dev
13 days ago
[-]
That should also enable iOS style, scroll-offset bound animations, right?
reply
afavour
13 days ago
[-]
Scroll linked animations are the best way to do that:

https://web.dev/shows/http-203/Qf5wdXOxW3E

Sadly no Safari support yet.

(attaching an active JS event listener to scroll is considered bad because it means your scroll movement gets tied up in the JS event loop. On a fast device it’ll probably be fine but on a slower one things can start getting choppy pretty easily)

reply
easyThrowaway
12 days ago
[-]
Unfortunately if you're doing any sort of DOM manipulation linked to scroll events it will cause stalls in the main rendering thread, at least on Chrome, and slow down even on faster machines.
reply
afavour
12 days ago
[-]
The exception there is hardware accelerated CSS transforms, which will work great.
reply
rasz
12 days ago
[-]
"hardware accelerated" CSS transforms take ~30% of one CPU thread in Chrome, thats a very specific definition of great :(
reply
easyThrowaway
12 days ago
[-]
I should check it on a more recent release, but at least on Chrome 80-ish and earlier CSS transforms were evaluated fully in software mode when blending filters or transparency effects were active, at least on mac os.
reply
afavour
11 days ago
[-]
That’s not what I’ve observed. But in any case, the point in the context here is that such CPU usage doesn’t tie up the JS event loop.
reply
nikisweeting
13 days ago
[-]
Yeah you can do tons of interaction-dependent stuff without losing the GPU-accelerated nature and declarative style of CSS animations.

We built a high performance animation library on this concept: https://github.com/Monadical-SAS/redux-time

reply
krebby
13 days ago
[-]
For anything more advanced than a simple easing function or some basic keyframes on one or two channels you'll quickly run into the limitations of this approach.

I've been using Theatre.js the last few years and really loving it. It's a library divided into two parts; one is a studio UI with a timeline for editing keyframes and bezier curves, and the other is a runtime for taking those keyframes and interpolating values in relation to a timeline. Try it for anything that requires coordinated animations.

https://www.theatrejs.com/

reply
ulrischa
13 days ago
[-]
Very impressive. But somehow I think we just reached the point coming close to what we were able to do with flash decades before. I do not want flash back but a more user friendly tool to create css animations. If somebody knows any I would be glad.
reply
TekMol
13 days ago
[-]

    CSS now has enough Math functions supported,
    particularly mod(), round(), and trigonometric
    functions

    CSS can not start a timer like JavaScript does,
    but nowadays it's possible to define a custom
    variable with the CSS Houdini API to track time
    in milliseconds. 
Why do we need all this when we have JavaScript?

Shouldn't the drawing layer be concerned with drawing primitives? Why put more and more of the higher layers into it?

reply
joestrong
13 days ago
[-]
Don't forget that CSS animations are hardware accelerated. If you're doing trig in JS for animations, you're probably also manipulating the DOM to update the UI every millisecond, which is very expensive
reply
mattgperry
13 days ago
[-]
Most CSS animations aren't hardware accelerated. Only a few values like opacity and transform.

Updating the DOM (as in innerHTML) is always expensive because it triggers layout. This is true whether you're doing it from JS or a CSS trick like on this page.

Finally this approach is using CSS custom properties. These are slow - slower than JS for most things.

If you stop all animations on this page and profile it via Chrome you can see this in effect. The root node is animating a CSS variable. 117 elements have their style recalculated. Every frame - yet no animations are running. There's also a tiny paint triggered too, obviously there's been no changes so it is tiny in this instance, but a paint is always triggered when a CSS variable updates.

This is why animating x and y separately via CSS and a style like `translate(var(--x) var(--y))` would be worse than animating them via JS ala Framer Motion/GSAP.

reply
amsheehan
13 days ago
[-]
We shouldn't diminish the power of transform here. It's not just one property like opacity. You can animate quite a bit on the compositor with transform
reply
agust
13 days ago
[-]
Just one nitpick: the DOM is not updated by CSS here, only the value of the CSS variable is. (It will indeed cause style recalc and paint though, and result in poor performance as we can see with the demos.)
reply
SirHound
13 days ago
[-]
Yeah true tho I’m referring to the counter being set via the content style, which doesn’t update the DOM as such but does/can change layout
reply
pax_americana
11 days ago
[-]
If you’re doing animations in JS, better do it with the Web Animations API since it’s also hardware accelerated.
reply
tuyiown
13 days ago
[-]
> Shouldn't the drawing layer be concerned with drawing primitives?

CSS is not a drawing layer. The drawing is driven by the DOM, either setup by HTML or updated through JS. CSS only piggyback on it by giving parameters to the painting engine about what to do with dom, essentially overriding the defaults parameters. You can add dom blocks, modify positioning behavior with complex algorithms, even margins are not really trivial.

I don't think this is a great solution, but people started this to be able to modify colors during dom rendering without having to update their HTML code, probably because updating HTML code was not fun at all, but I really think the wrong problem was addressed. Those were the «webmaster» times, so there's also that.

reply
gibbitz
13 days ago
[-]
One thing that comes to mind is separation of application logic and presentation. It's easier to read your fetch logic if it doesn't include loader DOM element geometry progress logic (calculating rotation degrees and applying inline styles etc.). I know it isn't popular to keep the chocolate separate from the peanut butter these days, but conventions like this draw hard lines that make it easier to reason about where code will be found and where to put it.
reply
onion2k
13 days ago
[-]
Regardless of anything else, JS is a single thread (mostly, there are workers of course) so anything you can do to offload work somewhere else is a good thing for keeping the UI running at a solid frame rate.
reply
SirHound
13 days ago
[-]
This doesn’t offload work to another thread though. And it’s actually more costly as it trigger style recalculations throughout the tree even when it no-ops.
reply
toxik
13 days ago
[-]
Just wait until we have WebSockets in CSS!

Jokes aside, CSS is declarative and while there is overlap between problems solvable by JS and CSS, I think that’s only natural.

I’m actually really excited for all the new features added to CSS lately, less interpreted code is better.

reply
RunSet
13 days ago
[-]
> Why do we need all this when we have JavaScript?

In practice, mostly so google can still invade the privacy of users after they disable javascript.

No user ever asked for this.

reply
shepherdjerred
13 days ago
[-]
No user ever asked for any of these APIs or technologies, but that doesn't mean they can't be useful to developers.
reply
matheusmoreira
13 days ago
[-]
How can CSS be abused to invade our privacy?
reply
vinnymac
13 days ago
[-]
I can think of a few ways, but here is an example where CSS triggers a request to track a user: https://underjord.io/is-this-evil.html
reply
matheusmoreira
13 days ago
[-]
The reason the server can track who is hovering over links is the browser apparently requests the background image only when the interaction happens. I certainly didn't expect that!

This seems to be an example of browser optimization leaking private information. There's an obvious way to fix that: deoptimize. Instead of lazily downloading resources as they are needed, request all the linked resources when the page loads, and do it only once. Now they can't tell whether the requests were due to user interaction or just normal browser behavior.

I wonder if uBlock Origin can deal with this. I know it's got a lot of options for blocking weird information leaks like this, webfonts being an example. Firefox also has fingerprinting resistance, I wonder if it resists this.

reply
simple10
13 days ago
[-]
The article uses custom css @properties which are awesome and have 88% browser support [1].

One thing to watch out for is differences in how browsers handle setting the fallback initial-value. Chrome will use initial-value if CSS variable is undefined OR set to an invalid value. Firefox will only use initial-value if the variable is undefined. For most projects, this won't be an issue, but for a recent project, I ended up needing to use javascript to set default values in Firefox to iron out the inconsistency between browser implementations.

[1] https://caniuse.com/?search=%40property

reply
andypants
13 days ago
[-]
If anybody wants to try the demos in firefox, go to `about:config` and toggle on `layout.css.properties-and-values.enabled`.
reply
simple10
13 days ago
[-]
Here's how to set the property value in Javascript if needed:

Get property value defined in :root{}:

getComputedStyle(document.documentElement).getPropertyValue("--some-custom-css-var");

Set property value:

document.documentElement.style.setProperty("--some-custom-css-var", "some value");

This can work as a polyfill for browsers that do not yet have CSS @property support enabled.

reply
chrisjj
13 days ago
[-]
Interestingly without 'Experimental feature flags' demos before mod() work, though mod() and sin() do nothing - on latest Android Chrome.
reply
keepamovin
13 days ago
[-]
I wanted this for a while so I could do a 'box-breathing' thing. With the inhale-hold-exhale-hold stages all being customizable to fit your body and go for the effect you wanted.

Could not figure out a way to do it (sans JS or insane complexity) without something like what's on this page. Holy stylesheets! This page has really creative demos!!! Haha :)

reply
spartanatreyu
11 days ago
[-]
Isn't that just an animation curve in css?
reply
keepamovin
7 days ago
[-]
I couldn't figure it out. I mean, could you have a go at it? Like I want each of the 4 edges to be parameterizable (by a CSS var) so I can add a slider to adjust how long the circle stays in each of the states.

I'm thinking of a customizable CSS version of: https://quietkit.com/box-breathing/

reply
spartanatreyu
5 days ago
[-]
Well I could replicate that example with an animation curve using only css:

https://codepen.io/spartanatreyu/pen/YzbPjwg?editors=1100

But if you want each step's duration to be parameterizable then you'll need to reach for js, I'd recommend the Animation API.

reply
simple10
13 days ago
[-]
Really well written article. Chrome hasn't yet released CSS support for mod() so most of the examples on the page don't animate unless you're using the preview release of Chrome.

If you're not needing to control ticks and just want smooth CSS animations in all browsers, the FLIP method is useful:

https://medium.com/outsystems-experts/flip-your-60-fps-anima...

reply
silvestrov
13 days ago
[-]
The animation at the bottom of the page is very impressive. It looks very much like a <video> rather than <div> elements.
reply
rtcode_io
13 days ago
[-]
https://RTCode.io, a real-time web playground without reloads (and without state resets) might greatly benefit anyone working with CSS animations/transitions!

Demo on https://RTCode.io

(To import pens, simply replace the hostname with xcodepen.io in your URLs.)

reply
simple10
13 days ago
[-]
Has anyone tried RTCode yet? Looks interesting but the homepage is a bit confusing as to whether it's a CodePen replacement with realtime support or can also be used for production?
reply
rtcode_io
13 days ago
[-]
We want to be somewhere between CodePen and Vercel (but with a focus on web standards): a real-time full-stack playground with global hosting and in-editor→edge-deployed workers(/functions).

RTCode is in private alpha. (Currently building Code Versions: diff, rollback, A/B testing; and Code Search: full-text search across all your active deployments).

Our playground and network have proven stable in production in several large enterprises for the last year.

I can answer any questions via email (support@elefunc.com) or chat (https://meet.elefunc.com).

reply
KRAKRISMOTT
13 days ago
[-]
Anyone want to try replicating YouTube's upvote counter fly-in animation?
reply
hartator
12 days ago
[-]
Super good job. The clock looks super polished.
reply
GalaxyNova
13 days ago
[-]
Whatever you're doing isn't working on Firefox.

Edit: Just read the disclaimer, whoops!

reply
kevingadd
13 days ago
[-]
Oddly on my system, it works better in Firefox (it's not animated, but all the elements are there) than it does in Chromium (completely blank where the animated demo should be).
reply
capitainenemo
12 days ago
[-]
Same here. chromium, blank, firefox animating fine.
reply