Web animation gotchas - HTTP 203

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
JAKE ARCHIBALD: OK. Audio synchronization time, 3, 2, 1. DAS SURMA: 3, 2, 1. JAKE ARCHIBALD: Brilliant. DAS SURMA: It's a little bit like a Power Ranger. It's like, audio synchronization time, [GRUNTING].. JAKE ARCHIBALD: The worst Power Rangers ever. The power to synchronize audio. [MUSIC PLAYING] So I've been doing animation on the web for probably about 20 years now. I used to write a library for it, the BBC used back when I worked there. I'd say more recently, most of the animation work I do is like, when I do slides for talks. It's one of the times I really throw lots of weird animation effects and stuff to try and keep people awake while I'm droning on about something boring and standardsy. 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 that some of the tools we have at our disposal are not so great and could be a lot better. DAS SURMA: Yeah, this rings very much true to home for me. True to home? Is that a thing? I think I just mis-Englished. Either way, but yeah, I remember when we were writing procs that I had to write an animation library for the Canvas space animations, and you were like, I've done this before. Let me tell you of all the struggles you're going to encounter. JAKE ARCHIBALD: 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. 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 GreenSock or the library I wrote. I used GreenSock because I used to be a Flash developer, I don't know if I've mentioned that enough times, but that GreenSock 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 set timeout or, later, request animation frame, which would be the proper way of doing it. But with this you got compositing, and the animation happening off the main thread. So 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 throw out your jQuery and your GreenSock or whatever, and that's what I did. And I thought, 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, and now I want the new thing to fade in. So I'm gonna create it, give it an opacity of zero, add it to the document, and then transition it to full opacity. Cool. Yeah, cool? DAS SURMA: Eh, no. JAKE ARCHIBALD: Go on, tell me about it. DAS SURMA: The only reason I know this is because we, actually, had a question about this, I think, on the first [INAUDIBLE] quiz at some point. JAKE ARCHIBALD: Yeah. DAS SURMA: And then we started testing, and discovered that browsers were still behaving differently here, actually. So I think because this is all JavaScript, there is no yielding to the browser between the append and the subsequent changes of the styles. So the browser [INAUDIBLE] the rendering engine only sees the transition and an opacity of one, because the previous change is invisible to the rendering engine. JAKE ARCHIBALD: Spot on. Yes, absolutely. The browser never sees the opacity zero, it never thinks about it in terms of styling. It's just a JavaScript value, and it's overwritten 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, OK. 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? DAS SURMA: Actually, I cannot. JAKE ARCHIBALD: 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 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. DAS SURMA: So you should check for-- yes, OK. JAKE ARCHIBALD: 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 more generic animation system, that's something you have to check. Because people will say, 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'll be checking what the input value versus the computed value. But even with opacity, 0% and 0 are the same thing. The computed value will always come out as zero, but the user of the library might have given you 0%. 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 display none or it's inside an element that's displaying none 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 we're going to do this time is use CSS animations, because this arrives slightly later than transitions, but we've had them for at least a decade now I'd say. This just works. So I'm setting the opacity, and then it's animating from opacity zero to it's 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. DAS SURMA: Yeah, this is my current go to preference for making things appear when they get added to the DOM. Just like, it automatically plays once, it stops at the end. It's brilliant. JAKE ARCHIBALD: Which is great, isn't it? Because we are 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, still have the event bubbling issue, but this time it's fine if the value's the same. So if you're animating from opacity zero to opacity zero, you'll still get the event, which is great. DAS SURMA: Oh really? JAKE ARCHIBALD: Yes. DAS SURMA: I did not know that. JAKE ARCHIBALD: You won't get it if it's display none or inside an element that's display none. So 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 providing my one and only keyframe, which is opacity zero. And I give it an offset of zero, which means that this keyframe is at the start of the animation. The offset between zero and one, and off it will animate. 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. DAS SURMA: Does this currently working Chrome, this exact syntax? JAKE ARCHIBALD: Yes it does. Yeah, so what you're thinking there is-- and for a long time you had to specify at least two keyframes. DAS SURMA: Yeah. JAKE ARCHIBALD: 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. It wasn't, but yeah, this works now. And it's well supported, the Web Animation API-- if you're targeting modern browsers-- it's everywhere. It arrived to Safari more recently than the others, but it's there now. So this is how I do animations now. DAS SURMA: Well, it shows that Chrome support of Animation API from 44 to 84, but I think that wasn't complete support, right? So that table seems to be a tiny bit misleading. JAKE ARCHIBALD: 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've been 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 display none 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. DAS SURMA: Of course there is. JAKE ARCHIBALD: 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 yeah, that's a workaround that works today. I want to 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 going to remove it from the document. Something like this. Can you see the gotcha? DAS SURMA: Um. I mean, I'm immediately drawn to just one keyframe where, I don't know if it's implicit that it's at the end of the animation if you just specify one keyframe. JAKE ARCHIBALD: That's a good point, yes it is. But if you provide one keyframe, it implies the end keyframe for some reason. DAS SURMA: OK. JAKE ARCHIBALD: So we gotcha here-- DAS SURMA: Then I don't see it. JAKE ARCHIBALD: 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. DAS SURMA: Yeah, that's why I usually-- I almost always define the fill behavior because I just don't trust the defaults. JAKE ARCHIBALD: There we go, job done. Fill forwards. And, yep, now it works. DAS SURMA: So just to be clear, that means when the animation ends, and in the timeline you have keyframes, and you play from one key to the next. Fill tells you what happens when the time is outside these two keyframes. Should you loop? Should you fill? Or there's probably others as well, but yeah. That's what this probably does. JAKE ARCHIBALD: Looping is different. It's forwards and backwards in both of the values. So yes, if you have an animation that is delayed, a fill of backwards means it will apply the first keyframe 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 because of this. It doesn't work. The animation has higher priority. DAS SURMA: Oh, because the animation is technically still running, I guess? JAKE ARCHIBALD: 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, right. So we need to fix this some other 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. DAS SURMA: Oh, I didn't know that one. That's neat. JAKE ARCHIBALD: 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. DAS SURMA: Just don't animate, ever. JAKE ARCHIBALD: Don't animate, it's awful. People hate it. No, I'm going to make an animateTo function, which takes the element and then the [INAUDIBLE] 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 start values as we saw before. So we're going to fix that. I'm going to create a finish event. I could use a promise, except for Chrome, that bug. So I'm going to use the events, and then I'm going to call commitStyles. 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 dump out in line 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 maybe that shouldn't be the behavior. Maybe this should just work. And if you call this 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 que. And you end up with the inline, so it's held the final position, but it means that you can update inline styles, like run, and it will all work. DAS SURMA: And for the people already wondering, I'm pretty sure this event listener doesn't leak. JAKE ARCHIBALD: 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. DAS SURMA: Because animation can get garbage collected, yeah. JAKE ARCHIBALD: Yeah, I guess that the listener could leak if you were holding onto the animation object, because you could restart the animation. And in which case, you could get finish 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 finish event, 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 some time 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 it some way, but that is not in the spec at all. So maybe that will change in future. DAS SURMA: I mean, it would make sense 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 que. But yeah, that definitely is something that needs to be specified and written down that that is how it's supposed to behave. JAKE ARCHIBALD: Oh, and even if the values are synchronized, it's more about what you're seeing on the screen 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 event in synchronization with that thing happening. But there's no spec for how the composer 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 que. There you go, like, 20 years it's taken me to get to this. A combination of my own learning, and also features being added to the browsers. DAS SURMA: You had to use fill in the end, after all. JAKE ARCHIBALD: After all, but there is discussions in the spec to avoid having to do this. Where if you call commitStyles at the end in the finish event, 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. DAS SURMA: Having fiddled with writing an animation "engine" which makes it sounds way fancier than it was in procs. I appreciate the simplicity of what you boiled it down to. So put it in a gist, I might want to copy it. JAKE ARCHIBALD: Yes, I'll link to it in the description, along with the various bugs, and spec, bits, and arguments, and stuff that are happening. And I'll put that down in the description. [INAUDIBLE] Did you clap that second time? DAS SURMA: Yeah. JAKE ARCHIBALD: Oh, OK. DAS SURMA: It's just perfectly in sync. JAKE ARCHIBALD: My earphones-- DAS SURMA: Or maybe the sound-- well, maybe the GVC swallowed it. JAKE ARCHIBALD: Yeah, probably. DAS SURMA: I could see that happening.
Info
Channel: Google Chrome Developers
Views: 19,733
Rating: undefined out of 5
Keywords: GDS: Yes, web animation, web animation common mistakes, web animation examples, coding animations, java animations, website animations, web design, making engaging websites, animations on the web, web animations tools, new videos from Chrome, Chrome, Chrome Developers, Web, Chrome devs, developers, Google, tech, tech videos, web, videos for developers, css, javascript, performance, new in tech, new in web, new in chrome, Halloween, HTTP203, Jake and Surma
Id: 9-6CKCz58A8
Channel Id: undefined
Length: 20min 10sec (1210 seconds)
Published: Tue Nov 10 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.