www.manucurist.com - Product images get displayed initially but appear stuck loading when scrolling back up
Categories
(Web Compatibility :: Site Reports, defect, P1)
Tracking
(Webcompat Priority:P1, Webcompat Score:8)
People
(Reporter: ctanase, Unassigned)
References
(Depends on 1 open bug, )
Details
(Keywords: webcompat:platform-bug, webcompat:site-report, Whiteboard: [webcompat-source:web-bugs])
User Story
user-impact-score:800 platform:windows,mac,linux,android impact:workflow-broken configuration:general affects:all branch:release diagnosis-team:dom
Attachments
(4 files)
Environment:
Operating system: Android 12
Firefox version: Firefox Mobile 147.0/149
Steps to reproduce:
- Go to https://www.manucurist.com/it/collections/couleurs-vernis-green-flash
- Scroll past a few products and then back up.
Expected Behavior:
Product images are still displayed.
Actual Behavior:
Product images appear to be stuck loading.
Notes:
- It reproduces on desktop as well for some products when loading the page
- Reproduces regardless of the status of ETP
- Reproduces in firefox-nightly, and firefox-release
- Does not reproduce in chrome
Created from https://github.com/webcompat/web-bugs/issues/202200
| Reporter | ||
Comment 1•5 months ago
|
||
| Reporter | ||
Comment 2•5 months ago
|
||
On desktop it reproduces only for some products when the page is loaded, no scrolling needed to reproduce.
Updated•5 months ago
|
Updated•5 months ago
|
Comment 3•5 months ago
|
||
Can't repro on mobile nor on desktop today, can you still repro this?
| Reporter | ||
Comment 4•5 months ago
|
||
Yes, on both mobile and desktop on the latest Nightly.
Comment 5•5 months ago
|
||
Hmm, on Linux desktop I see this...
Comment 6•4 months ago
|
||
It looks like image loading is triggered by an intersection observer.
Comment 7•4 months ago
|
||
But it seems like the bigger problem is that the containing <a> has loading class.
Comment 8•4 months ago
|
||
handleLazy in https://www.manucurist.com/cdn/shop/t/72/assets/lazyimage.js is where this stuff gets done.
Comment 9•4 months ago
•
|
||
Each image has:
<card-gallery>
<div class="image-animate media media--adapt media--hover-effect" style="--image-ratio-percent: 100.0%;">
<img src="//www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=1100" alt="Rainbow da personalizzare Green Flash™" srcset="//www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=165 165w, //www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=360 360w, //www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=535 535w, //www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=750 750w, //www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=940 940w, //www.manucurist.com/cdn/shop/files/rainbow-gif-carre.gif?v=1768834912&width=1100 1100w" width="1100" height="1100" loading="eager" class="motion-reduce" sizes="(min-width: 1600px) 367px, (min-width: 990px) calc((100vw - 10rem) / 4), (min-width: 750px) calc((100vw - 10rem) / 3), calc((100vw - 3rem) / 2)" is="lazy-image">
<img src="//www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=1100" alt="Rainbow da personalizzare Green Flash™" srcset="//www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=165 165w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=360 360w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=535 535w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=750 750w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=940 940w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=1100 1100w" width="1100" height="1100" loading="lazy" class="motion-reduce" sizes="(min-width: 1600px) 367px, (min-width: 990px) calc((100vw - 10rem) / 4), (min-width: 750px) calc((100vw - 10rem) / 3), calc((100vw - 3rem) / 2)" is="lazy-image">
</div>
<div class="image-animate media no-js-hidden media--adapt media--hover-effect medium-hide large-up-hide" style="--image-ratio-percent: 100.0%;">
<img src="//www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=1100" alt="Rainbow da personalizzare Green Flash™" srcset="//www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=165 165w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=360 360w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=535 535w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=750 750w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=940 940w, //www.manucurist.com/cdn/shop/files/DUCROS_Q230615-MANUCURIST_022_0657_Adb98_83e6f6e8-f716-4553-a403-7509ece4ab4f.jpg?v=1768834912&width=1100 1100w" width="1100" height="1100" loading="lazy" class="motion-reduce" sizes="(min-width: 1600px) 367px, (min-width: 990px) calc((100vw - 10rem) / 4), (min-width: 750px) calc((100vw - 10rem) / 3), calc((100vw - 3rem) / 2)" is="lazy-image">
</div>
</card-gallery>
is="lazy-image". FWIW this seems to only happen without image cache, so after some attempt to reproduce you need to clear the cache to reproduce it again. (devtools no-cache option doesn't seem to apply for images?)
Comment 10•4 months ago
|
||
FWIW, lazyimage.js has some WebReflection's libraries:
- https://github.com/WebReflection/custom-elements-attributes
- https://github.com/WebReflection/custom-elements
Those polyfills always override window.customElements.whenDefined regardless of browser support (which is fair and expected for polyfills).
And then its code:
class LazyImage extends HTMLImageElement {
constructor() {
super();
this.wrapper = this.closest('.media-wrapper');
if (this.wrapper === null) return;
this.handleLazy();
addEventListener('resize', this.handleLazy.bind(this), true);
const observer = new MutationObserver((changes) => {
changes.forEach((change) => {
if (change.attributeName.includes('src') || change.attributeName.includes('srcset')) {
this.handleLazy();
}
});
});
observer.observe(this, {attributes : true});
}
handleLazy () {
const mql = window.matchMedia('(min-width: 750px)');
if (mql.matches && this.classList.contains('medium-hide')) return;
if (!mql.matches && this.classList.contains('small-hide')) return;
if (this.complete || this.classList.contains('loaded')) return;
this.wrapper.classList.add('loading');
this.addEventListener('load', () => {
const loaded = () => {
this.classList.add('loaded');
this.wrapper.classList.remove('loading');
};
window.requestIdleCallback ? window.requestIdleCallback(loaded, {timeout: 150}) : setTimeout(loaded);
}, false);
}
}
window.customElements.define('lazy-image', LazyImage, { extends: 'img' });
So handleLazy() is called on construction. If it's .complete then it returns and does nothing else. That's problematic as the this.wrapper.classList.remove('loading') part is not called even though it's complete.
I see that branch also runs on Chrome, and I can confirm it also affects Chrome.
Comment 11•4 months ago
|
||
Comment 12•4 months ago
|
||
Hmm, on mobile, something else removes src and that causes reloading based on srcset.
Comment 13•4 months ago
|
||
https://www.manucurist.com/cdn/shop/t/72/assets/global.js?v=24674843534642605641764679293
class CardGallery extends HTMLElement {
constructor() {
super();
theme.initWhenVisible({
element: this,
callback: this.init.bind(this),
threshold: 200
});
this.matchMedia();
}
init() {
if (!theme.config.mqlSmall) return;
this.flickity = new Flickity(this, {
accessibility: false,
rightToLeft: theme.config.rtl,
prevNextButtons: false,
pageDots: true,
on: {
ready: () => {
this.querySelectorAll('img').forEach((image) => {
// Force lazy image reload
image.removeAttribute('src');
})
}
}
})
}
initWhenVisible works through intersection observer, as Jeff mentioned in comment #6. But I see this always called properly on mobile, and cannot reproduce comment #1.
Comment 14•4 months ago
|
||
Hmm wait, I see it on tablet but not on my phone.
Comment 15•4 months ago
|
||
The repro requires scroll-to-hide-toolbar customization to be on. (Which is by default. I disabled it because it's so annoying...)
Comment 16•4 months ago
•
|
||
Ok, I see what's happening.
From comment #10, you see there's a resize listener. Scrolling up with scroll-to-hide-toolbar triggers resize event, and the listener is handleLazy. And from comment #9 you see that each card-gallery has multiple images, with the second being hidden by display: none (it's for mouse hover).
handleLazy is called for both. As the first image is already loaded, the early return happens and nothing goes wrong. The culprit is in the second image. We do not load the second image at all, so .complete remains false. That means the early return does not happen, so handleLazy goes on and add loading class to its ancestor, causing both images to be hidden. Now given the first image is already loaded and the second image never loads, the load listener never runs and the loading class stays forever.
Interestingly Chrome loads the second image and has .complete being true.
Comment 17•4 months ago
|
||
not a minimal repro but to check on mobile...
Comment 18•4 months ago
|
||
Okay. That third image I initially missed in comment #9 was the key.
The first and third are non-hidden, only second is hidden. And the second and third have the same image sources.
With loading=lazy, the third is loaded on both Firefox and Chrome, which effectively loads the second too. Then comment #13 comes in and removes src for all image elements, including the second image.
Boom! Somehow Chrome then sets .complete to true on the second image element, as the resource is technically loaded. Not on Firefox. Thus only Firefox suffers from the problem in comment #16.
I need some good ice cream and beer and whatever by debugging this hell.
Comment 19•2 months ago
|
||
The chrome behavior makes zero sense to me.
Description
•