Video details

NG-BE 2021 - Lightning Talk - Mike Hartington - High performance design of angular

Angular
12.09.2021
English

https://ng-be.org/talks/2021/12/3/mike-hartington
Mike is a developer, Angular GDE, and Ionic Developer Advocate who's been working in the mobile landscape for most of his professional career. When he's not working Ionic itself, Mike works with community members and helps them succeed at mobile. In his spare time, he’s an aspiring woodworker, occasional musician, and craft beer lover.

Transcript

Hi, my name is Mike Hartington. I am a developer advocate at Ionic, and we're going to look at how we can take design implementations, do them in Angular and do them in the web and make sure that they are fast and performant. If you have any questions afterwards, feel free to reach out on Twitter. You can tweet at me and Hardington. Happy to know what your thoughts are. Now. This talk does have a secondary title, one that I think really captures what points I'm trying to get across, and that is we can have nice things. I know I hear it a lot from my friends in the native world that the web just can't do everything native can. We're never going to be able to have really high performance animations. We're never going to be able to get great interactions or really cross that threshold of native quality. And I wholeheartedly reject that. I think that you can have really good performing animations, really good interactions, and really good design using the web. We just have to try a little bit harder. We have all these expectations for what is a great app, right? It's fast performant. It's engaging. Interactions are reactive to user clicks and taps. We really want to get this deals native kind of badge of honor. A good web app should be able to feel native not only in terms of our JavaScript code, but also designs and animations that we want to add to this. So I spent about six months doing some research and trying to figure out how to get that native quality animations and designs using the web. And I use the Apple Music app as my starting point because it is a pretty good app, but it also has these really nice rich interactions and animations embedded right inside of it. Here we can see that we have different animations as the play pause state our toggles. We have this animation of a queue as it enters and leaves the screen. We also have background colors being updated as the current playing song and album artwork changed. So there's a lot going on in here that might not seem that complicated, but turns out it's really difficult getting it done and doing it right. Very different things. We have to keep in mind that performance here is going to be critical. If any one of these little interactions are slightly off or caused some form of jank, it's going to ruin the whole experience for users. Animations have to stay 60 frames per second and have to be performant. We can't do anything like animating our positions directly or animating within scale. We have to use performance techniques in the long running web app world. We have to make sure that it stays fast. It's not enough to just be a fast learning app. We have to make sure that we are doing things fast throughout the entire life of the app. So we're going to look at three examples that cover all of these different performance checklists and show how we can get a native like app using the web. So the first one that I want to tackle was this floating card state where we have a card or an album artwork. In this case that is given the sense of elevation depending on the play pause date. So here I'm just using the CSS animation to mimic it. But what I'm doing is adding a box shadow or drop shadow to the card and animating its position. Right? That should be totally doable. And then I just have a transition all 300 seconds done, right? Well, no, because this drop shadow is so terrible for performance, you might not think that it's terrible for performance. In some cases it can be okay. But when you're trying to do animations or if you have drop shadows on everything, you are essentially having the browser to do two rounds of what they call painting. They do the first initial paint where they set the background colors and do essentially a render. Then they go through and they add the drop shadow and then they have to now recalculate, what should that background color be as a drop shadow interacts with it. So now we're having to do multiple rounds of painting and if we throw animation in there, well, that's going to be a lot of painting being done. If there's going to be a lot of painting being done, there's going to be a huge impact on the CPU usage and also the battery usage. So drop shadows animations just going to be a surefire way for your user's device to either heat up, slow down, or eventually just bring their battery. Now this can be inspected. We can see the impact of this. And probably the easiest way that we can do that is using this repaint flashing feature in our Dev Tools. So this is in Safari. On their Elements tab they have this little paint icon or paint brush icon where you can toggle to see all the times you are causing a repaint. Chrome has something similar, so does Firefox. So this is a cross browser feature. Dev Tools feature. What we have here is we have the drop shadow. It's animating its size as it goes in and out and we can see all the affected areas that are getting repainted by it resizing. So not only are we affecting the drop shadow itself or affecting the outer box of the whole element, not good. This is terrible for performance. So the way that I have this working in this recording is we just have an animation. We have two or three States depending on how you look at it, where we just have a box shadow and we're going through and setting all the different values for that box shadow, whether it's the card or the album art is close to the surface or we want to give it that elevated status. Now this is going to be very slow and bad for performance. So how can we do a better job at this? Well, we can actually make use of a pseudo element. We can use the after pseudo element, create essentially a Div or a 100% percent width and height element with a background blur and we can transform that element itself instead of the box shadow. So at zero 100% it should be scaled down to be a little bit smaller than the actual card itself and then it should be centered right behind the album art at 50%. We should scale it up a little bit more and then we should do some translation. Then we should translate it down a little bit just to give it that effect of having some elevation with an angled light. So by just changing it to this we get this as a result, but we still have that elevated status of the card. Feels like it's lifting off the platform, but we're doing it in a way that's super performant. And how can I tell? Well, this was recorded with that repaint flashing flag enabled, so I should be seeing a bunch of red flashing here. But I'm not because we are translating and transforming something on its own layer, therefore it is much better for performance. So this is a really nice little deep dive. And if you want to go on a side tangent to see the impact on this, I have this link to a tweet that I put out with a longer explainer video on. It definitely worth checking out if you are a performance nerd like myself. Moving on, we have animations here. This was something that I will be honest, it took a lot out of me. I wasn't quite sure if this was going to be possible. I could animate one of these elements, either the central now playing card or section to the left and to the right and then I can animate the queue entering and leaving. But I couldn't animate both of them at the same time, so I wasn't sure how to account for all the layout shifts and the change in positions that the elements have throughout the two different States. So I almost gave up on this and went with a stark layout change until I rediscovered this flip concept. This isn't the first time I've heard of it and I'm not the person to have invented this. There's a really great blog post by Paul Lewis on his old blog Archive. It's a concept that has been used in traditional animation or 3D animation for a while now, where you get the snapshot of an element, you get the first position and then you cause a layout change, then you get the snapshot of that new layout change, you invert the values, you do some math to figure out the Delta and then go ahead and use that data to create an animation and play it so first last invert play flip so what does this look like in practice? Well we have this small little snippet here of animation code where we go ahead and say get me the position layout data for the now playing card and then the queue we'll go ahead and store those later we'll do some layout change and then we'll get a snapshot of those two elements after the layout change. Now we can come through figure out the Delta of the X and y values and then the scaling values we'll go ahead and create this animation where we use that data and this will create the animation scale for the playing queue we'll go ahead we'll use the same data to go ahead and say here is the translation of the now playing section based on that change in left to right value and then we go ahead and then we play the animation. Now over here we do have some things where I can improve which is the leaving state just kind of flashes but as we enter we have this nice scaling effect, nice changing opacity and then the card or the now playing section itself is able to translate appropriately but feel nice and smooth. This is really nice, really powerful and allows us to be able to do some more complex animations because all we need to know is the change in layout. So in here we have a different animation based on the device orientation but the animation logic and the math being used here is the same throughout we just are animating different things so this is really nice. You only have to learn how to do this once and then you can just adapt it to whatever you want to animate the demo I showed used Ionic's own animation API but the same effect can be achieved using Angular's own animation API and specifically the animation builder class instead of the animation array in the component decorator because you're going to essentially be creating your own timeline sequence. Animation builder is going to be what you need. Now the last little bit is this offloading of heavy logic where we are just getting the background color or the dominant color of an album art and I kind of cheated here because I had done this before in a different project and I was able to use some existing code but I figured out a way to make it better and improve upon what had already been submitted to the world. So I started off with this library called Color fees. I adapted it a little bit to change how it performs but all it does is just analyze an image and give you the dominant color for that image. So I created this directive called Color from image where it goes ahead and we watch the changes for an image source. We go through that, we get the image do some dipping to figure out if we are on the first change, do something different. If not, we need to do some normalization and then we call this get palette from URL, which just goes ahead, just loads up the image data and then we use this get palette method to go ahead and place that image in a canvas which goes ahead, does some more math, and then we send that to a worker to calculate the common RGB values across that image. So our Pixel data ends up being an array with multiple arrays inside of them where we have the inner array of RGB values across the entire image. When we get that average or that palette back from the worker, we can listen to it on message. We'll grab that data, we can clean up afterwards and then admit the color data. With that, we get something like this, whereas we translate across the different or as we change the album arts, we're able to pick out that color, do it in a worker so it's fast and performant and just go ahead and update the background color with some event. Putting it all together, we get something like this where we have a song being played, we open up the full page view, we get the color to change automatically. We can play and pause and change the scale of the artwork and then we can open up the playing queue to see what songs are playing next. We can tap the next one, have the animation just work and update the background color again. So all in all, we basically have reimplemented the most important design features of Apple music on the web. So kind of wrapping it up we can have nice things. We went through all of those key things, I think make Apple music a great app, but we had to be smart about how we do it. We don't use the drop shadow CSS property, we go ahead and we use something like a pseudo element and transform that to create the illusion of a shadow. We use flip animations to be able to change the layout and have the calculations basically done for us. We use web workers to make sure that we can offload some of the complex data to a different thread and then just get all that data back once it's done. So this is all how we can get a really nice performing app and really nice performing animations without having to go through and resort to building it natively. So hopefully you get something out of this. I want people to at least try a web worker because I think they're super cool now. But yeah, rethink what you can do with the web, we can have nice things. Thank you.