Lazy Loading HTML5 picture element - javascript

I have been searching (unsuccessfully) for a reliable method to lazy load images while using the HTML5 spec for <picture>. Most solutions/plugins out there currently rely on using data- attributes. I could be wrong, but it doesn't seem this method will work in conjunction w/ <picture>.
I'm really just looking to be pointed in the right direction. If anyone has a solution that they're currently using, I'd love to see. Thanks!
Here is standard markup per the HTML5 spec:
<picture width="500" height="500">
<source media="(min-width: 45em)" src="large.jpg">
<source media="(min-width: 18em)" src="med.jpg">
<source src="small.jpg">
<img src="small.jpg" alt="">
</picture>

It's February 2020 now and I'm pleased to report that Google Chrome, Microsoft Edge (the Chromium-based Edge), and Mozilla Firefox all support the new loading="lazy" attribute. The only modern browser hold-out is Apple's Safari (both iOS Safari and macOS Safari) but they've recently finished adding it to Safari's codebase, so I expect it will be released sometime this year.
The loading="lazy" attribute is only for the <img /> element (and not <picture>) but remember that the <picture> element does not represent the actual replaced-content, the <img /> element does (i.e. the image that users see is always rendered by the <img /> element, the <picture> element just means that the browser can change the <img src="" /> attribute. From the HTML5 spec as of February 2020 (emphasis mine):
The picture element is somewhat different from the similar-looking video and audio elements. While all of them contain source elements, the source element's src attribute has no meaning when the element is nested within a picture element, and the resource selection algorithm is different. Also, the picture element itself does not display anything; it merely provides a context for its contained img element that enables it to choose from multiple URLs.
So doing this should just work:
<picture>
<source media="(min-width: 45em)" srcset="large.jpg" />
<source media="(min-width: 18em)" srcset="med.jpg" />
<source src="small.jpg" />
<img src="small.jpg" alt="Photo of a turboencabulator" loading="lazy" />
</picture>
Note that the <picture> element does not have any width or height attribute of its own; instead the width and height attributes should be applied to the child <source> and <img> elements:
4.8.17 Dimension attributes
The width and height attributes on img, iframe, embed, object, video, source when the parent is a picture element [...] may be specified to give the dimensions of the visual content of the element (the width and height respectively, relative to the nominal direction of the output medium), in CSS pixels.
[...]
The two attributes must be omitted if the resource in question does not have both an intrinsic width and an intrinsic height.
So if you want all <source> images to be rendered as 500px by 500px then apply the size to the <img> element only (and don't forget the alt="" text for vision-impaired users, it's even a legal requirement in many cases):
<picture>
<source media="(min-width: 45em)" srcset="large.jpg" />
<source media="(min-width: 18em)" srcset="med.jpg" />
<source src="small.jpg" />
<img src="small.jpg" alt="Photo of a turboencabulator" loading="lazy" width="500" height="500" />
</picture>

For anyone still interested...
After revisiting this issue, I came across a fairly new script called, Lazysizes. It's actually quite versatile, but more importantly it allows me to do lazy loading of images while utilizing the HTML5 markup as described in the OP.
Much thanks to the creator of this script, #aFarkas.

Working example of lazy loading images using the picture element and intersection observer tested in Chrome and Firefox. Safari doesn't support intersection observer so the images are loaded immediately, and IE11 doesn't support the element so we fallback to the default img
The media queries in the media attr are arbitrary and can be set to suit.
The width threshold set is 960px - try a reload above and below this width to see either the medium(-m) or large(-l) variation of the image being downloaded when the image is scrolled into the viewport.
Codepen
<!-- Load images above the fold normally -->
<picture>
<source srcset="img/city-m.jpg" media="(max-width: 960px)">
<source srcset="img/city-l.jpg" media="(min-width: 961px)">
<img class="fade-in" src="img/city-l.jpg" alt="city"/>
</picture>
<picture>
<source srcset="img/forest-m.jpg" media="(max-width: 960px)">
<source srcset="img/forest-l.jpg" media="(min-width: 961px)">
<img class="fade-in" src="img/forest-l.jpg" alt="forest"/>
</picture>
<!-- Lazy load images below the fold -->
<picture class="lazy">
<source data-srcset="img/river-m.jpg" media="(max-width: 960px)">
<source data-srcset="img/river-l.jpg" media="(min-width: 961px)">
<img data-srcset="img/river-l.jpg" alt="river"/>
</picture>
<picture class="lazy">
<source data-srcset="img/desert-m.jpg" media="(max-width: 960px)">
<source data-srcset="img/desert-l.jpg" media="(min-width: 961px)">
<img data-srcset="img/desert-l.jpg" alt="desert"/>
</picture>
and the JS:
document.addEventListener("DOMContentLoaded", function(event) {
var lazyImages =[].slice.call(
document.querySelectorAll(".lazy > source")
)
if ("IntersectionObserver" in window) {
let lazyImageObserver =
new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.nextElementSibling.srcset = lazyImage.dataset.srcset;
lazyImage.nextElementSibling.classList.add('fade-in');
lazyImage.parentElement.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// Not supported, load all images immediately
lazyImages.forEach(function(image){
image.nextElementSibling.src = image.nextElementSibling.dataset.srcset;
});
}
});
One last thought is that if you change the screen width back and forth, the image files are repeatedly downloaded again. If I could tie the above method in to a cache check then this would be golden...

On web.dev Google advices that picture tags can be loaded lazy.
https://web.dev/browser-level-image-lazy-loading/
Images that are defined using the element can also be lazy-loaded:
<picture>
<source media="(min-width: 800px)" srcset="large.jpg 1x, larger.jpg 2x">
<img src="photo.jpg" loading="lazy">
</picture>
Although a browser will decide which image to load from any of the elements, the loading attribute only needs to be included to the fallback element.

Related

How to render different images based on screen size

I want to render a different svg based on the browser screen width. I've found a solution that expects me to use srcSet within an img tag. However, I can't get it to work.
The goal should be to render SmallTalents on mobile, and BigTalents on tablets+
Quick notes:
I'm using TailwindCSS.
The svgs are completely different, not just scaled down versions.
Example
import React from "react";
import SmallTalents from "../../../assets/smallTalents.svg";
import BigTalents from "../../../assets/bigTalents.svg";
function Example() {
return (
<section>
<img
srcSet={`${SmallTalents}, 200w ${BigTalents} 600w`}
sizes="(max-width: 600px) 480px, 800px"
src={SmallTalents}
alt="talents"
/>
</section>
);
}
export default Example;
Update:
Tried this, not working
<picture>
<source media="(min-width: 300px)" srcset={SmallTalents} />
<source media="(min-width: 900px)" srcset={BigTalents} />
</picture>
Solution (not ideal)
<img src={SmallTalents} alt="talents" className="my-5 md:hidden" />
<img src={BigTalents} alt="talents" className="my-5 hidden md:flex" />
You can use picture element in html for rendering different images based on the screen size:
<picture>
<source media="(min-width: 650px)" srcset="img_food.jpg">
<source media="(min-width: 465px)" srcset="img_car.jpg">
<img src="img_girl.jpg">
</picture>
I think there is a small typo in your srcSet property, the 200w should be on the left side of the comma. i.e srcSet={${SmallTalents} 200w, ${BigTalents} 600w}

Fallback image if browser can't display webm with alpha channle

I have a webm with transparency. If the browser can't show it, I want to show an image instead.
I tried the following first:
<video autoplay loop muted>
<source src="http://video.webmfiles.org/big-buck-bunny_trailer.webm" type="video/webm" />
<img src="http://placehold.it/350x150" />
</video>
This posed two problems:
Internet explorer doesn't show anything at all.
Firefox shows the webm with a black background; no transparency.
I gave up a little and took to Javascript (and Modernizr):
<img ... class="shittybrowser" />
<video ... class="gloriousbrowser" style="display:none;" />
 
if(Modernizr.video.webm){
$(".shittybrowser").hide();
$(".gloriousbrowser").show();
}
This fixed the problem in Internet Explorer but Firefox still claims to do webm, despite only half-assing it.
How do I make sure only browsers that can display webms with transparency does so?
You can set image as value of poster attribute at <video> element
poster
A URL indicating a poster frame to show until the user plays or seeks.
If this attribute isn't specified, nothing is displayed until the
first frame is available; then the first frame is shown as the poster
frame.
<video autoplay loop muted poster="http://placehold.it/350x150">
<source src="http://video.webmfiles.org/big-buck-bunny_trailer.webm"
type="video/webm" />
</video>

How to do lazy loading image with srcset?

I'm using slick.js to build a carousel. However, even though I change the attribute from src to data-lazy the images still get loaded before I scroll to that image. I suspect that it's because I have srcset tag in in my image. My question is how to prevent browser to load responsive image or how to do lazy-loading for responsive images properly.
This is the sample of my img tag
<img data-lazy="better_me.jpg" srcset="better_me.jpg 400w, better_me.jpg 200w" class="avatar photo avatar-200" alt="better_me" width="200" height="200" sizes="(min-device-resolution: 1.6) 400px, 200px">
lazySizes is just working fine. You need to alter your markup into something like this however.
<img data-src="better_me.jpg" data-srcset="better_me2.jpg 400w, better_me.jpg 200w" class="avatar photo avatar-200 lazyload" data-sizes="auto" alt="better_me" width="200" height="200" />
Note srcset is changed to data-srcset and data-lazy is changed to data-src. Additionally you must add the class lazyload.
Your sizes attribute didn't made too much sense. Maybe you want to use x descriptors instead? Or simply use sizes="200px"? I don't know. I simply switched it to data-sizes="auto", so it gets automatically calculated for you. (But in that case the image dimension has to be computable before the image is loaded.)
lazySizes indeed loads images before they get in view. This is a big improvement for user experience. A user, who scrolls something into view doesn't want to wait then. A lazyloader that starts downloading an image after it is already in view disrupts the user experience.
One nice thing about lazySizes is that this lazy loader checks whether the browser is currently heavily downloading and decides on this fact, whether it only downloads in view images or to also preload near view images.
But if you don't want this you can control this by setting the lazySizes' expand and expFactor options.
I recommend responsivelyLazy. The implementation is SEO-friendly and does not mess your HTML code. Here is a snippet:
<div class="responsively-lazy" style="padding-bottom:68.44%;">
<img
alt=""
src="images/2500.jpg"
srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
data-srcset="images/400.jpg 400w, images/600.jpg 600w, images/800.jpg 800w, images/1000.jpg 1000w, images/1500.jpg 1500w, images/2000.jpg 2000w"
/>
As you can see the value in the src attribute is not modified.
Read more at http://ivopetkov.com/b/lazy-load-responsive-images/
Usually, to implement lazy loading in HTML, instead of src or srcset attributes, we use data-src or data-srcset so that browser does not load images during speculative parsing. Later on, when Javascript is executed, and the user has scrolled near the image element, we load the actual image and update the src or srcset attribute’s value.
Two very popular lazy loading libraries lazysizes and vanilla-lazyload support responsive images out of the box.
Here are a few examples of using lazysizes.
Lazy loading responsive images in srcset and sizes
<img
sizes="(min-width: 1000px) 930px, 90vw"
data-srcset="small.jpg 500w,
medium.jpg 640w,
big.jpg 1024w"
data-src="medium.jpg"
class="lazyload" />
Using low quality placeholder in lazy loading
<img
src="low-quaity-placeholder.jpg"
sizes="(min-width: 1000px) 930px, 90vw"
data-srcset="small.jpg 500w,
medium.jpg 640w,
big.jpg 1024w"
data-src="medium.jpg"
class="lazyload" />
Lazy loading images in picture element
<picture>
<source
data-srcset="500.jpg"
media="(max-width: 500px)" />
<source
data-srcset="1024.jpg"
media="(max-width: 1024px)" />
<source
data-srcset="1200.jpg" />
<img src="fallback-image.jpg"
data-src="1024.jpg"
class="lazyload"
alt="image with artdirection" />
</picture>
You can learn more about responsive images from this guide - https://imagekit.io/responsive-images

html5 video fallback/backup image out of place...?

I wrote up the following html which shows a video background and has 2 fallback images(for mobile and old browsers)
<div>
<video id="video_background" preload="auto" autoplay="true" loop="loop" muted="muted" volume="0">
<!--<source src="whatever......"> Disabled for testing -->
<img id="browserFallback src="videos/video.gif" title="Your browser does not support the <video> tag">
</video>
<img id="mobileFallback" style="display:none;" src="videos/video.gif" title="Your browser does not support the <video> tag">
</div>
the below javascript displays the second img gif on mobiles instead of the video:
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
document.getElementById("mobileFallback").style.display = "block";
}
however, because I have 2 img objects at the same time like above, the mobile fallback goes out of alignment and only half of it gets displayed for some reason. It works perfectly fine if i remove the "browserFallback" img.
Any ideas on why this happens?
Your code has a few simple errors in it which may solve your problem.
Namely:
You forgot to close the quotes on the id attribute of the first image tag
"Your browser does not support the <video> tag" should be changed to "Your browser does not support the $lt;video> tag". This will render in a browser as the < and > symbols respectively and may be confusing your HTML.
Try changing your code to this:
<div>
<video id="video_background" preload="auto" autoplay="true" loop="loop" muted="muted" volume="0">
<img id="browserFallback" src="videos/video.gif" title="Your browser does not support the $lt;video> tag">
</video>
<img id="mobileFallback" style="display:none;" src="videos/video.gif" title="Your browser does not support the $lt;video> tag">
</div>

