@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](https://private-user-images.githubusercontent.com/5598481/296732497-d2aa65ea-1f78-4ecf-855f-1ecb2fbba36d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTU4NjM4NDYsIm5iZiI6MTcxNTg2MzU0NiwicGF0aCI6Ii81NTk4NDgxLzI5NjczMjQ5Ny1kMmFhNjVlYS0xZjc4LTRlY2YtODU1Zi0xZWNiMmZiYmEzNmQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDUxNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA1MTZUMTI0NTQ2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YzM3OTIxN2NjNzgzODQ0M2MzMWNlNmRiYzMxYTMxMjA3ZDY1YWE1YzRhN2U1ZTVhYzM5MDQ5NjIxZTMzZWZhMCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.-5nNVPO-Ygrhq8X0Fc8VVTMtH3fz-B9r5BBE1LAEjac)
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](https://private-user-images.githubusercontent.com/5598481/296733125-567c8438-3221-4300-883a-060c63eeb085.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTU4NjM4NDYsIm5iZiI6MTcxNTg2MzU0NiwicGF0aCI6Ii81NTk4NDgxLzI5NjczMzEyNS01NjdjODQzOC0zMjIxLTQzMDAtODgzYS0wNjBjNjNlZWIwODUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDUxNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA1MTZUMTI0NTQ2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTUzYzA1NmE4NWEzOTQ3MDRlZmY2YzhiZTM1NTFiNmQ3NGE2ZDhlYzJlZDVmYzc4ZDhkMGM5OTg4ODFhMmRiOSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.A9-d-8wZVrR10DFlMMQZLDBbCTVyGNmrAXYNp4A7Knc)
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 />
- If I spread
transformedProps
, it does not work but specifying props one by one just does. Hm. - 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](https://private-user-images.githubusercontent.com/5598481/296827130-a6e5f155-3f73-4e70-a45d-89ed84e02308.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTU4NjM4NDYsIm5iZiI6MTcxNTg2MzU0NiwicGF0aCI6Ii81NTk4NDgxLzI5NjgyNzEzMC1hNmU1ZjE1NS0zZjczLTRlNzAtYTQ1ZC04OWVkODRlMDIzMDgucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDUxNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA1MTZUMTI0NTQ2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9Y2M4YWY2NjNiOGViNWUwMmE5YjYzMTU1MmVkZTFjNzkxOTg4YjYyOWQwOTQ1NmE2ZWM1YmFmY2IyN2E2ZTA3ZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.GK4Mad6MAPJzg9G35Ts2tm2jxNAbWSYTMaFMHqacq7g)
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](https://private-user-images.githubusercontent.com/5598481/296843992-ec3071fe-07f5-4451-8885-52942826ec24.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTU4NjM4NDYsIm5iZiI6MTcxNTg2MzU0NiwicGF0aCI6Ii81NTk4NDgxLzI5Njg0Mzk5Mi1lYzMwNzFmZS0wN2Y1LTQ0NTEtODg4NS01Mjk0MjgyNmVjMjQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDUxNiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA1MTZUMTI0NTQ2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MzM2MzI2MTMxZTk5YjQxZmE4YjlmNzJmY2IyMDFhZTQ3ZWFlMTNmNmQ1YmFmYTBlZGJjNjg3NGFlNjEwYjVmZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.QuAFUIU_5K0eNKLIsqsREsJ0HMvoXbovGGdbkEpH9Ag)
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">
- β unpic-img/svelte @ 0.0.43: Lazy loading doesn't always work as expected.
- β @adrienrn's patched component): Lazy loading usually work as expected.
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 barReload
buttonHard 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.
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.
- Inconsistent user-experiences with native lazy-loading images
- The performance effects of too much lazy loading
- Browser-level lazy loading for CMSs
- LazyLoad: Experiment with loading the first K images eagerly
- Omit lazy-loading attribute on first content image/iframe
- "lazy loading" implementation on images have a different behaviour depending on the order you define attributes
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.