solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
31.65k stars 887 forks source link

Cannot lazy load images in Firefox or Chrome #1828

Open rschristian opened 11 months ago

rschristian commented 11 months ago

Describe the bug

(I'm putting this first as I think it's important pretext, sincere apologies if this throws anyone off)

FireFox and Safari have rather interesting behavior regarding setting the src property of an image element, in that the browser will attempt to fetch the media after a microtask, regardless of whether or not the element has been (or will be!) inserted into the DOM. This is rather simple to demonstrate:

const img = document.createElement('img');
img.src = 'https://some.img.url';

Using the Network pane of a browser's devtools, you'll see the immediate fetch of the image.

FF Bugzilla Report: it's unclear whether this is a bug in FF & Safari or a spec issue, but it's been around for a while now.

Because of this, if one wants to set the image to lazy load, in Safari and FF, one needs to ensure the loading prop is set first, else it's effectively not set at all. Preact, React, and Vue mirror vanilla JS behavior, making loading an ordered prop in their JSX and SFCs.


As we try to decide what to do about this for Preact, I've been testing this behavior through multiple frameworks to see who accounts for this and who doesn't, but Solid's results have been exceptionally odd: lazy loading doesn't seem to work, regardless of property order, in FireFox and Chrome (tested desktop & mobile for both). However, it DOES work correctly on iOS Safari (I don't have a Mac so can't test desktop, sorry).

In the above mentioned frameworks, Chrome had been handling out-of-order properties just fine, meaning one could use <img src"..." loading="lazy" /> without any issue. I'm not quite sure what about Solid's implementation differs to cause Chrome to now have issues, or why Safari now works as expected, but something seems to be off.

This could very well be browser bug / spec issue territory, of course.

Your Example Website or App

https://github.com/rschristian/solid-lazy-img-bug

Steps to Reproduce the Bug or Issue

  1. yarn
  2. yarn dev (Alternatively, yarn build && yarn preview, issue can be reproduced in both)
  3. Open network pane in Firefox or Chrome
  4. Navigate to localhost:5173
  5. Notice the request for solid.svg, despite being quite far out of the viewport

Expected behavior

The image should be lazily loaded, only being requested when near the viewport / after a scroll.

Screenshots or Videos

No response

Platform

Additional context

No response

ryansolid commented 11 months ago

Thanks @rschristian. Hmm.. I'd think the other framework most similar to our version would be Lit. We do a similar template cloning approach to rendering so. We clone all the elements. and then set the attribute before we attach it to the DOM.

Effectively the code ends up being:

const _tmpl = document.createElement("template");
_tmpl.innerHTML = `<div><div>Spacer</div><img loading="lazy" height="150" width="150">`;

const _el = _tmpl.content.firstChild.cloneNode(true),
    _el2 = _el.firstChild,
    _el3 = _el2.nextSibling;
_el2.style.setProperty("height", "200vh");
_el3.setAttribute("src", "/path/to/asset.svg");
rschristian commented 11 months ago

I can add that Lit isn't subject to the aforementioned FF & Safari issue with its implementation or any problems in Chrome (that I saw; I have been testing 6+ frameworks across 6+ browsers, it's possible I missed something of course).

I'm not sure where or in what details you diverge from Lit, but that might be worth looking into first.

ryansolid commented 10 months ago

Ok I finally looked at this.

My results for your Solid example:

MacOS: Chrome - delayed Firefox - immediate Safari - delayed

Windows Chrome - delayed Firefox - immediate

Basically I only see the issue in Firefox in both OSs. In Chrome changing the order didn't seem to matter either. But Firefox seems to always eagerly load.

I think I may have figured out the difference with Lit. They use document.importNode to clone instead of element.cloneNode this seems to "fix" the issue in Firefox. We already do this swap when there are custom elements I suppose we could always use it instead.

danieltroger commented 8 months ago

Also suffering from this. This coupled with my responsive image implementation causes insane delays where chrome literally locks up for seconds.

Welp, back to the basics with the image related stuff I guess

Screenshot 2023-10-25 at 10 13 29 Screenshot 2023-10-25 at 10 14 16
ryansolid commented 8 months ago

I know the fix but it is slower. I didn't want to make a call here without figuring out of this is spec or a firefox bug.

zoto-ff commented 4 months ago

@ryansolid, I have the same problem without lazy loading, src attr delays render. The delay is small in Chrome, but it's large in Safari.

<img class={style.commentAvatar} src={local.avatar} />

image (3x elements) image (5x elements)

if I just remove src:

<img class={style.commentAvatar} />

image (3x elements)

ryansolid commented 4 months ago

To be fair this issue is about whether lazy works in the given browser. My testing indicated that only Firefox wasn't lazy loading images. That is something we are looking at fixing.

If images themselves are rendering slow that's a different thing and probably has nothing to do with Solid. If Chrome or Safari are locking up with images it comes down to how they are rendering them because all we are doing is setting the src attribute. That could be a reason to use lazy but that is outside this issue (unless lazy isn't working as the spec intends).

zoto-ff commented 4 months ago

@ryansolid, it can be fixed with setTimeout(() => /* set src attr */) <img ref={el => setTimeout(() => el.src = local.src)}> works well

ryansolid commented 4 months ago

Sure.. I'm saying that the src attribute slowing down rendering in the absense of loading="lazy" is expected browser behavior.

zoto-ff commented 4 months ago

there is no difference, both with loading="lazy" and without it still slowing down rendering

beingflo commented 3 months ago

Until it's clear if it's a Firefox bug or something that should be addressed in Solid, is there a workaround for this? Maybe using the Intersection Observer API manually would be the simplest approach?

Antonio-Bennett commented 1 month ago

Seems like this issue will be fixed in Svelte 5 maybe some inspiration can be taken from there?

ryansolid commented 1 month ago

Seems like this issue will be fixed in Svelte 5 maybe some inspiration can be taken from there?

It looks like they are implementing my suggestion higher in the thread https://github.com/solidjs/solid/issues/1828#issuecomment-1671949231

I just have been hesitant because it means worse perf for everyone. But it is probably ultimately the right decision. We just haven't had a minor release in Solid for a while. Not sure when I want to get it in.