Lessons Learnt from Building Interactive React Applications

Bookmark

When users directly manipulate onscreen objects instead of using separate controls to manipulate them, they’re more engaged and more readily understand the results of their actions. Subtle animations can give people meaningful feedback to help clarify the result of their actions. But, the devil is in the details. What often seems simple can be complex to get right, especially when you care about accessibility. Sid shares the lessons he has learned building interactive UIs.



Transcription


Hey, I'm Sid and I do a bunch of things here and there. So I just put all of it on the screen for you. But what I want to talk to you about is the work I've done at Code Sandbox for the last one, one and a half years. And most of that work involved around the front of the front end. And I was focused on animations, design, gestures, drag and drop, those sort of things. And I want to kind of ask you this question. What makes an interface good to use? So this is what I've been exploring for the last year now. And there's the common answers, which is it should be well designed. It should be minimal, no clutter on the screen. It should be fast so that when you use the UI, it appears snappy, it loads quickly, there's feedback. But something that gets ignored often is the use of motion or animations to help you out. And animations get chucked into this category where it's for whimsical websites or it's, you know, if you're making a movie or a game website, then you might use it. But I'm here to pitch that animations can be used to give the user context when used tastefully and you definitely want them in your professional websites as well. So enough talk. I'm going to show you what I mean. So I built this social media website sort of thing. It's called Saturday and the logic is that it's only open on Saturdays. And so this is my feed. I have a few posts from my friends and let me show you what that looks like. So when I click on a post, it kind of just opens, it just appears and then there's this reply that appeared. And if I click on the second one, it goes to the feed page, it goes to the post page and you can see the URL changing and it just kind of appears. So that looks a bit odd. Let me go full screen on this. And then the replies also just suddenly appear. So that's not really a good, good, it doesn't feel like this is an interface that you're interacting with, it doesn't feel smooth. So the first thing that we'll do is feed these in. And that's, I think that's a pretty common thing across the web. You see it pretty often and you can, you can do this. You can of course do any of the examples that I show you with CSS animations. But I'm going to use frame of motion for my examples just because that's what I've been using for a while. And if you're already in React learn, then you should definitely check out frame of motion if you haven't. So the way frame of motion works is that you can take any element and I can say motion.div. And if you use style components or something similar, the API is similar to that, where you can basically get any element out of motion and use that. I'm just going to use motion.div because that's what it was. And because I'm using a component library here, I'm going to pass it to the as prop. And that's just to show that frame of motion can combine well with your component libraries. So it gives me access to these two props. The first one is initial and the second is animate. And I can say what the initial state is and what should it animate to. Of course there are a bunch of other props, but this is enough to get you started. So now when I open a post, it fades in instead of just appearing, which gives you this subtle feeling of that something has changed. There's some context here. Even the first post, even though it's just in place, just that tiny flicker which fades in gives you the feeling that you've navigated to a different place. So let's do that with our comments as well. Because right now the comments or replies just kind of pop in, just like appear. They don't really do anything. So I'm going to change my branch because I've already written the code for it. Let me stash this and go to this branch, fade only. So this is my branch where everything fades in. And you can see when I go from one post to another, the replies fade in subtly. And this is good. This is not great yet. Another place where you can see this is if you have new posts, then they kind of fade in and it gives you a sense of that something has changed, even though you've kind of lost your position there. So let's do something about this first. I'm going to reload. And this is a common pattern that you also see on Twitter and I'm sure they have their reasons for it. When you load more posts or when they choose to load more posts for you, the posts appear in front or they fade in, but you kind of lose your position of where you were. And it's especially annoying when you were the one who initiated it. So you kind of just lost your position in the feed. So I'm going to go to my feed and suggest this change. I already have frame of motion here. All of these elements, each one of these list elements is already a motion.li. And I'm going to remove this fade from opacity zero to one and instead use the layout prop. And that's it. Just say, give me a layout animation. And what that does with frame of motion is that it tells frame of motion that all of the elements which are siblings or have the layout prop, animate them together. Right. Let me, it's hard to say. So let me show you what that means. Now, when there are new posts, notice what happens to these cards. You see, they got shifted. Let me show you again, because it's easy to miss. So when I, when there are new posts, all of these elements move down to make space for this new element. And that's kind of what layout animations give you. So when I load more new, load more posts, the original posts kind of got shifted down. Let me, let me show that again because this, this animation is really gratifying. When there are new posts, the original posts get shift down, get moved down. And I think that's a brilliant way of doing it because it gives you the context that these posts have come from the top and they've just shifted your original posts. So if you want, you can scroll down and still find your posts. And it's especially nice if the, the first post that was earlier is still visible, but even when it's not, just the shifting animation gives you the context that you can scroll down and find your place again. So that's, that's a really good improvement according to me. Now let's talk about these comments for a bit. So these fade in, which is nice. And then I can add a new comment, new comment, and it kind of just fades in after a while. Did you see there's like a weird delay over there? I'm not sure why. And I mean, I do know why. I'm trying to simulate that there's a API request going on. So you add a comment, you send it to an API and when it succeeds, then you add the comment. And this is a fairly common pattern on the web that wait for the validation from backend and then insert it. The bad thing is that it doesn't really lead to a good interaction for the user almost ever because the user doesn't really care what is your server validation or what is your logic there. What they want is to leave a comment on this post. So when I leave a comment, leave a comment, I want it to appear instantly because I want to be on the happy path. I'm kind of done here. And you notice that this is what mobile applications do. They always insert your comment or your interaction first, and then they sync it with the background, right? There's no waiting for validation. It's usually a background sync later. And I think that's what we should do here. And there's a name for it, which is optimistic updates, which means whatever the update was supposed to do, let's assume it's going to pass. That's the optimism. And if it doesn't pass, then we do the trickier thing of figuring out how do we show the error validation. So the error case is harder to handle, but the optimistic happy path is always good. So I don't actually have a backend for this. I'm faking it by using the delay prop from Frame in Motion, which is actually useful if you're doing animations that do animations where one should come after the other. So in this case, I'm just going to remove the delay and this is what it should look like. So when you add a comment, that's not the right delay. This one. So when you now when I add a comment again, it should just fade in right away. So this is fine. I like fade, but there, you know how we saw this motion, we're using the change in position to kind of give context to the user. We probably should do the same over here. So let me check out to that. I'm going to stash this again. Check out to pre-final. So. All right, there we go. That's a lot of motion. I'm going to reload. So what I'm trying to do here is do you see these replies? These replies kind of come out of this post and that gives you the feeling that they're part of the post and then they stem out from it with a delay. So the code for this looks like I have an initial Y of minus 40 and this Y of minus 40 converts to it's a transform animation with a translate Y, which is a good thing to animate because it's more performant. And when you go from minus 40, which means it's final position, so frame of motion calculates its final position and then goes minus 40 and animates it to that point. So I'm doing minus 40 to Y of zero and I also have a height auto animation, which is cool. And so when you open this post, the comments come out of the post. It's coming from the direction of a post, which gives you this feeling that these two are related content. So the post comes first and then the comments come out of it. Now check out what happens when I add a new comment. So if I do nothing, then it will also come from the top, like the other replies, but that doesn't make a lot of sense because I mean, why is it coming out of the other replies? So what I've done instead is, let me show you what that would look like if I don't do anything. So when I add a new comment, it comes from the previous comment, which doesn't make sense. So what I do instead is put a plus 20 on it and try to make it look like it's coming from the reply, the input box. So here we go. New comment. And you see this gets shifted down and the new comment takes its place. I'm going to show it to you. Oh, sorry. Let me reload and show it to you again. New reply. New reply comes from the input and gets added to the list. So we're using a bunch of animations here. The first one is for the post. The second one is the replies coming out of the post. And the third one is a new reply going from the form to the list of replies. And this is a good example of how you can really use motion in multiple ways to always think about where is this thing coming from to give context to the user. And honestly, these are the kind of things that make native apps feel more native, make them more smooth and nice to use when you use them with your thumb. And we can do all of that on the web as well. All right. Now I skipped one thing that I should talk about. There's this pattern. Actually, you know what? Let me first show you Twitter. So I'm going to open a new tab and there's this thing that's pretty common on the web, which is when you open a new post. So I'm going to open this one. You see this back button, right? And when you press this back button, you kind of go back in history and we all know what that means. Also, shout out to Michael. But this arrow points left. Now, there's nothing on the left side. Right. We just have this shared vocabulary of three dots means there's a menu here. There's more. So there's a menu and a heart means you're going to heart or like this post and a left arrow means back. Now, even though it's not going left at all. So on the web, we kind of have these interesting mix of symbols which don't really say what they mean and compare that to the, this is the app on, this is the Twitter app on mobile. I recorded the video. And so when you load a post, I'm going to load the first post. It comes from the right. And then when you press the left arrow, it actually goes to the left, right? So they have this notion of there are two screens. When you click one of the posts, the other screen comes from the right. And then when you press back, it goes back there. So they're actually using motion and direction to create a new context quite literally. And then when you click left, it actually reveals the thing that's on the left side. So that's a good example of using motion. Another one I really like is on Instagram. So when you click the camera button on Instagram, that was way too fast. So when you click the camera button on Instagram, it doesn't pop right out. It actually slides from the left and they're trying to do the same thing where they're trying to say that there is a camera on the left screen. So you can actually drag it out and drag it back over there to close it. And I think that's a great use of motion to actually give the user a hint that there's stuff on the left side so you can drag it. Otherwise, on a small mobile screen, how do you even tell people that you can drag around? Of course, it's not all unicorns and rainbows. Check this button out. So when this is still Instagram, so this is the profile part. There's this arrow here, right? It points down. And just from the shared vocabulary, we know that a down arrow means a drop down or more options. And when you click this, I expected like a drop down over here or something to come from the top because the arrow is pointing down. But what happens is you get a switcher on the bottom of the screen. So there's no relation to why is it on the bottom. But because the iOS guidelines say it should be at the bottom so that it's closer to your thumb, which totally makes sense. But then the arrow kind of loses its meaning. So for Saturday, what I decided to do was create a details view, which is a post view and try to zoom into a post and zoom out of a post. And let me show you how this works. So I have a selected post component and the whole idea here is that on any post, you can click it and now you're in that post. You can't scroll out of it. It's a full screen thing and then you close it. It goes back into its original position. So even this one, which is out of the screen, I click it, it comes down, becomes a full page. And then when I close it, it goes and gets tucked under the header to its original position. And let me show you how that works. So there are multiple ways to do shared layout animations. The way I like is a bit old school, which is I have a selected post component, which is different than the post component. So when you click a post, a selected post component is rendered absolutely on top of the original post. So there's like, this is the post and then this is the selected post. And this is the one, the selected post is the one that animates and glows. And when you close it, it goes to the old position and then stops rendering. So it looks like it's the same component. And the way I like to do this is calculate the initial Y position of the post. So for example, this post, get the element, get the Y position. This is the initial Y. The final Y of course is Y zero because it's the top of the screen. And then when you close it, the exit should be back to its initial Y. So I do this very imperatively. I get the bounding box and get the initial Y. And then with frame and motion, I say initial is initial Y, animate it to Y zero. I have a duration, I have a delay. I have an exit which goes back to the initial Y. And like just a tiny motion tip for you, you want your entry animation. So in this case, it's 300 milliseconds. You want to exit animation to be shorter than your entry because when you enter, you want to give context, but when you close it, you want it to be quickly out of the screen. So you still want to snap it back to its position, but you don't want to show the user a long animation. They canceled it. They want to move on. So you want to quickly get it out of there. So that's a tiny tip. Exit animation should always be a little shorter than your entry animation. Now yes, so the part that I really like about this approach is that even when it's out of the screen, you can click it and it comes to the spot and it remembers the initial Y. So then it goes, gets tucked back there. All right. Two thoughts over here. If you can open it like this, if you click it and opens, like comes to the top of the screen, shouldn't you be able to push it back? And this is where it becomes really interesting on mobile because you use your fingers. So if you go to, if you go to saturday.wursel.app, which is where this app is hosted right now. So if you open it on your mobile phones, you can actually drag along with me. And this is what I want to show you. So FavorMotion already has drag and drop support, or at least drag support. So I can say drag Y, which sets the direction of the drag. So now I can drag it Y, I can just send it wherever, but I can also change the momentum and constraints. So what this does is I can try to drag a component, but it will always snap back to its original position. So when I drag it, it will go back. And depending on how much I drag, the speed would be adjusted according to the distance. And this is because FavorMotion uses spring animations. So if I pull it really far, the speed would be faster than if I pull it a little, because you want it to take the same time. And finally, what I do is when it stops dragging, if it has dragged more than a hundred pixels, if the offset is more than a hundred pixels in any direction, I want to snap it back into its original position. And I'm handing the off, I'm actually storing the offset in state so that I can also change the opacity of its background. So check out how that looks. So when I start dragging, you'll see that you can start seeing the background slowly based on the movement. And this is one, it's to give the user a hint that when you start dragging, this is what is expected to happen, that you will go back to the feed and your card will go back to its position. But also we're using some, we're using the drag offset to calculate the opacity. So the feedback that the user gets is based on their motion. And that creates a really smooth interactive. It really feels like you're interacting with the UI. So I'm going to drag it more than a hundred pixels, which means it will snap back into its position. Let me do that again. There you go. And yeah, so this is what, this is what I had in motion examples to show you. A couple of things to remember here, even though we're trying to invent new patterns here, like this is a zoom in and a zoom out, it really helps to make them closer to semantics that are already available on the web. So for example, this, if you think about it, is really close to what a dialogue looks like, right? And we already have this accessibility semantics for it. For example, if I press escape, it should close it and it auto focuses on this reply. But if I tab, it should go to the exit, the cross button. And when I tab again, it should come back. So there's a focus trap in this model. And that's because you shouldn't start tabbing and go to the next post and the next post and all the way to the footer, even though the only things visible here are these two. So I'm using a pattern that already exists to get accessibility benefits. And the other accessibility thing to keep in mind is the idea that not everyone wants motion. There are people who have vestibular diseases who would actually, what's the opposite of benefit, they would actually hate when the animation happens because it makes them feel sick. So a good example of this is on Mac. So you can drag between windows. I'm sure you've seen this with like the fingers swipe. But if you go to accessibility settings and say you want reduced motion. Now, when I try to do the same thing, it doesn't move. It actually only fades between the two and browsers expose this to us. So, frame of motion already has a hook for it. So I'm going to use the use reduced motion hook. And what I can do with this is our animation that goes expands and closes. I'm going to say if the user has reduced motion, then it shouldn't do this expand. It should just snap to the next position and same goes for the exit. And it would be nice if we do a little fade here, but that's not the point. So now if I have reduced motion on, I can click it and it goes to the top. There's no motion in the post. We should do the same with replies. But if I disable reduced motion, then I get the same animations. So all this to say that with motion, we also have to remember the accessibility concerns that come with motion. And I want to say that it's truly possible to have both and make it useful and make it accessible for all sorts of users because the APIs and the tech and the settings and everything is there. It's up to you on how you use them. All right. I think that's all I have. So if you already have the URL, that's good. I have the repository at the bottom of the last post and you can open this repository. It has the examples and the multiple branches that I jumped through. So it has a fade example and the motion examples. The last post is about a course that I'm making about the topic. So if you're curious to dive in more, then feel free to check that out. And yep, I guess that's it. Thanks so much for listening. Hey, Sid, that was an awesome talk. Great job. Let's see the answer to the question. So you asked what kind of web applications should use animations? And it looks like 75% of the audience said all of them. So people are pretty into animations. Nice. Yeah. I'm glad to see that. I was expecting like a smaller number. Actually, it started with a smaller number, but it just grew and grew and grew. So I guess job done here. That's awesome. That's awesome. Cool. Well, we've got a bunch of questions from the folks in Discord. So let's start off with one from Vasily Shelkov. Do you have a resource for common animation patterns that are applied often in lots of use cases that can help us think about easy animations in our day to day? That's a good one. So there are a bunch of blog posts here and there. I haven't seen a dedicated like a resource for which just has patterns. But I think the best way, or at least the way that I really like learning about these is just taking your favorite apps and trying to like really stare at them until you ruin it for yourself and think what patterns are they using? And then you start seeing these common trends like iOS menu comes from the bottom, hamburger menus come from the left and the background becomes dark slowly. So I think, yeah, just noticing other apps is I would say the best way to do it. That's a great answer. Just seeing what's out there already. And you use these apps on a day to day basis. You see these animations and just building up this internal repository of common patterns that you see in the day to day. Cool. I also think that there are like common ones across different libraries too. So that might be another place to look. So another question, and this one's from Chris J. Lee. And I think that you talked about this a little bit in your talk, but how do you solve for accessibility in your animations? Yeah. So there are two big pieces here. The first one is not everyone wants a lot of motion and that's something to remember. So browsers give us away. There's a 10 CFS, it's in JavaScript called preferred reduced motion. And it's interesting because it's not against animations. It's about motion. So you can still have things like shading opacities and that's perfectly fine as long as you remove shaking and spring animations. And the second one with something like animations is a lot of these things don't have native elements. So we tend to create our own and then trying to find the closest native element or a pattern for accessibility so that you still have, you don't break screen readers, you don't break keyboard navigation. So the example I showed in the demo was the zoom in animation, but I used a dialogue as a primitive because we still have accessibility primitives for that. Awesome. Awesome. So important to think about accessibility. And something that I think is related is we got a lot of questions about performance. So to aggregate these questions all together, what are your tips for making animations performant? And also how much is the performance impact for the type of animations that you showed in your talk? Yeah. So the short answer is that there are a few properties that you can animate with help from the GPU. So if you just look up GPU accelerated animation properties, there's a subset like opacity and transform and scale and stuff. And then the rest of them perform poorly. So the demos that I showed you, stream of motion, which tries to stick to GPU accelerated as much as possible, but because stream of motion is a React library, what you'll often end up with is if you're not very careful about it, you might end up with extra re-renders and those will be the cause of your performance troubles rather than the animations that you're running. So those are the two parts, which is look up what is safe to animate and what is not. And second is if your app feels slow, profile it and figure out which extra re-renders are you causing. Awesome. That's a very comprehensive answer and great summary. Another question, and this one's from Tanvir Singh. Why did you choose frame of motion over any other animation library? And is there any other library which you would also recommend? That's a tough one because are there any other libraries in the React ecosystem would be like, I don't know, I'm sure there are like a couple of hundred of them. I chose frame of motion because of the API. I really like how simple the API is and how almost always it feels like magic because they hide a lot of abstraction away from you. But the other one that I've used before and really like is called React Spring, which is still based on Spring animations, but it's more closer to the metallic Spring, I guess. It gives you a lot more control over the Spring animation. Awesome. Awesome. We got a specific question from Jason as a follow up here. Have you used GreenSock before and what are your thoughts on it if you have? I've used it. I like it. I end up using React a lot and that's why I end up using frame of motion with it a lot. But everything that I showed you could do that with GreenSock, you could do that with CSS animation. So I don't know. There's no X versus Y with it. Cool. Cool. Another question. What are some examples of web applications that use motion well? So the ones that I'm a big fan of are Linear. Linear is this new task editing app. There is Discord. Discord does it really well. If you're on Discord right now posting these questions, try to use the sidebar. I look at the, I don't know, like dozen of microinteractions over there. And I'm going to plug code sandbox dashboard because that's the one I built. That's awesome. That's awesome. Gotta, you know, plug your own work sometimes. That's awesome. Cool. Let's see. I'm trying to run through these and I think I was able to group a lot of them together. Okay. One more. So yesterday Ken Wheeler mentioned that you can increase perceived performance by removing animations on user interaction. When a user does an action, don't delay the result by animation. What is your response to that? I love that. That's a brilliant question. So I think you can use animations to improve perceived performance. And if you was incorrectly or too much, you can also ruin it. So think about it this way. If you click something and posts are going to load and it takes a hot half a second, you could use animations by expanding during that time. And that creates a feeling of it expanded and the results are already here. So that way you can use motion to speed things up or at least feel like it's faster. But a lot of times, like you're opening a menu and the menu takes 300, 400 milliseconds to open up. That feels slow. You already know the next action, but you have to wait for it. So that's where you can use animations to ruin things for yourself. So there's a really good blog post from Material UI which talks about how much distance should an animation cover and how fast should it be based on that. And the gist of it is if the distance is very small, like a tooltip or a menu, it should feel very snappy. But if it's a page transition, then you want to go up to like 300 milliseconds to 50 milliseconds to make it feel smooth. So totally with Ken, but use them tastefully and you're good. Use it too much, like everything, and you can ruin it. That's an awesome one. I love that this conversation about perceived performance is making more of an impact to the developer community because from a psychological perspective, it's so interesting to me that people can perceive things to be faster or slower based off of animation. So I think it's a cool topic. Amazing. Thank you so much. Again, please give Sid a huge round of applause in the Discord. This was such a great talk and I know that I learned a lot from it. Thank you.
35 min
14 May, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic