react-ronin
Version:
Access the RONIN data platform via React.
74 lines (73 loc) • 3.54 kB
JavaScript
"use client";
// We are purposefully importing `React` here, as the build output contains
// references to it, and those would fail if we don't import it explicitly.
import React, { useCallback, useRef, forwardRef } from "react";
const supportedFitValues = ["fill", "contain", "cover"];
const Image = forwardRef(({ src: input, alt, size: defaultSize, width: defaultWidth, height: defaultHeight, fit = "cover", format = "webp", quality = 80, aspect, loading, style, className, }, ref) => {
const imageElement = useRef(null);
const renderTime = useRef(Date.now());
const isMediaObject = typeof input === "object" && input !== null;
const width = defaultSize || defaultWidth;
const height = defaultSize || defaultHeight;
const onLoad = useCallback(() => {
const duration = Date.now() - renderTime.current;
const threshold = 150;
// Fade in and gradually reduce blur of the real image if loading takes
// longer than the specified threshold.
if (duration > threshold) {
imageElement.current?.animate([
{ filter: "blur(4px)", opacity: 0 },
{ filter: "blur(0px)", opacity: 1 },
], {
duration: 200,
});
}
}, []);
if (!height && !width)
throw new Error("Either `width`, `height`, or `size` must be defined for `Image`.");
// Validate given `quality` property.
if (quality && (quality < 0 || quality > 100))
throw new Error("The given `quality` was not in the range between 0 and 100.");
const optimizationParams = new URLSearchParams({
...(width ? { w: width.toString() } : {}),
...(height ? { h: height.toString() } : {}),
...(format !== "original" ? { fm: format } : {}),
fit: supportedFitValues.includes(fit) ? fit : "cover",
q: quality.toString(),
});
const responsiveOptimizationParams = new URLSearchParams({
...(width ? { h: (width * 2).toString() } : {}),
...(height ? { h: (height * 2).toString() } : {}),
...(format !== "original" ? { fm: format } : {}),
fit: supportedFitValues.includes(fit) ? fit : "cover",
q: quality.toString(),
});
const source = isMediaObject ? `${input.src}?${optimizationParams}` : input;
const responsiveSource = isMediaObject
? `${input.src}?${optimizationParams} 1x, ` +
`${input.src}?${responsiveOptimizationParams} 2x`
: input;
const placeholder = input && typeof input !== "string" ? input.placeholder?.base64 : null;
return (React.createElement("div", { ref: ref, className: className, style: {
position: "relative",
overflow: "hidden",
flexShrink: 0,
width: width || "100%",
height: height || "100%",
aspectRatio: aspect === "video" ? "16/9" : aspect === "square" ? "1/1" : "auto",
...style,
} },
placeholder && (React.createElement("img", { style: {
position: "absolute",
width: "100%",
height: "100%",
objectFit: fit,
}, src: placeholder, alt: alt })),
React.createElement("img", { alt: alt, style: {
position: "absolute",
width: "100%",
height: "100%",
objectFit: fit,
}, decoding: "async", onLoad: onLoad, loading: loading, ref: imageElement, src: source, srcSet: responsiveSource })));
});
export default Image;