Page MenuHomePhabricator

Images do not load until scroll event completes (not during scroll) on iOS
Closed, ResolvedPublic2 Estimated Story Points

Description

Currently, as one scrolls down a mobile web article in beta, the images often remain blank for a noticeable amount of time before filling in. This is on a fast connection!

There is obviously a balance we want to meet between page load time (waiting time) and time waiting for an individual image to load (wait + disruptive to reading experience), but we started at one end of the spectrum. Arguably, even mild waits while reading an article can cause cognitive jolts and frustration.

Since we started on one end of the spectrum (100% page load time, 0% image load time) I suggest we start with a more conservative approach. I would recommend we move the scroll load trigger earlier so that by the time a user scrolls to the image, it is more likely to be loaded. Maybe even 2-3 screens away. This way, we can measure results while being more sure that we are not disrupting the reading experience and then increase or decrease the laziness

I don't think there is anything we should do for images in collapsed sections for now, since this would conceivably be harder to do (without auto loading first screen- images in each section).

Implementation notes

  • We use isElementInViewport which has the default behaviour of only loading stuff in the current viewport. We should use isElementCloseToViewport which would load them sooner - the threshold should be 1.5x the height of the viewport.

Related Objects

Event Timeline

Device, browser and page viewed please.

The threshold is already high so I'm guessing this is yet another ios issue (sigh).

(Also note that we don't lazy load images outside collapsed sections.)

iphone 6, iOS 9.3.1 Safari, Chrome
macbook air 2014, OSX 10.10.5, Chrome

Here is a gif of the experience...

timing lazy3.gif (673×1 px, 7 MB)

As I mentioned above, I know there is nothing to be done for the first screen of a newly uncollapsed section.

Oh I see. If I remember correctly scroll events only fire when the user stops scrolling. So if you don't release your finger and scroll past an image it will not load. I've seen issues with fixed positioning JavaScript implementations impacted by this problem.

Not sure if this is a feature or a bug... And if a bug how we might remedy it.

Thanks for the gif it helps a lot!

Jdlrobson renamed this task from start loading lazy loading images earlier on mobile web to Images do not load until scroll event completes (not during scroll).Apr 25 2016, 10:01 PM

can you clarify the current trigger? is it a scroll event that ends within
X screens of an image?

The current trigger is when you lift your finger off the screen on an ios device (not from the time you place your finger on the screen and keep it on there).. if that makes sense?

That does make sense, but what proximity to the image triggers the load? I
assume any scroll release doesn't load all the images.

Jdlrobson renamed this task from Images do not load until scroll event completes (not during scroll) to Images do not load until scroll event completes (not during scroll) on iOS.Apr 29 2016, 3:35 PM

The issue is well documented here :
http://developer.telerik.com/featured/scroll-event-change-ios-8-big-deal/'

"any changes to the DOM will not be painted until the scroll action completes."
This means we cannot load images until the scroll action completes.

I'm not sure what we can with this imposed constraint.

It sounds like we should be able to load the image in the background but only when the scroll stops would the image appear.

@Jdlrobson thanks for looking into this. I think the background loading make sense. fwiw it doesn't look to be limited to iOS as it happens on my macbook air as well.

MBinder_WMF set the point value for this task to 2.May 16 2016, 4:26 PM

We use isElementInViewport which has the default behaviour of only loading stuff in the current viewport. We could use isElementCloseToViewport which would load them sooner, but to be clear even with this change they would still only render when the user releases their finger.

From @Peter.Hedenskog (originally on T135476)

Trigger image loading earlier

Check this recording, it's done using a good wifi and still the last image in the video is loaded late.

out2.gif (736×414 px, 3 MB)

I think we should aim for trigger the load of images earlier so that they are there in almost all cases. If we load one/two images too many shouldn't be a problem I think it's more important that the user experience is better = seeing the images immediately. https://github.com/aFarkas/lazysizes have some cool things like lazySizesConfig.expand , lazySizesConfig.expFactor and lazySizesConfig.loadMode for making it configurable of when to load images.

I think @Krinkle has good ideas on how we can trigger images earlier in a good way, if we want input on how to do that.

Jdlrobson added a subscriber: Peter.

@Peter does the thread above match your thoughts. We can fold in configuration options to the requirements if you think those are of value.

Related to isElementCloseToViewport and the threshold, mentioned in T135607 and relevant:

I think the threshold should be 60% of the height of the device and not fixed amount. for an average 4" phone, it would be around 600px-700px

Also, what is the threshold we use for lazy loading images?

I thought it was already using isElementCloseToViewport. That not being used should be fixed right away. (Even with a fixed margin for now, but one based on the viewport size would be better indeed.)

The images not appearing seems to be caused by a combination of factors.

  1. The animation being applied from a JavaScript handler related to the image load event (not the scroll event). As such, those are deferred and de-prioritised until the scroll settles. Chrome does this to avoid random JS handlers from potentially doing slow unrelated operations resulting in a janky scroll experience. Removing the animation, or applying the animation directly, or using a transition instead. Would solve that.
  1. Even if the animation starts earlier, MF currently doesn't attach the image to the layout until the file is completely loaded. Inserting it directly (matching the behaviour of native image loading) allows the browser to handle it which will probably allow it to render in between scroll events (because it's handled natively in a separate thread that doesn't block the main JS thread). - See also T135476 and T135430.

Change 290543 had a related patch set uploaded (by Jdlrobson):
Load images earlier in scroll

https://gerrit.wikimedia.org/r/290543

Change 290543 merged by jenkins-bot:
Load images earlier in scroll

https://gerrit.wikimedia.org/r/290543

290543: Load images earlier in scroll handles the AC, i.e. replaces the use of mw.viewport.isElementInViewport with isElementCloseToViewport. However, neither of @Krinkle's points in T133565#2310070 have been addressed.

I note that T135476 has changes available for review but doesn't appear to have consensus and T135430 is blocked on this.

@Jdlrobson: Am I to assume that this should be moved to Ready for Signoff and further required work is covered in @Krinkle's/@Peter's tasks?

Edit

I ask because the title of the ticket is broad but the single AC is narrow and doesn't include suggestions from related tasks.

The change has been pulled by our staging server. You'll be able to test this at http://reading-web-staging.wmflabs.org.

Copied from https://gerrit.wikimedia.org/r/#/c/290543/

+    offset = $( window ).height() * 1.5,
     ..
-        mw.viewport.isElementInViewport( placeholder ) &&
+        mw.viewport.isElementCloseToViewport( placeholder, offset ) &&
@Krinkle wrote

According to mw.viewport documentation, the threshold argument is based on proximity to the edge of the viewport. Not the top of the screen.
As such, passing 1.5x the height of the window, means an image will load even if it is not on the current screen, and also not in the next "full-page scroll", but in the second full-page scroll away from the current scroll position. That seems relatively far?
I think the idea was to extend the lookahead to the next half screen full. And presumably you thought offset is measured from the top of the screen?

Please verify the above. It seems that otherwise we've almost entirely disabled lazy loading in favour of deferred loading by default. (2.5 pages loaded by default, could be fine, but that's not what was discussed afaik).

