- Google Chrome Developers
- High performance web user interfaces - Google I/O 2016
Click on text below to jump to specific point in the video
I am Paul, I work on the web developer relations team. I typically talk about performance. Today I want to talk about building stuff. I will tell you the overall story about how this fits.
We have mentioned a bunch of things about progressive web apps. You need https for a bunch of features. Like push messaging. Service workers, which help you with the offline story, require https.
I love the stuff about poor connectivity. Service workers help with poor connectivity.
Closer look at the home screen reveals an interesting challenge for us. Which of these are progressive web apps, which are the native apps?
User is thinking: "that app looks like native, I hope it behaves like that"PerformanceWe have talked in terms of a model called RAIL - Response, Animation, Idle, Load.
Response = 0.1 seconds
Animation = 16 milliseconds
Idle = 50 milliseconds
Load = 1 second
Often people think we are saying all of it is top priority and must do. Reality tells us something else. If we are on a home screen, the priority changes a bit.
Assume app has been added to the homescreen. User was probably prompted. It is probably because you satisfied some criteria - e.g. you have a service worker. So for home screen, the importance graph changes. Three components1. SideNav
2. Dismissable card system
3. Expanding and collapsing viewSide NavigationLet us consider some theory. We have a containing element. We place a black background.We have a side nav content, which we slide in and out. I make position: fixed. I make overflow: hidden. I also have pointer-events: none. We want the sidenav ready to come and go, we don't want it out of the render tree. So clicks will pass through to underlying content. When we bring the side nav up, we switch it to pointer-events: auto. So consider pointer-event set to none for such elements.
Promote it to its own layer. Easiest way is to use will-change: transform. When the layer is separated from the page, we can use transform and move it around. We don't have to repaint the page content behind it.
Should I promote every element? Don't do it.
1. Keep your memory usage down
2. Keep time spent in compositing to a minimum
So we will translate the side-nav out of the view. Add class to side-nav to remove the transform.
We also use opacity.Dismissing the side-nav: similar but in reverse. Add a cancel to stop propagation of event.
Behavior expectation: gesture to get rid of side-nav. Visually update every frame with requestAnimationFrame.
The onTouchMove method. We want to use preventDefault.
update method. Make sure we don't go past 0, and apply a translation to the container.
onTouchEnd: remove transform.
Looking at the dev tools timeline: The fps is a steady 60fps. We are doing really well on fps. Swipeable cardsThey are not primed, we don't know which card will be used. Theory: promote to layer, but at the last minute. Transform + opacity are our two best friends when it comes to animating performantly.
Decoupling touch input from the visual update - pretty much what game developers do as well. Imagine every frame, we call requestAnimationFrame for every frame and if we get a touchmove event we will incorporate that into the frame.
An aside about bind: copying from the prototype into the instance. And binding it in the process. The key benefits: this refers to instance, and not event target. Also they are named - I can remove event listeners more easily.
onMove: pretty straightforward.
update: if you are dragging the card, the translateX is currentX - startX. We use a transform to just move it.
I put thresholds to decide when to dismiss cards.
Formula for value.
It basically slows down as we get closer and closer to the target. Detecting done-nessAsk if translation is small enough. Check if it is invisible. Slide all the other cards into place.
Dev-tools: very green. There is a little dip in fps at the start of the slide cards. Expand and CollapseIt is the most challenging - we don't know where we are starting and where we are finishing. The size is changing. Look up csstriggers.com. You will trigger layout and trigger paint. Not a good idea for every story of animation. Again, transforms are useful here. So can we do that effect with transforms? We can use the scaled transforms. The approach I take is FLIP it.
First - where the thing starts on screen. We can use getBoundingClientRect. We can then immediately snap animation.
Last - call getBoundingClientRect again. We know where we started, and where we finished.
Layout is not as bad here: 1. We only do it once. 2. RAIL to the rescue!RAIL + FLIPYou can typically afford to do a styles + layout pass. You should do it once. It needs to complete in < 100ms.
We can now introduce the transform to this. FLIP caveatsScales change child elements; you may need to use siblings
First to Last involves forcing styles & layout; be careful. FLIP is responsive web design friendlyDev-tools inspection. Closing thoughtsPromote elements that you intend to animate; use sparingly
Stick to transform and opacity changes wherever possible
Use FLIP to remap expensive properties to transforms
We have a section on google web fundamentals if you are new to this stuff.
Link to source code: http://bit.ly/supercharged-uiVideo outline created using VideoJots