Smoothness Reporting for Animations and Scrolling
Michal Mocny (mmocny@google.com), TPAC 2020
Why?
Animations and Scrolling are a big part of UX!
“Stuttering” tends to be a very visible problem to users, leaves a bad impression.
Today: hard for developer to know when, what, why hiccups happen
Topics covered
Measuring Smoothness
Measuring Smoothness
Folks tend to think of Smoothness in terms of FPS, but this has some flaws:
Measuring Smoothness
What really matters:
Missed opportunities to show expected animation updates
Aka: “Dropped Frames”
What do we consider Animations?
Defining: Dropped Frames
Examples...
rAF animation that conditionally updates
var frameCount = 0;
function animationStep() {
if (++frameCount % 2 == 0) {
anim.style.transform = 'translate(' + frameCount + 'px)';
}
requestAnimationFrame(animationStep);
}
requestAnimationFrame(animationStep);
CSS animation with idle periods defined
<style>
div {
[...]
animation: mymove 5s infinite;
}
@keyframes mymove {
0% {transform: translate(0px)}
25% {transform: translate(100px)}
75% {transform: translate(100px)}
100% {transform: translate(200px)}
}
</style>
Scroll-linked animations
document.body.addEventListener('mousewheel', () => {
const now = new Date();
while ((new Date() - now) < 1000) {}
}, {passive: false});
window.addEventListener('scroll', () => {
const now = new Date();
while ((new Date() - now) < 1000) {}
document.querySelector('#top').style.top =
window.scrollY + 'px';
});
Here, a non-passive listener delays scroll start… but scroll is smooth otherwise.
Here, scroll handler is passive and so scroll itself is smooth, but the animation run within the handler is delayed.
Options for reporting the data
Diagram: Timeline w/ animations & scroll
Option 1: Report all the raw data points
Note: Fabricated data to serve as example. Reality likely much less dense.
Option 1: Report all the raw data points
Concerns:
Option 2: Report per animation (summaries)
Option 2: Strawman API
interface PerformanceAnimationTiming : PerformanceEntry {
entryType: “animation”,
type: <one of “css-animation”, “scroll”, “js-animation”, …>,
startTime: <when it started>,
duration: <how long did it last>,
interval: <average vsync interval during this animation>,
framesProduced: <# of frames produced during this animation>,
framesExpected: <# of frames that were expected during this animation>,
element: <the animating element, if/when possible>,
id: <the animation id, if present>
};
Option 3: Report per frame (on timeline)
Option 3: Strawman API
interface PerformanceFrameTiming : PerformanceEntry {
entryType: “frame”,
startTime: <when it started was presented, possibly needs fuzzing>,�updatesProduced: <# of animation updates produced during this frame>,
updatesExpected: <# of animation updates that were expected since the last frame>,
};
Note: may have 0 updatesExpected if there are no animations active.�Alternatively: could replace counts with state? COMPLETE, PARTIAL, IDLE
Option 4: Report a single summary
Option 4: Strawman API
interface PerformanceFrameTiming : PerformanceEntry {
entryType: “animation”,
type: < “page” or “summary” >,
startTime: <when it started>,
duration: <how long did it last>,
interval: <average vsync interval during this animation>,
framesProduced: <# of frames produced during this animation>,
partialFramesProduced: <# of partial frames produced during this animation>,
framesExpected: <# of frames that were expected during this animation>,
};
Here we treat the overall page timeline as an animation, and report it using almost the same format as Option 2.
Mix and match?
Open Questions
Open Questions