Reputation: 15269
The srcset
attribute can be set from JavaScript to implement responsive lazy loaded images.
However, setting the sizes
attribute doesn't seem to work. Coding sizes
in HTML works as expected, but when I try to set the sizes
attribute from JavaScript (as in the code below) it has no effect: the image chosen matches the viewport width and doesn't follow the sizes
hints.
Is this by design, or has this just not been implemented?
Sample code below.
const BASE_URL = 'https://res.cloudinary.com/foobar/f_auto,dpr_auto,q_auto:eco/';
const SIZES = {
0: 'calc(100vw - 60px)',
420: 'calc((100vw - 90px) / 2)',
750: 'calc((100vw - 120px) / 3)',
1200: 'calc((100vw - 150px) / 4)'
}
const WIDTHS = [500, 1000, 1500];
const options = {
rootMargin: '0px 0px 100px 0px',
};
function callback(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
const id = lazyImage.dataset.id;
lazyImage.sizes = getSizes();
lazyImage.srcset = getSrcset(id);
io.unobserve(lazyImage);
}
}
}
function getSrcset(id) {
const srcset = [];
for (const width of WIDTHS) {
srcset.push(`${BASE_URL}w_${width}/${id}.jpg ${width}w`);
}
return srcset.join(',');
}
function getSizes() {
const sizes = [];
for (const size in SIZES) {
sizes.push(`(min-width: ${size}px) ${SIZES[size]}`);
}
return sizes.join(',');
}
const images = document.querySelectorAll('img.lazy');
let io;
if (window.IntersectionObserver) {
io = new IntersectionObserver(callback, options);
}
for (const image of images) {
if (window.IntersectionObserver) {
io.observe(image);
} else {
console.log('Intersection Observer not supported');
image.src = BASE_URL + image.getAttribute('data-id' + '.jpg');
}
}
Upvotes: 1
Views: 1792
Reputation: 15269
As per @amed0zmey's comment, I fixed this by getting the sizes
code right.
Working example below.
const BASE_URL = 'https://res.cloudinary.com/foo/f_auto,dpr_auto,q_auto:eco/';
const BREAKPOINTS = [0, 420, 750, 1200];
const DISPLAY_WIDTHS = ['calc(100vw - 60px)', 'calc((100vw - 90px) / 2)',
'calc((100vw - 120px) / 3)', 'calc((100vw - 150px) / 4)'];
const IMAGE_WIDTHS = [500, 1000, 1500];
const options = {
rootMargin: '0px 0px 100px 0px',
};
function callback(entries) {
for (const entry of entries) {
if (entry.isIntersecting) {
const image = entry.target;
const id = image.dataset.id;
image.srcset = getSrcset(id);
io.unobserve(image);
}
}
}
function getSrcset(id) {
const srcset = [];
for (const width of IMAGE_WIDTHS) {
srcset.push(`${BASE_URL}w_${width}/${id}.jpg ${width}w`);
}
return srcset.join(',');
}
function getSizes() {
const sizes = [];
for (let i = 0; i !== BREAKPOINTS.length; ++i) {
let size = `(min-width: ${BREAKPOINTS[i]}px) `;
const nextBreakpoint = BREAKPOINTS[i + 1];
if (nextBreakpoint) {
size += `and (max-width: ${nextBreakpoint}px) `;
}
size += DISPLAY_WIDTHS[i];
sizes.push(size);
}
return sizes.join(',');
}
const images = document.querySelectorAll('img.lazy');
let io;
if (window.IntersectionObserver) {
io = new IntersectionObserver(callback, options);
}
for (const image of images) {
image.sizes = getSizes();
if (window.IntersectionObserver) {
io.observe(image);
} else {
console.log('Intersection Observer not supported');
const id = image.getAttribute('data-id');
image.srcset = getSrcset(id);
image.src = BASE_URL + 'id' + '.jpg';
}
}
Upvotes: 0
Reputation: 573
I ran your sample and it works fine and as expected for me.
Something is wrong with how sizes are coded, probably.
I think it's not a good idea to put min-width: 0px media condition since it will always be selected and all other conditions will be ignored.
From here https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
The browser ignores everything after the first matching condition, so be careful how you order the media conditions.
Upvotes: 1