ascorbic / unpic-img

Multi-framework responsive image component

Home Page:https://unpic.pics

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

@unpic/svelte: lazy loading problem (different behavior than @unpic/react)

adrienrn opened this issue Β· comments

Describe the bug
My project is a Astro website, one page is using React and another page is using Svelte. When loading the React page, all is well. When loading the Svelte page, it seems to me that @unpic/svelte does not lazy load the images but loads all the images on page load. When using @unpic/react, it does lazy load the first N images and loads the rest as you scroll.

To Reproduce

Here's the version with Svelte: https://codesandbox.io/p/devbox/unpic-svelte-lazy-loading-nyznkw

When loading the page at https://nyznkw-5173.csb.app/, I can see the first 3 images loading and then all the images loading right away (without interacting with the page at all).

image

Here's the React version: https://codesandbox.io/p/devbox/unpic-react-lazy-loading-9ftzh8 which does not exhibit the same behavior. Loading the page at https://9ftzh8-5173.csb.app/:

image

Expected behavior
It is my understanding @unpic/* components would only load the images as they appear in the viewport and it works like a charm with React I must say. I also understand this is using the browser to do it. So is it a browser problem? Is it a Svelte problem, somehow?

I've looked through the code and both component use the core package, but maybe the React binding is doing more than the Svelte one?

Smartphone (please complete the following information):

  • OS: iOS
  • Browser: Chrome
  • Version 120.0.6099.129

Additional context

In my project, I also don't get completion for Image props but that might be my setup for some reason.

I am coming back after some nasty looking debug session 😬

What I did: duplicate the Image.svelte component locally (still relying on @unpic/core, so this is just the binding to Svelte), commenting out all the bits and adding props one by one to the image tag.

1. Testing out order or props

The following code works!
(it's not TS as it's a quick and dirty version and ImageProps is not exposed as a type)

<script>
  import { transformProps } from "@unpic/core";
  import styleToCss from "style-object-to-css-string";
  const { style: parentStyle, ...props } = $$props;
  const transformedProps = transformProps({ ...props });

  const { alt, decoding, loading, srcset, style, src } = transformedProps;
  const styleAsString = styleToCss(transformedProps.style);
</script>

<img {alt} {decoding} {src} {srcset} {loading} style={styleAsString} on:load />
  1. If I spread transformedProps, it does not work but specifying props one by one just does. Hm.
  2. It's not the ordering, I can swap props in any order and it still works. What made me try this is this issue: sveltejs/svelte#7657 and this related PR: https://github.com/sveltejs/svelte/pull/7833/files specifying how props are handled in Svelte (sequentially).

2. Reactivity

A difference is that my version is not reactive so I re-added the reactivity like below and... to my surprise it works. I don't know what it is with spreading and Svelte.

Full version at: https://codesandbox.io/p/devbox/unpic-svelte-lazy-loading-forked-4ygy95

<script>
  import { transformProps } from "@unpic/core";
  import styleToCss from "style-object-to-css-string";

  $: ({ style: parentStyle, ...props } = $$props);
  $: ({
    alt,
    style: styleObj,
    src,
    width,
    height,
    loading,
    decoding,
    srcset,
    role,
    sizes,
    fetchpriority
  } = transformProps({ ...props }));
  $: style = [styleToCss(styleObj || {}), parentStyle]
    .filter(Boolean)
    .join(";");
</script>
  
<img {alt} {style} {src} {width} {height} {loading} {decoding} {srcset} {role} {sizes} on:load />
image

Great work. Thanks for looking into this. What happens if you pass other valid img attributes? We want to allow and pass through any other img prop, which are too many to specify by hand.

Thanks! I totally get what you're asking. What's above is not a fix/is not a viable solution.

In order to support any props, we would probably need to spread {...$$props}. I gave it a try (https://codesandbox.io/p/devbox/unpic-svelte-lazy-loading-forked-2-7t5rxj) and it looks like so:

{#each assets as asset}
  <div>
    <Image
      data-id={asset.uid}
      ...
...
<img
  {...$$props}
  {alt}
  {style}
  {loading}
  {width}
  {height}
  {decoding}
  {src}
  {srcset}
  {role}
  {sizes}
  {fetchpriority}
  on:load
/>

πŸ₯ It does not load all the images but it seems to trigger a re-render/reload of the images (2x 3 images in the screenshot below):

image
commented

tl;dr

This comment is light* on answers and heavy on anecdotal experience.

*"Light" = Close to none


@ascorbic Thanks for @unpic-img and @unpic-placeholder! They're very well made and really helped in a Svelte project I'm working on.

@adrienrn Thanks for finding, investigating, reporting, and posting a fix for this issue.

I thought I was noticing it too but wasn't sure of the cause at the time. It seemed like it could have something to do with @unpic-img. But if it did, I didn't think I was going to be able to figure it out.

I was about to create an issue for it and found this.


Confirmation-ish

@adrienrn I can confirm what you found originally, that @unpic-img/svelte + lazy loading is not working in some cases.

(I'm not able to test @unpic-img/react)

I read through this post of yours above, followed the reference links, and agree with everything.

I tried your patched component and it definitely worked in a reduced test case. Swapping between unpic-img/svelte @ 0.0.43 and the patched code reliably made lazy loading go from not working ➑ working in Brave and Safari (Firefox is another story, more on that below).

The priority attribute is respected as well.

After a bunch of testing (where, apparently, I was unknowingly seeing false positives and negatives) I decided to use the patched component (instead of the native @unpic-img component) in a production site (🀞🏻) and I haven't noticed any new problems.


"I feel like I'm taking crazy pills"

However I still notice that not all images* are lazy loading.

One page yes, another page no, different behaviors in different browsers, and via different Reload methods.

(*Images are sourced from imgix.net)

In my reduced test case, I setup something like this in a test route of my existing production project (IOW: It's a child of /+layout.svelte):

<div style="height: 5000px;"></div>

<img src="url" loading="lazy">

<Image src="url">

In both cases the standard <img src="url" loading="lazy"> works as expected. (Even in Firefox: #1647077)

(I also tried <img loading="lazy" src="url">. That works too.)

So that's cool.

But beyond that, things start to get odd and I can't find the pattern of when lazy loading will/won't work.

I've tried:

  • Different Reload methods
  • Different browsers
  • Different pages
  • Disabled cache or non-Disabled cache
  • npm run dev vs. npm run preview (or deployed to the server) seems to affect loading as well.

In all of those cases, I seem to get mixed results.

As for the production site, one page's images will lazy load and another's will not.

  • βœ… https://castlerockmusic.com

    • Lazy loading usually/mostly works as expected.
    • There's different sizes of images in many different containing elements.
    • Some images are even inside of containers with overflow-y: auto. And those horizontally overflowing images do lazy load.
    • (A few above-the-fold images have the priority attribute and they load eagerly as expected.)
  • ❌ https://castlerockmusic.com/photos/

    • Lazy loading usually doesn't work as expected.
    • These images are in a single container (each <Image> is inside of a <figure>), one after the other. A much simpler arrangement. One that makes the page very tall. Perfect, I think, to test lazy loading.
    • And these pretty reliably don't lazy load as expected.
    • (The first 2x above-the-fold images have the priority attribute and again they too load eagerly.)
  • At the bottom of every page is a component with a quote from an artist along with an image. That seems to reliably lazy load. It doesn't seem subject to the methods I tried in "I've tried" above. I don't know what to make of that.

The type of reloading seems to affect this as well

  • Enter key while focus is in the browser's location bar
  • Reload button
  • Hard reload (something like shift + Reload button)

And then there's the browsers

I think I'm seeing different behaviors in:

  • Safari (16.5)
  • Brave (1.61, Chromium 120)
  • Firefox (121)

I thought about creating tables to mark βœ…/❌ lazy loading for each <Image> component version, page, browser, and reload type.

But I didn't do that yet because I thought maybe there's something simple, or fundamental which would explain the seemingly inconsistent results, that I've missed in my research.

IOW: Something that's not specific to unpic-img. Maybe something related to browsers or SvelteKit.

But if you think that would help, let me know and I'll put that together.


I hope this anecdotal report helps a little.

I'm sorry I wasn't able to track down the pattern that's causing these results. (I look forward to looking very dumb very soon :-)

Hopefully it helps either of you (or others) out.

Thanks again!

The thing that makes no sense to me is that the current version sets loading="lazy", so I don't understand why the browser is doing this, and why your example is any different.

commented

The thing that makes no sense to me is that the current version sets loading="lazy", so I don't understand why the browser is doing this, and why your example is any different.

I agree.

At this point, I think it has more to do with browser heuristics.

This is some of the best information I've been able to find about this. I don't think any of these exactly nail down the issue I'm seeing, but there's helpful information about this subject, for anyone perusing this issue in the future.


In updated to unpic-img/svelte 0.0.46 (Which uses: #485).

It seems to work well (those other probably-browser-related-usses aside).

Thanks again.

Yeah. To clarify, I confirmed the fix was just to move src and srcset before loading in the attributes list.