Background
We're currently taking part in the origin trial for Event Timing on ruwiki and eswiki and the most common type of slow event is the mobile site's click handler for section expansion. Investigating further, it appears that the majority of the cost for that event handler comes from image lazy loading.
When users click on a section to expand it, 61.7% of the time that click's event processing (due to the image lazy loading stuff) takes more than 20ms. When it takes more than 20ms, the median is 56ms and the p95 235ms. Essentially, the majority of the time the browser becomes unresponsive for a short period when the section is clicked/pressed to be expanded.
What happens
When the "section-toggled" event coming from this click is dispatched, it triggers a proximity check for images present in the expanded section:
For each lazy-loading image container, this triggers an expensive call to scrollTop/scrollLeft:
Calls to these functions trigger reflows and style recalculations: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
The more images the expanded section contains, the worse it gets.
While this issue has been surfaced by the click handler, they are also affecting the other ways lazy loaded images are processed.
Profile
Recommended actions
I understand that this proximity logic was put in place to make the lazy loaded image proximity check "smarter" than the default intersection observer behaviour of knowing about an image only once it starts being in the viewport. However, this feature comes at a cost in terms of page responsiveness, CPU spike usage, and battery.
Minimum optimisation
Currently scrollTop and scrollLeft are accessed for each lazy loading container. This is inefficient, as within an event handler the scroll position of the viewport won't change. Saving this information so that the coordinates are reused when processing each placeholder should be straightforward.
Extended optimisation
The viewport position from the last scroll event should be a sufficient approximation when a section expansion occurs. Expanding the section doesn't change the scroll position. As a result, if the last known viewport scroll position is saved, there is no need to compute it upon section expansion. Reusing the last known viewport coordinates should suffice. While the debouncing of the scroll event handler reduces the accuracy a little, it's identical to the accuracy that happens when scrolling, which I imagine is what the proximity threshold was tuned for.
Similarly this saved viewport scroll position could be used for the resize event.
"Maximum" optimisation
As you might guess, I recommend that this logic is scraped entirely and replaced with an intersection observer. Which will bring us more in line with upcoming native lazy loading.
In order to emulate the current behaviour, one could consider using spare time in the background (eg. using requestIdleCallback) to preload the next 1-2 images in the DOM that aren't visible yet. This might achieve the same effect as the current behaviour most of the time.
I believe that this would be a better compromise in terms of browser responsiveness / bandwidth usage while retaining the bulk of the desired effect.
Acceptance Criteria
- Out of the options listed above, determine which one we should pursue
- Create a new task outlining the appropriate action and your findings so we can run it through our pipeline!