68432eef26
Objective: - Get appropriately sized images to improve performance and Core Vitals score. - Ensure images using "objectFit=cover" doesn't get stretched out if the source is large enough. - Peg to 100px increments for better caching. Notes: - Skip images hosted in '/public'. If we really want to optimize it, then we'll need to provide the full path in the code, otherwise CDN lookup will fail.
108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
// @flow
|
|
import React from 'react';
|
|
import { getThumbnailCdnUrl } from 'util/thumbnail';
|
|
|
|
function scaleToDevicePixelRatio(value: number, window: any) {
|
|
const devicePixelRatio = window.devicePixelRatio || 1.0;
|
|
return Math.ceil(value * devicePixelRatio);
|
|
}
|
|
|
|
type Props = {
|
|
src: string,
|
|
objectFit?: string,
|
|
};
|
|
|
|
function OptimizedImage(props: Props) {
|
|
const { objectFit, src, ...imgProps } = props;
|
|
const [optimizedSrc, setOptimizedSrc] = React.useState('');
|
|
const ref = React.useRef<any>();
|
|
|
|
function getOptimizedImgUrl(url, width, height) {
|
|
let optimizedUrl = url;
|
|
if (url && !url.startsWith('/public/')) {
|
|
optimizedUrl = url.trim().replace(/^http:\/\//i, 'https://');
|
|
|
|
// @if TARGET='web'
|
|
if (!optimizedUrl.endsWith('.gif')) {
|
|
optimizedUrl = getThumbnailCdnUrl({ thumbnail: optimizedUrl, width, height, quality: 85 });
|
|
}
|
|
// @endif
|
|
}
|
|
return optimizedUrl;
|
|
}
|
|
|
|
function getOptimumSize(elem) {
|
|
if (!elem || !elem.parentElement || !elem.parentElement.clientWidth || !elem.parentElement.clientHeight) {
|
|
return null;
|
|
}
|
|
|
|
let width = elem.parentElement.clientWidth;
|
|
let height = elem.parentElement.clientHeight;
|
|
|
|
width = scaleToDevicePixelRatio(width, window);
|
|
height = scaleToDevicePixelRatio(height, window);
|
|
|
|
// Round to next 100px for better caching
|
|
width = Math.ceil(width / 100) * 100;
|
|
height = Math.ceil(height / 100) * 100;
|
|
|
|
// Reminder: CDN expects integers.
|
|
return { width, height };
|
|
}
|
|
|
|
function adjustOptimizationIfNeeded(elem, objectFit, origSrc) {
|
|
if (objectFit === 'cover' && elem) {
|
|
const containerSize = getOptimumSize(elem);
|
|
if (containerSize) {
|
|
// $FlowFixMe
|
|
if (elem.naturalWidth < containerSize.width) {
|
|
// For 'cover', we don't want to stretch the image. We started off by
|
|
// filling up the container height, but the width still has a gap for
|
|
// this instance (usually due to aspect ratio mismatch).
|
|
// If the original image is much larger, we can request for a larger
|
|
// image so that "objectFit=cover" will center it without stretching and
|
|
// making it blur. The double fetch might seem wasteful, but on
|
|
// average the total transferred bytes is still less than the original.
|
|
const probablyMaxedOut = elem.naturalHeight < containerSize.height;
|
|
if (!probablyMaxedOut) {
|
|
const newOptimizedSrc = getOptimizedImgUrl(origSrc, containerSize.width, 0);
|
|
if (newOptimizedSrc && newOptimizedSrc !== optimizedSrc) {
|
|
setOptimizedSrc(newOptimizedSrc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
const containerSize = getOptimumSize(ref.current);
|
|
if (containerSize) {
|
|
const width = 0; // The CDN will fill the zeroed attribute per image's aspect ratio.
|
|
const height = containerSize.height;
|
|
|
|
const newOptimizedSrc = getOptimizedImgUrl(src, width, height);
|
|
|
|
if (newOptimizedSrc !== optimizedSrc) {
|
|
setOptimizedSrc(newOptimizedSrc);
|
|
}
|
|
} else {
|
|
setOptimizedSrc(src);
|
|
}
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
if (!src) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<img
|
|
ref={ref}
|
|
{...imgProps}
|
|
src={optimizedSrc}
|
|
onLoad={() => adjustOptimizationIfNeeded(ref.current, objectFit, src)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default OptimizedImage;
|