volto icon indicating copy to clipboard operation
volto copied to clipboard

Add Image for srcset and lazy loading

Open nzambello opened this issue 3 years ago • 37 comments

Features:

  • Add ImageFromUrl component which selects Plone miniatures using srcset attr and using lazy loading as in #2097
  • Add getSrcSet helper for srcset and sizes basing on selected miniature

Added code and tests, refactored components with images to use ImageFromUrl

This should improve performances avoiding loading heavy images if not needed.

nzambello avatar Jan 05 '21 14:01 nzambello

My experiment, which passed the validator, is here https://github.com/datakurre/my-volto-app/commit/89c86ba93db2ad404f45a7dfc5fc9dde686390e6

I don't remember my use of sizes any more :see_no_evil:

But I recall that we had to hack Plone image sizes with unlimited heights, e.g.

huge 1600:65536
great 1200:65536
large 800:65536
preview 400:65536
mini 200:65536

to avoid browser getting scale, which required blocky upscaling :thinking:

datakurre avatar Jan 05 '21 19:01 datakurre

@datakurre thanks a lot!! I searched for it, but I couldn't find it!

sneridagh avatar Jan 05 '21 19:01 sneridagh

Updated the PR:

  • Refactor of the new component and the helper method
  • Renamed both, now it's Image
  • Loaded low resolution placeholder loading afterwards the with other resolutions using the width descriptor making the browser to decide which image is the best match to load based on the actual size of the image on the page

nzambello avatar Jan 15 '21 11:01 nzambello

Preview of the loading with the placeholder. https://user-images.githubusercontent.com/21101435/104717825-37b84c00-572a-11eb-847f-d1dc75237bde.mov

Heavily inspired by Gatsby image

nzambello avatar Jan 15 '21 11:01 nzambello

Sizes we use for the EEA website:

print 2000:2000
panoramic 1920:1080
landscape 1370:771
portrait 771:1370
xlarge 950:950
wide 325:183
large 768:768
preview 400:400
mini 200:200
thumb 128:128
tile 64:64
icon 32:32
listing 16:16

avoinea avatar Jan 19 '21 10:01 avoinea

It doesn't seem like the srcset properly responds to the images. I can see the /listing being loaded, but it's always loading the big image instead of one of the smaller sizes. Maybe the sizes attribute needs to be specified?

tiberiuichim avatar Jan 22 '21 22:01 tiberiuichim

I'm sorry to have not been able to try this pull yet, but reading the Smasing Magazine article and trying out the linter really helped me:

  • https://www.smashingmagazine.com/2014/05/responsive-images-done-right-guide-picture-srcset/
  • https://github.com/datakurre/my-volto-app/commit/89c86ba93db2ad404f45a7dfc5fc9dde686390e6
  • https://ausi.github.io/respimagelint/

So srcset could describe all the available versions and their widths, but it may not be enough for browser to properly choose the size, because browser might load the image before it knows the eventual size of the rendered image area (and loading the largest one just in case). That's why sizes is needed to tell the browser the size of the image to help it to choose the right scale:

https://github.com/datakurre/my-volto-app/commit/89c86ba93db2ad404f45a7dfc5fc9dde686390e6#diff-b46cef1aaa7352b0e8144c7a2e58944f9640faea4b66dff62bfa86bd1f9aa8b1R48

Also, because sizing is based on widths, Plone image scaling must provide those widths or images may be upscaled on browser. That's how I ended with image sizes like large 800:65536.

datakurre avatar Jan 23 '21 08:01 datakurre

Thanks @datakurre!

I ran the validator on the PR, on a page with a listing block and a hero block. Here's the things it has to say:

Screenshot 2021-01-23 124120

tiberiuichim avatar Jan 23 '21 10:01 tiberiuichim

Isn't the @@images/image/listing size too small? 16px, as is the default, is no bigger then a small icon. Yes, it loads fast, but it's also ugly. I suggest maybe use tile (64px) or thumb (128px) as default?