picture element srcset responsive image rollovers

I am trying to figure out how to apply an image rollover effect to the picture element in a responsive website.
The question is can an image rollover be applied the scrset attribute in the picture tag?
Working example img tag with javascript rollover effect
<img src="media/images/feature-films/tmbs/zen-zero.jpg"
onmouseover="this.src='media/images/feature-films/tmbs/zen-zero-ro.jpg'"
onmouseout="this.src='media/images/feature-films/tmbs/zen-zero.jpg'"
alt=""/>
Not working example of picture element with javascript rollover effect
<picture>
<source srcset="media/images/feature-films/tmbs/zen-zero-large.jpg"
onmouseover="this.src='media/images/feature-films/tmbs/zen-zero-large-ro.jpg'"
onmouseout="this.src='media/images/feature-films/tmbs/zen-zero.jpg'"
media="(min-width: 880px)">
<source srcset="media/images/feature-films/tmbs/zen-zero-small.jpg" media="(max-width: 478px)">
<source srcset="media/images/feature-films/tmbs/zen-zero-medium.jpg">
<!-- fall back -->
<img srcset="media/images/feature-films/tmbs/zen-zero-medium.jpg" alt="">
</picture>
Does anyone have any suggestions hoe to achieve a rollover effect on the picture tag using the srcset?
The web page has about 12 responsive images that need a rollover effect.
Changing src/srcset is not optimal, even for a single img, since it can abort the download of the image if you hover it or leave it before the download is complete.
I think I would do one of these:
Clone the picture with cloneNode(true) and rewrite the URLs of the clone. When the original picture element receives a mouseover event, replace it with the clone. When the clone receives a mouseout event, replace it with the original.
Duplicate the picture in the markup, with the second one representing the hovered image. The second picture has a hidden attribute set. Toggle the hidden attribute on both elements as appropriate.
In the future, it should be possible to toggle the image with CSS using something like img:hover { content:image-set(...); }.
HTML:
<picture id="zen">
<source class="large" srcset="media/images/feature-films/tmbs/zen-zero-large.jpg" media="(min-width: 880px)">
<source srcset="media/images/feature-films/tmbs/zen-zero-small.jpg" media="(max-width: 478px)">
<source srcset="media/images/feature-films/tmbs/zen-zero-medium.jpg">
<!-- fall back -->
<img srcset="media/images/feature-films/tmbs/zen-zero-medium.jpg" alt="">
</picture>
jQuery:
$(document).ready(function() {
$('#zen').on('mouseover', function () {
$(this).find('.large').attr('srcset', 'media/images/feature-films/tmbs/zen-zero-large-ro.jpg');
console.log('mouse over');
})
$('#zen').on('mouseout', function () {
$(this).find('.large').attr('srcset', 'media/images/feature-films/tmbs/zen-zero-large.jpg');
console.log('mouse out');
})
});

Categories