oont-contents/plugins/jetpack-boost/app/modules/optimizations/image-cdn/src/srcset.ts
2025-02-08 15:10:23 +01:00

113 lines
2.8 KiB
TypeScript

type Dimensions = {
width: number;
height: number;
};
type ImageMeta = {
url: URL;
width: number;
height: number;
};
function getDpr() {
return window.devicePixelRatio || 1;
}
export function parseImageSize( resizeParam: string ): Dimensions | null {
const [ width, height ] = resizeParam.split( ',' ).map( Number );
if ( isNaN( width ) || isNaN( height ) ) {
return null;
}
return { width, height };
}
export function getImageSizeFromUrl( url: string ): Dimensions | null {
const resizeParam = new URL( url ).searchParams.get( 'resize' );
if ( ! resizeParam ) {
return null;
}
return parseImageSize( resizeParam );
}
export function calculateTargetSize( dimensions: Dimensions ): Dimensions {
const dpr = getDpr();
const ratio = dimensions.width / dimensions.height;
const targetWidth = Math.ceil( ( dimensions.width * dpr ) / 10 ) * 10;
const targetHeight = Math.ceil( targetWidth / ratio );
return {
width: targetWidth,
height: targetHeight,
};
}
export function isSizeReusable( desiredWidth: number, existingWidth: number ) {
if ( existingWidth <= 0 ) {
return false;
}
const diff = existingWidth - desiredWidth;
if ( diff < 0 ) {
return false;
}
if ( diff < 50 ) {
return true;
}
const ratio = desiredWidth / existingWidth;
return ratio > 0.9 && ratio <= 1;
}
export function findClosestImageSize( urls: string[], targetWidth: number ): ImageMeta | undefined {
for ( const src of urls ) {
const [ url, widthStr ] = src.trim().split( ' ' );
if ( ! widthStr?.trim().endsWith( 'w' ) ) {
continue;
}
const imageSize = getImageSizeFromUrl( url );
if ( ! imageSize ) {
continue;
}
if ( isSizeReusable( targetWidth, imageSize.width ) ) {
return { url: new URL( url ), ...imageSize };
}
}
return undefined;
}
function resizeImage( imageUrl: string, targetSize: Dimensions ): URL {
const newUrl = new URL( imageUrl );
newUrl.searchParams.set( 'resize', `${ targetSize.width },${ targetSize.height }` );
return newUrl;
}
export function dynamicSrcset( img: HTMLImageElement ) {
if (
! img.getAttribute( 'width' ) ||
! img.getAttribute( 'height' ) ||
! img.srcset ||
! img.src ||
! img.src.includes( '.wp.com' )
) {
return;
}
const rect = img.getBoundingClientRect();
const targetSize = calculateTargetSize( rect );
const srcset = img.srcset.split( ',' );
const closestImage = findClosestImageSize( [ `${ img.src } 0w`, ...srcset ], targetSize.width );
if ( closestImage ) {
closestImage.url.searchParams.set( '_jb', 'closest' );
srcset.push( `${ closestImage.url } ${ window.innerWidth * getDpr() }w` );
img.srcset = srcset.join( ',' );
img.sizes = 'auto';
} else {
const newUrl = resizeImage( img.src, targetSize );
newUrl.searchParams.set( '_jb', 'custom' );
srcset.push( `${ newUrl } ${ window.innerWidth * getDpr() }w` );
img.srcset = srcset.join( ',' );
img.sizes = 'auto';
}
}