tiberiuichim avatar Jan 23 '21 10:01 tiberiuichim

For me the validator results look like I expected: Plone image scaling is too smart for srcset and that's why widths do not match. My indefinitive heights in image scale forces Plone always to scale by width, what should make it match.That should also prevent cropping mentioned by validator. Also sizes-attribute should be set (and that's the hardest one, because its values probably depend on themes).

datakurre avatar Jan 23 '21 11:01 datakurre

@datakurre No worries, I haven't yet plugged the new image scales into Plone, this was with the default image scales. I am trying to get a feeling of what this whole problem space is about. The above comment, with the screenshot, wasn't aimed at you. Just documenting things as I encounter them

tiberiuichim avatar Jan 23 '21 11:01 tiberiuichim

@nzambello My hope for this PR is that, in the end, it provides a comprehensive solution for responsive images. One issue I have though, and maybe I have read the code wrong, but it seems to only take Plone image content types into account (so basically, only the "image" field). I think this should be handled better and allow also handling content types with either non-standard image field or multiple image fields.

tiberiuichim avatar Jan 23 '21 11:01 tiberiuichim

No problem. For me the key for understanding this was to acknowledge that there is no magic involved, but browser really must be able to decide the optimal scale from HTML only. On 23. Jan 2021, 13.23 +0200, Tiberiu Ichim [email protected], wrote:

@nzambello My hope for this PR is that, in the end, it provides a comprehensive solution for responsive images. On issue I have though, and maybe I have read the code wrong, but it seems to only take Plone image content types into account (so basically, only the "image" field). I think this should be handled better and allow also handling content types with either non-standard image field or multiple image fields. — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

datakurre avatar Jan 23 '21 11:01 datakurre

