Dynamic size renditions
by Marco Abate
HTML
<div class="spacer"></div>
<div class="picture-container">
<ul class="picture-info">
<li class="picture-info-width">Image Width: <span></span></li>
<li class="picture-info-renditions">Renditions: <span></span></li>
<li class="picture-info-current-rendition">Current Rendition: <span></span></li>
</ul>
<img loading="lazy" src="https://jlr.scene7.com/is/image/jlr/L461_23MY_140_GLHD_CARPATHIAN_GREY_V3_CROP?wid=600" data-src="https://jlr.scene7.com/is/image/jlr/L461_23MY_140_GLHD_CARPATHIAN_GREY_V3_CROP?wid={width}" data-max-src="https://jlr.scene7.com/is/image/jlr/L461_23MY_140_GLHD_CARPATHIAN_GREY_V3_CROP?wid=2000" data-renditions="600,700,800,900,1000" />
</div>
SCSS
* {
font-family: sans-serif;
}
.spacer {
width: 100%;
height: 3000px;
}
.picture {
&-container {
img {
width: 100%;
}
}
&-info {
background-color: rgba(#000, 0.3);
position: fixed;
padding: 15px;
bottom: 8px;
right: 8px;
border-radius: 10px;
box-shadow: 2px 3px 4px #999;
span {
font-weight: bold;
}
}
}
JavaScript
const container = document.querySelector('.picture-container')
const img = container.querySelector('img')
const infoContainer = container.querySelector('.picture-info');
const widthText = infoContainer.querySelector('.picture-info-width span')
const renditionsText = infoContainer.querySelector('.picture-info-renditions span')
const currentRenditionText = infoContainer.querySelector('.picture-info-current-rendition span')
const baseSrc = img.dataset.src
const maxSrc = img.dataset.maxSrc
const renditions = img.dataset.renditions.split(',').map((breakpoint) => Number(breakpoint)).sort((a, b) => a - b)
let currentUrl = img.src
renditionsText.textContent = img.dataset.renditions
const findFirstHigherIndex = (num) => {
const firstHigherIndex = renditions.findIndex((rendition) => rendition >= num)
return firstHigherIndex
}
const buildSrc = (target) => {
const bounding = target.getBoundingClientRect()
widthText.textContent = bounding.width;
const firstHigherIndex = findFirstHigherIndex(bounding.width)
const hasNoMatchHigh = firstHigherIndex === -1
let newUrl = ''
// TODO: Setup debounce
if(hasNoMatchHigh) {
newUrl = maxSrc
currentRenditionText.textContent = "Max Size"
} else {
const currentRendition = renditions[firstHigherIndex]
newUrl = baseSrc.replaceAll('{width}', currentRendition)
currentRenditionText.textContent = currentRendition
}
return newUrl
}
const resizeObserver = new ResizeObserver((entries) => {
const newUrl = buildSrc(entries[0].target)
if(newUrl !== currentUrl) {
currentUrl = newUrl
entries[0].target.src = currentUrl
}
});
resizeObserver.observe(img);