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);