 OK, audio synchronization time, three, two, one. Three, two, one. Brilliant. There we go. A little bit like Power Rangers, like audio synchronization time. Oh, the worst Power Rangers ever, the power to synchronize audio. So I've been doing animation on the web for probably about 20 years now. I used to write a library for it. That's the BBC used back when I worked there. And I'd say more recently, mostly animation work I do is like when I do slides for talks, it's kind of one of the times I really throw lots of weird animation effects and stuff to kind of to try and keep people awake while I'm drawing on about something boring and standard. But I don't normally give my credentials up front like that. But the reason I'm doing that now is because I still get caught out by loads of animation stuff on the web. I think it's really hard. And I think some of the tools we have at our disposal are not so great and could be a lot better. Yeah, this rings very much to me. True to home, home, is that a thing? I think I just mis-englished either way. But yeah, I remember when we were writing prox that I had to write like an animation library for the canvas based animations. And you were like, I've done this before. Let me tell you of all the struggles you're going to encounter. Yes, exactly. So let's talk about two relatively basic animations. Fading something in, you just saw that there. And fading something out. There you go. But even those have a ton of complexity and gotchas. Yeah, and I only feel like I'm starting to come to some conclusions of how these basic animations should be done in a way that's safe and avoids all of the gotchas. I would say way back when I would use JQuery or Green Sock or the library I wrote. I used Green Sock because I used to be a Flash developer. I don't know if I've mentioned that enough times. But Green Sock was an animation library for Flash that then moved to the web. So I used that for a bit. But then we got these things, CSS transitions. So the older animation libraries were just changing inline styles over time, either by using setTimeout or later, requestAnimationFrame, which is the proper way of doing it. But with this, you've got compositing and the animation happening off the main thread. So like, you know, opacity and transforms. Even if your main thread is janky, these animations will run nice and smooth. Pretty good for things like hover effects because no JavaScript needed. There you go. You can, you know, throw out your JQuery and your Green Sock or whatever. And that's what I did enough for. I'm just going to use these. This is it from now on. But I was also using JavaScript for interactions. So I wanted to drive transitions with JavaScript. So the user clicks a button. Now I want the new thing to fade in. So I'm going to create it, give it an opacity of zero, add it to the document and then transition it to full opacity. Cool, yeah? Yeah, cool? Yeah, I didn't know. Go on, tell me about it. The only reason I know this is because we actually had a question about this, I think on the first big web quiz at some point, and then we started testing and discovered that browsers were still behaving differently here, actually. So I think because, you know, this is all JavaScript, there is no yielding to the browser between the append and the subsequent changes of the styles. So the browser, the rendering engine, only sees it to the transition and an opacity of one because the previous change is invisible to the rendering engine. Spot on, yes, absolutely. The browser never sees the opacity zero, never thinks about it in terms of styling. It's just a JavaScript value and it's overridden before it thinks about styling anything. So the way you work around this is either by waiting until the next frame, which means using request animation frame twice. Of course it's twice because request animation frame is before the next frame. So if you want to push into the next frame, you have to use it twice, or you hack around it. So all I'm doing here is using getComputedStyle and just calling getComputedStyle is not enough. You have to then read a property from it and that is when the browser will think about the style and it thinks about that opacity zero and then it will transition to one. Fine, okay, let's fade something out. There we go, pretty good, good start. But what if like opposite to before, I want to remove it once it's faded out? So let's give that a go. You get an event, so there we go. Job done, can you see the gotcha here? Actually, I cannot. And neither did I for a long time until it caught me out and it's that events bubble. So I could get this event not for when my transition finishes, but from when some other thing finishes its transition. And once you start getting into more complex transitions where like something inside fades out slightly sooner, you start finding your things disappearing from the document much sooner because you're actually catching that event from another animation, so. So you should check for, yes, okay. So that caught me out as well. But if that element was already zero opacity, you get no transition. So you don't get an event, which is great. So now, if you're building a sort of more generic animation system, that's something you have to check. Because people will say, like, try and, you know, make this go to zero and then remove it. And if it's already zero, they just want it to be removed. This kind of code is extremely fragile because you're checking, you know, you'll be checking what the input value versus the computed value. But even with opacity, zero percent and zero are the same thing. The computed value will always come out as zero, but the user of the library might have given you zero percent. You're not going to get a transition from those two because they compute to the same value. And we don't have the tools to easily convert arbitrary values into computed values. Also, if it's displaying on, or it's inside an element that's displaying on, or it's not in the document, you're not going to get a transition event. So this is just not going to work properly. So we'll throw all of that out and start again. What I'm going to do this time is use CSS animations. Because this arrives slightly later than transitions, but, you know, we've had them for at least a decade now, I'd say. This just works. So, you know, set in the opacity, and then it's animating from opacity zero to its computed style, which is going to be one. And I don't have to do any computed style hacks. This will just work, which is great. Yeah, this is my current go-to preference for making things appear when they get added to the DOM. Just like it automatically plays, just plays once, it stops at the end. It's brilliant. Which is great, isn't it? Because we're now in a situation where it's easier to use CSS animations for transitions than CSS transitions. Great, excellent. But yes, I agree with you. This is moving towards how I do things with animations these days. So what about the fade out and remove? That's a slightly different model. So I'm going to add another keyframe animation this time fading out. And off we go. We still need to deal with the bubbling. So I'll have the event bubbling issue. But this time it's fine if the value is the same. So if you're animating from opacity zero to opacity zero, you'll still get the event, which is great. But really, I did not know that. You won't get it if it's displaying non or inside an element that's displaying non. So, you know, we're making steps forward, but we're not there yet. But then enter the web animations API. This just works, which is great. So I'm saying like providing my one and only keyframe, which is opacity zero, and I give it an offset zero, which means that this keyframe is at the start of the animation. So it's between zero and one. And off it will animate from opacity zero to whatever the style resolves as, which would be one by default, but it could be something else. So this works in those cases as well. Does this currently work in Chrome? This exact syntax? Yes, it does. Yeah, so what you're thinking there is, and for a long time you had to specify at least two keyframes. You don't anymore. This just works. It will figure out the other one automatically, which is great. I wish it was there from the start. But yeah, this works now. And it's well supported, right? The web animation API is if you're targeting modern browsers, it's everywhere late. It arrived to Safari more recently than the others, but it's there now. So this is how I do animations now. Well, it shows that Chrome supported web animations API from 44 to 84, but I think that wasn't complete support, right? So that table seems to be a tiny bit misleading. That's true. So in terms of the multiple keyframe thing, I think it was actually Firefox that landed that first. So credit to them. But yeah, Chrome supports it now. So yes, although that chart shows everything looking good for a long time in Chrome, there are features that I'm showing today that arrived in between then and now. And I didn't look up when, so don't ask me. But they're being in Chrome for multiple versions now, so they're safe to use. So what about fading out? There you go, that's it. JavaScript animations, they finish even if it's displayed non or if it's not in the document. So you don't have to overcome that as well. Actually, there is a bug here in Chrome. Of course there is. Yeah, bloody Chrome, spoiling all our fun. The promise resolves too late. So the animation will finish and then sometime later, the promise resolves, which if you're doing accurate animation work, this is far too late. So instead of the promise, use the events. This is fixed in Canary, so it's on its way to stable, but that's a workaround that works today. I wanna talk about more gotchas. What if I wanted to fade something from whatever it is now to not point to opacity and leave it, I'm not gonna remove it from the document. Something like this. Can you see the gotcha? I mean, I'm immediately drawn to just one key frame where I don't know if it's implicit that that's at the end of the animation if you just specify one key frame. That's a good point. Yes, it is. But if you provide one key frame, it implies the end key frame for some reason. Okay. So the gotcha here. Then I don't see it. Yeah, and I mean, if you ran this code, you would see it immediately. Because here's what it does. Boop. The animation ends and then the animation is gone. So everything that the animation was affecting stops being affected. So it- Yeah, that's why I usually, I almost always define the fill behavior because I just don't trust the defaults. There we go. Job done. Fill forwards. Yeah, now it works. So just to be clear, that means when the animation ends, in the timeline, you have key frames and you play from one key to the next. Fill tells you what happens when the time is outside these two key frames. Should you loop? Should you fill? Or there's probably others as well. But yeah, that's what this property does. Looping is different. It's forwards and backwards and both are the values. So yes, if you have an animation that is delayed, a fill of backwards means it will apply the first key frame while it's waiting for the animation to start. A fill of forwards means that once the animation is done, it will hold the last state. Both does both. But I have come to the conclusion that this is a huge gotcha. And every time I have done this, it has bitten me at some point later on, especially if I'm doing something a little bit more complicated. And it's because of this. It doesn't work. Like the animation has higher priority. Oh, because the animation is technically still running, I guess? Effectively, it's still held. And while an animation is running, or it's still happening to some extent, it has priority over almost everything. The only thing it doesn't have priority over is important styles. When you do exclamation important, it doesn't have priority over that. But yes, if your solution is to use important styles, you've done something wrong earlier on. So we need to fix this some of way. And actually, there's a method, which I'm going to struggle to remember, like document.getanimations. And you will see all of these animations that have fill forwards. Anything that ever ran, essentially, is going to be in there. Oh, I didn't know that one. That's neat. Yes, because in CSS, you will remove that animation by removing the style resolution which has the animation property on it. So if you remove that class name, then it removes the animation. With web animations, you need to deliberately cancel the animation if you wanted that. So what's the answer? And it's taken me years to figure out, and so I might still be wrong. But here's the conclusion that I've come to. So strap in. Just don't animate ever. Don't animate. It's awful. People hate it. No, I'm going to make an animate to function which takes the elements and then the animation arguments, the keyframes and the options. I'm going to create the animation and return it. So this will run to completion and then snap back to the style values as we saw before. So we're going to fix that. I'm going to create a finish events. I could use the promise except for Chrome, that bug. So I'm going to use the events and then I'm going to call commit styles. And this is an API that not many folks seem to know about. What it does is it commits inline styles for the animation at its current position. So you can call this multiple times in the animation and it will just like dump out inline styles for whatever's happening right now. There's a problem here though because the current position is finished. So everything has already snapped back to the start. So this doesn't work. There is a spec disagreement on this already that like maybe that shouldn't be the behavior. Maybe this should just work if you call it after it's finished it will give you the final styles but that's not the way browsers behave. So I'm going to add fill both. And I know I said that was bad and it is, but we need it. So we have those styles when the animation ends. And then once committed cancel the animation which removes it from the queue and you end up with the inline styles. So you've got that it's held the final position but it means that you can update inline styles later on and it will all work. And for the people already wondering I'm pretty sure this event listener doesn't leak. No, because it fires once and then the browser knows it's not going to be fired again especially once the animation is garbage collected. So the animation can't be restarted. Because animation can get garbage collected, yeah. Yeah, I guess that the listener could leak if you were holding on to the animation objects because you could restart the animation and in which case you could get finished again. But yeah, it's otherwise fine. It's not something to worry about. My previous solution to this was to apply the inline styles manually and I wouldn't use fill at all because I hate it. But this raises a question about composited animations that I've seen browser folk argue about. So if you've got an animation that completes you get your finished events and then you set the inline styles. What if your main thread is blocked at that point and the animation is happening on the compositor? Some people would argue that the animation would complete snap back to the original values and then sometime later you get your event. So you would still have a visual flicker. Like animation completes, it snaps back and then you apply the inline styles. I couldn't get any browser to actually do this. So it seems like it's a bug they've solved or worked around in some way but that is not in the spec at all. So maybe that will change in future. I mean, it would make sense to set to somehow tie the value that the animation object is representing to the time that the event is being processed or the event is created and put in the event queue. But yeah, that definitely is something that needs to be specified and written down that that is how it's supposed to behave. I mean, even if the values are synchronized it's more about what you're seeing on the screen that you need that needs to be synchronized. And what it seems like browsers do is they run the animation on the compositor. So off the main thread, but then the removal of the animation where it would snap back to the start point that is still handled on the main thread. So that means that you get your events in synchronization with that thing happening but there's no spec for how the compositor works. But browsers seem to be doing the right thing but to defend against changes in terms of how the compositor works with the main thread this code will defend against that because it's going to use that fill forwards or fill both in this case to hold that final frame until your JavaScript gets to run and it's your JavaScript which removes the animation from the queue. There you go, like 20 years it's taken me to get to this combination of my own learning and also features being added. And you know what? You had to use fill in the end after all. After all, but there is discussions in the spec to avoid having to do this where if you call commit styles at the end in the finish events, it will apply the final styles which that's just what I want. That's what I always wanted. Just make that easy for me please. But in the meantime, I at least now have a simple small function that does exactly what I want. And that's all I've got. Having fiddled with writing an animation engine which makes it sound way more fancy. Way fancier than it was in Prox. I appreciate this simplicity of what you boiled it down to. So I'll put it in a gist. I might want to copy it. Yes, I'll link to it in the description along with the various plugs and spec bits and arguments and stuff that are happening. And I'll put that down in the description. There's loads of it. Did you clap that second time? Yeah. That's just perfectly insane. My earphones just fall down. Or maybe the sound, or maybe the GPC swallowed it. Yeah, probably. I could see that happening.