Good catch timo. That's bad. Will revert the change.

The suggestion in the description was to load 2-3 screens ahead.

This is not that aggressive (it appears to be 1.5 page ahead). But I'd say from review on tablet (all sections expanded) / phone (all sections but first collapsed; granted, JS loading has to happen to collapse things - sidenote: is there some better way to collapse sections sooner and avoid the layout penalty?) at fast / medium / slow connection speeds and it seems to behave reasonably.

Most of the time on a fast connection the pre-emptive fetching for images outside of the viewport beat the spinner. And on slower connections I was more likely to get the spinner when scrolling realistically fast

I do realize we're looking at removal of the spinner and potentially other enhancements, which I think is not unreasonable, but anyway it seems to behave nicely.

I cancelled the revert per Adam's comments to allow us some time to think as I think Adam has a point - it's not as bad as I first thought. It's worth noting on mobile, the scroll factor should be higher as scrolling happens quicker but on tablet this will be much less noticeable. I think the statement " we've almost entirely disabled lazy loading" is unfair and not true. We possibly have done on tablet/desktop but definitely not mobile.

To take an example the Barack Obama page on an iPhone 5 has a viewport height of 568
So...

$( window ).height() * 1.5
852

Currently we are measuring from the bottom of the viewport so we are actually loading any images 852px from the bottom of the viewport.

I've submitted a follow up patch that makes this option configurable so we can play with it, but on second thoughts, I think Adam is right that this not that aggressive.
https://gerrit.wikimedia.org/r/291281

@dr0ptp4kt @Krinkle are we satisfied enough to mark this as resolved or are we keen to have https://gerrit.wikimedia.org/r/#/c/291281 ?

There's a little mismatch now between images in the first section that are real image tags (picked up by the preloader) and images in the second/third section that still is within the scroll viewport so they are triggered to be downloaded even though the user don't scroll.

Maybe the example page isn't representative (http://reading-web-staging.wmflabs.org/wiki/T133565?useformat=mobile&mobileaction=beta) when I test it the first image in the second section is triggered really late (see request number 10). Could it be that we should to have the first two sections without lazy loading images? If it's loaded earlier, the chances are bigger that it's downloaded when the users scrolls through.

Screen Shot 2016-06-01 at 12.26.20 PM.png (662×1 px, 241 KB)

@Jdlrobson I'm okay with the change as is.

@Peter, I prefer we don't use standard <img> tags in the second section, as at least on phones that second section will get collapsed. As I understand it, the artifact you're observing has to do with the timing of the JavaScript execution, probably related to the ResourceLoader script delivery and execution pipeline, in this case on a slow connection. The lazy fetch won't start until the runtime code is in an executable state. I'm okay with this timing tradeoff at the moment, but I don't like it in the long run. To thicken the plot...

There's another UX artifact on phone form factor related to this I've never cared for whereby all of the sections will, once the RL code is executed, get collapsed. Often on fast connections it's not noticeable because stuff gets collapsed before one even realizes it. But it is noticeable on slow connections. And I suspect it may have something to do with the viewport being somewhat nonresponsive in some cases (this has been a longstanding complaint). Whatever the merits of collapsing, it is strange to be reading a section and then all of a sudden have it collapsed before your eyes without intervention. I believe the source of this too is the timing of this ResourceLoader-only feature.

I think we should probably do the following in a task later on. Does this make sense? If so, I'd be interested to file a task for it.

  1. Use JS early in loading - perhaps with a <head> NORLQ ? - to ensure sections beyond the first one are collapsed without first being expanded. Is it possible to apply the style override as an !important rule with JS in a way where we wouldn't get FUOCs? In any case, I believe in practice we need to retain the CSS / default behavior leaving the sections uncollapsed for <noscript> browsers for the time being (there are more elaborate designs or fundamental product changes that could obviate even the need for that, but that's a different discussion).
  2. Consider adding support to NORLQ so that for tablets we can actually pre-fetch images further ahead like you propose. Personally, I would prefer if we actually did this for content images in general even from the first screen, but I understand we have made a tradeoff call on the assumption the browser's image rendering will be more efficient if we just let it do what it does in the first section normally.
dr0ptp4kt claimed this task.

Marking as Resolved. @Peter, I would appreciate your feedback in response to my prior comments/questions to figure out any potential next steps.