Another idea: when definining image scales, I think it could be interesting to also define resizing parameters. Looking at these parameters, for example (most important, fit type and positions. Right now this PR only defines width in the image scales, thought kitconcept.volto defines square images as well (which we may want to chose how to fit):

    icon 32:32
    tile 64:64
    thumb 128:128
    mini 200:65536
    preview 400:65536
    teaser 600:65536
    large 800:65536
    larger 1000:65536
    great 1200:65536
    huge 1600:65536

The square image formats would need to be cropped.

tiberiuichim avatar Jan 24 '21 18:01 tiberiuichim

Roadmap:

  • [ ] Agree on the current settings
  • [ ] Implement current settings in kitconcept.volto
  • [ ] Document the image scales Volto expects from the backend
  • [ ] Gain experience with the new image sizes in production
  • [ ] Write PLIP to update Plone 6 core

tisto avatar Feb 02 '21 14:02 tisto

@sneridagh we now have the correct image scale being loaded only when the image is visible and the placeholder has loaded, so we have the actual size of the image in the page. I don't fully like this behavior, but I think it's the best way we have without forcing developers to know and set the final image width. It would be an anti-responsive pattern I guess.

nzambello avatar Oct 27 '21 14:10 nzambello

@nzambello it should be updated with the new way of showing images that is now used in the image block

giuliaghisini avatar Nov 19 '21 07:11 giuliaghisini

@plone/volto-team this is ready IMO. Fixed remaining things, tested in a production environment and updated the documentation.

nzambello avatar Nov 26 '21 13:11 nzambello

Deploy Preview for volto ready!

Name Link
Latest commit a07bc4c13ea948e7aacddf47dab623b072939cd2
Latest deploy log https://app.netlify.com/sites/volto/deploys/62b31ac1a31ee00008e28eca
Deploy Preview https://deploy-preview-2103--volto.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

netlify[bot] avatar Mar 29 '22 14:03 netlify[bot]

I'm not sure if understand the code right, but you did not seem to define sizes right? I think it would be better, so that the browser knows the image size before it is loaded, even more so when using lazy loading. I'm currently trying to solve this for classic UI. On one hand enabling the browser to automatically select the right image scale is awesome. But i would like to also allow art direction. In the form of providing different cropped versions for smaller screens, like i could create via plone.app.imagecropping. Art direction is only possible via source tags in combination with media queries.
This is more explicit and you can force the browser to choose e special scale which contains your cropped image. With srcset and sizes the browser will choose what ever fit's in the moment, even a bigger scale, when the bigger scale is already in the cache. This way you might see the bigger scale on small screens which is fine from the quality point of view, but might not what you want. The bigger scale does not have the crop and looks bad on mobile. Here is a good example of that problem and the solution: https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images

The challenge now is to find the right amount of flexibility without making the configuration to complex. I would like to exchange ideas on that topic. I'll join the team in Bucharest for the Beethoven Sprint, i hope we can make some progress then.

MrTango avatar Apr 23 '22 17:04 MrTango

I'm not sure if understand the code right, but you did not seem to define sizes right? I think it would be better, so that the browser knows the image size before it is loaded, even more so when using lazy loading. I'm currently trying to solve this for classic UI. On one hand enabling the browser to automatically select the right image scale is awesome. But i would like to also allow art direction. In the form of providing different cropped versions for smaller screens, like i could create via plone.app.imagecropping. Art direction is only possible via source tags in combination with media queries. This is more explicit and you can force the browser to choose e special scale which contains your cropped image. With srcset and sizes the browser will choose what ever fit's in the moment, even a bigger scale, when the bigger scale is already in the cache. This way you might see the bigger scale on small screens which is fine from the quality point of view, but might not what you want. The bigger scale does not have the crop and looks bad on mobile. Here is a good example of that problem and the solution: https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images

The challenge now is to find the right amount of flexibility without making the configuration to complex. I would like to exchange ideas on that topic. I'll join the team in Bucharest for the Beethoven Sprint, i hope we can make some progress then.

@MrTango image sizes are defined here https://github.com/plone/volto/blob/3660875628a9c721994a7de4a10309ad5735f1d9/src/config/ImageScales.jsx and inject in config here https://github.com/plone/volto/blob/3660875628a9c721994a7de4a10309ad5735f1d9/src/config/index.js#L117.

Browser try to determine the size of image here https://github.com/plone/volto/blob/3660875628a9c721994a7de4a10309ad5735f1d9/src/components/theme/Image/Image.jsx#L65 and loads the src-set maximum image size based on available width and pixel ratio.

In Volto we don't have yet the concept of 'cropped image' only the scaled image. I think crop will be a next feature and is not a theme to take care now on Volto.

giuliaghisini avatar Apr 26 '22 11:04 giuliaghisini

Do you set the sizes attribute somewhere? This is important for the browser to know how big the image will be on the screen. This will be used to calculate the needed image size by looking at the screen resolution (2x,3x aso) and the Intrinsic_Size of the image.

For example:

<picture class="image-size-large">
  <source
    srcset="https://picsum.photos/id/1011/800 800w,
            https://picsum.photos/id/1011/1000 1000w,
            https://picsum.photos/id/1011/1200 1200w,
            https://picsum.photos/id/1011/1600 1600w"
    sizes="800px"
    >
  <img src="https://picsum.photos/id/1011/800" sizes="800px">
</picture>

The image intrinsic size of the image is 800px. On a older 1x desktop screen that means the browser might prefer the image scale: https://picsum.photos/id/1011/800, but on a high dpi screen let's say 2x, the Browser would require the scale https://picsum.photos/id/1011/1600 1600w

If you don't provide the sizes attribute, the intrinsic size has to be calculated from CSS definitions, that takes more time.

PS. I'll be at the sprint in Bucharest the following days, let me know if we could sit together to discuss this and also my ideas for the srcset config in the backend. I made the default config much smaler now, but it is still valid to have it and gives us to the option to improve the frontends in time more.

MrTango avatar May 03 '22 10:05 MrTango

Do you set the sizes attribute somewhere? This is important for the browser to know how big the image will be on the screen. This will be used to calculate the needed image size by looking at the screen resolution (2x,3x aso) and the Intrinsic_Size of the image.

For example:

<picture class="image-size-large">
  <source
    srcset="https://picsum.photos/id/1011/800 800w,
            https://picsum.photos/id/1011/1000 1000w,
            https://picsum.photos/id/1011/1200 1200w,
            https://picsum.photos/id/1011/1600 1600w"
    sizes="800px"
    >
  <img src="https://picsum.photos/id/1011/800" sizes="800px">
</picture>

The image intrinsic size of the image is 800px. On a older 1x desktop screen that means the browser might prefer the image scale: https://picsum.photos/id/1011/800, but on a high dpi screen let's say 2x, the Browser would require the scale https://picsum.photos/id/1011/1600 1600w

If you don't provide the sizes attribute, the intrinsic size has to be calculated from CSS definitions, that takes more time.

PS. I'll be at the sprint in Bucharest the following days, let me know if we could sit together to discuss this and also my ideas for the srcset config in the backend. I made the default config much smaler now, but it is still valid to have it and gives us to the option to improve the frontends in time more.

yes, for example, the html generated is this: <picture class="volto-image"><source srcset="/huge.jpg/@@images/image/listing 16w, /huge.jpg/@@images/image/icon 32w, /huge.jpg/@@images/image/tile 64w, /huge.jpg/@@images/image/thumb 128w, /huge.jpg/@@images/image/mini 200w, /huge.jpg/@@images/image/midi 300w, /huge.jpg/@@images/image/preview 400w, /huge.jpg/@@images/image/teaser 600w, /huge.jpg/@@images/image/large 800w, /huge.jpg/@@images/image/larger 1000w, /huge.jpg/@@images/image/great 1200w, /huge.jpg/@@images/image/huge 1600w"><img src="/huge.jpg/@@images/image/listing" alt="" class="full-width" role="img" loading="lazy" style="width:100%;object-fit:cover"></picture>

we didn't set the real size of image in attributes because we don't know the real size of image here, we only know the miniature size.. this is the big problem of this implementation and need to think about it..
Another problem that we have here, is that srcset is injected in page only when image meets intersection observer.. it was a way to do the lazy loading of image... but i don't think it's the best way to do it, because this causes two image loading for one image:

  • the image set in src attribute of (/huge.jpg/@@images/image/listing)
  • and the needed image size from srcset.

Any ideas?

giuliaghisini avatar May 03 '22 12:05 giuliaghisini

@giuliaghisini I'm sorry for my previous comment (I now deleted) that made no sense.

Regarding sizes, could sizes="100vw" be better than nothing? At least then browser would never choose a size greater than the window itself. (Or does browser act like that by default?)

Or does it already work properly? Just loading listing size at first and the correct scale when CSS has been calculated?

I recall that my old PoC https://github.com/datakurre/my-volto-app/commit/89c86ba93db2ad404f45a7dfc5fc9dde686390e6#diff-3f5fdd396d4f51d380bccc043229925fc08f0b5c5bffa414d8e4e1cf9325e55bR19 only loaded a single scale, but I did not try that with loading="lazy" :thinking:

datakurre avatar May 03 '22 12:05 datakurre

@giuliaghisini I'm sorry for my previous comment (I now deleted) that made no sense.

Regarding sizes, could sizes="100vw" be better than nothing? At least then browser would never choose a size greater than the window itself. (Or does browser act like that by default?)

Or does it already work properly? Just loading listing size at first and the correct scale when CSS has been calculated?

I recall that my old PoC datakurre/my-volto-app@89c86ba#diff-3f5fdd396d4f51d380bccc043229925fc08f0b5c5bffa414d8e4e1cf9325e55bR19 only loaded a single scale, but I did not try that with loading="lazy" 🤔

yes, it could be better than nothing.. it's the sizes of the 'placeholder' slots into which the browser can insert images from the srcset.. Maybe 100vw will be too much, it will cause some 'flickers' area in page when real image is loaded.. i think.. we have to try...

Maybe sizes could be setted in applySrcSet fn where we know the real image size.. but i don't know it could be correct.. what do you think?

giuliaghisini avatar May 03 '22 13:05 giuliaghisini

@datakurre @MrTango i've added sizes="100vw" as default and make some improvements. Now i think it's working much better than before.. Could you try it? for sizes.. i think we cannot calculate them.. it depends on page layout.. i've added sizes as props to pass to the Image component and setted default to '100vw'

giuliaghisini avatar May 03 '22 14:05 giuliaghisini

@giuliaghisini Did you try https://ausi.github.io/respimagelint/ already?

datakurre avatar May 03 '22 15:05 datakurre

I think sizes as a prop is good start. Once someone is really building responsive theme with optimal sec-sets, that someone probably gets an idea, how this should be enhanced.

datakurre avatar May 03 '22 15:05 datakurre

giuliaghisini

we didn't set the real size of image in attributes because we don't know the real size of image here, we only know the miniature size.. this is the big problem of this implementation and need to think about it..

while defining the srcset we give all the needed info's by appending the size like 800w of that scale. The sizes attribute, is not only useful together with media queries, if you leave them of as i did in my example, you go just with the fallback size of 800px. This size is the size the image would take on the screen, no matter of the screen resolution.

On a 1x resolution screen it will be 800px as well as on a 2x resolution screen. Think of it as the physical size of the image.

Another problem that we have here, is that srcset is injected in page only when image meets intersection observer.. it was a way to do the lazy loading of image... but i don't think it's the best way to do it, because this causes two image loading for one image:

the image set in src attribute of (/huge.jpg/@@images/image/listing)
and the needed image size from srcset.

i don't why you think you need this, if you add the loading="lazy" attribute, the browser should be able to load lazy. Right now chrome will still load to fast all images, firefox and safari will save more data and only load images when you are getting close to them. So with that in mind, if you want the optimum control and have it working on chrome you have to do it with intersection observer.

https://www.ctrl.blog/entry/lazy-loading-viewports.html

For Plone 6 classic UI we will probably stick with the browser defaults and just use loading="lazy". Chrome will eventually fix the behavior.

I guess when you add the srcset and the src attributes via the observer it should work, the browser can't load anything without either a src or a srcset.

datakurre: src-set should really just list all the available images and their real pixel width

then

sizes attributes is there for telling the view port size; it supports responsive breakpoints and CSS math

Just with these in place, browser will pick whatever size it sees best fit. I didn't even see 2x or 3x src-set items to be really required, because browser was able know its pixel density and choose the right image, as long as one was available.

not quite, the sizes attribute with out a media query defines the Intrinsic Size of the image, the same you can archive by setting the width attribute on the img tag. But it is true that the Browser is able to do the math for us, by calculating Intrinsic Size * screen resolution >> needed image size.

With these sizes in place, I know that image scaling always scaled by width, src-set was giving correct information and browser was able choose the right size.

sizes attribute, of course, is more tricky, because its optimal value really depends on your theme.

it depends if you want to change the Intrinsic Size for different viewports, most of the time that not needed. With my example above, the image is 800px on a Desktop. For mobile that resolution works too. As we need a bit less than 400px usually * screen resolution (somewhere between 2x and 4x), leaves us with needed sizes of (800px up to 1600). Together with the general definition of img {max-with: 100%;} it only make sense when the image is smaller on Desktop than on a smaller screen.

For example if our image is 250px and we want it to be almost full screen on a mobile device.
Than something like this make sense: sizes="(max-width: 768px) 90vw, 250px".

In most cases it's not worth optimizing to much.

https://css-tricks.com/a-guide-to-the-responsive-images-syntax-in-html/

MrTango avatar May 03 '22 17:05 MrTango

also one thing i noted while playing around with my example page is that we need to set the height when we are using lazy loading or everything jumps arround :dancers: I hope we can get together tomorrow on discord for a chat.

MrTango avatar May 03 '22 17:05 MrTango