Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
225 lines
4.5 KiB
TypeScript
225 lines
4.5 KiB
TypeScript
import React, { useCallback, useState } from "react";
|
|
import { useInView } from "react-intersection-observer";
|
|
|
|
type State = {
|
|
lazyLoad: boolean;
|
|
isSsr: boolean;
|
|
isIntersectionObserverAvailable: boolean;
|
|
inView?: boolean;
|
|
loaded: boolean;
|
|
};
|
|
|
|
const imageAddStrategy = ({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
}: State) => {
|
|
if (!lazyLoad) {
|
|
return true;
|
|
}
|
|
|
|
if (isSsr) {
|
|
return false;
|
|
}
|
|
|
|
if (isIntersectionObserverAvailable) {
|
|
return inView || loaded;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
const imageShowStrategy = ({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
loaded,
|
|
}: State) => {
|
|
if (!lazyLoad) {
|
|
return true;
|
|
}
|
|
|
|
if (isSsr) {
|
|
return false;
|
|
}
|
|
|
|
if (isIntersectionObserverAvailable) {
|
|
return loaded;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
type ImageData = {
|
|
aspectRatio: number;
|
|
base64?: string;
|
|
height?: number;
|
|
width: number;
|
|
sizes?: string;
|
|
src?: string;
|
|
srcSet?: string;
|
|
webpSrcSet?: string;
|
|
bgColor?: string;
|
|
alt?: string;
|
|
title?: string;
|
|
};
|
|
|
|
type ImageProps = {
|
|
data: ImageData;
|
|
className?: string;
|
|
pictureClassName?: string;
|
|
fadeInDuration?: number;
|
|
intersectionThreshold?: number;
|
|
intersectionMargin?: string;
|
|
lazyLoad?: boolean;
|
|
style?: React.CSSProperties;
|
|
pictureStyle?: React.CSSProperties;
|
|
explicitWidth?: boolean;
|
|
};
|
|
|
|
const Image = function ({
|
|
className,
|
|
fadeInDuration,
|
|
intersectionThreshold,
|
|
intersectionMargin,
|
|
pictureClassName,
|
|
lazyLoad = true,
|
|
style,
|
|
pictureStyle,
|
|
explicitWidth,
|
|
data,
|
|
}: ImageProps) {
|
|
const [loaded, setLoaded] = useState(false);
|
|
|
|
const handleLoad = useCallback(() => {
|
|
setLoaded(true);
|
|
}, []);
|
|
|
|
const [ref, inView] = useInView({
|
|
threshold: intersectionThreshold || 0,
|
|
rootMargin: intersectionMargin || "0px 0px 0px 0px",
|
|
triggerOnce: true,
|
|
});
|
|
|
|
const isSsr = typeof window === "undefined";
|
|
|
|
const isIntersectionObserverAvailable = isSsr
|
|
? false
|
|
: !!window.IntersectionObserver;
|
|
|
|
const absolutePositioning: React.CSSProperties = {
|
|
position: "absolute",
|
|
left: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
};
|
|
|
|
const addImage = imageAddStrategy({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
});
|
|
const showImage = imageShowStrategy({
|
|
lazyLoad,
|
|
isSsr,
|
|
isIntersectionObserverAvailable,
|
|
inView,
|
|
loaded,
|
|
});
|
|
|
|
const webpSource = data.webpSrcSet && (
|
|
<source srcSet={data.webpSrcSet} sizes={data.sizes} type="image/webp" />
|
|
);
|
|
|
|
const regularSource = data.srcSet && (
|
|
<source srcSet={data.srcSet} sizes={data.sizes} />
|
|
);
|
|
|
|
const placeholder = (
|
|
<div
|
|
style={{
|
|
backgroundImage: data.base64 ? `url(${data.base64})` : null,
|
|
backgroundColor: data.bgColor,
|
|
backgroundSize: "cover",
|
|
opacity: showImage ? 0 : 1,
|
|
transition:
|
|
!fadeInDuration || fadeInDuration > 0
|
|
? `opacity ${fadeInDuration || 500}ms ${fadeInDuration || 500}ms`
|
|
: null,
|
|
...absolutePositioning,
|
|
}}
|
|
/>
|
|
);
|
|
|
|
const { width, aspectRatio } = data;
|
|
const height = data.height || width / aspectRatio;
|
|
|
|
const sizer = (
|
|
<svg
|
|
className={pictureClassName}
|
|
style={{
|
|
width: explicitWidth ? `${width}px` : "100%",
|
|
height: "auto",
|
|
display: "block",
|
|
...pictureStyle,
|
|
}}
|
|
height={height}
|
|
width={width}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={className}
|
|
style={{
|
|
display: "inline-block",
|
|
overflow: "hidden",
|
|
...style,
|
|
position: "relative",
|
|
}}
|
|
>
|
|
{sizer}
|
|
{placeholder}
|
|
{addImage && (
|
|
<picture
|
|
style={{
|
|
...absolutePositioning,
|
|
opacity: showImage ? 1 : 0,
|
|
transition:
|
|
!fadeInDuration || fadeInDuration > 0
|
|
? `opacity ${fadeInDuration || 500}ms`
|
|
: null,
|
|
}}
|
|
>
|
|
{webpSource}
|
|
{regularSource}
|
|
{data.src && (
|
|
<img
|
|
src={data.src}
|
|
alt={data.alt}
|
|
title={data.title}
|
|
onLoad={handleLoad}
|
|
style={{ width: "100%" }}
|
|
/>
|
|
)}
|
|
</picture>
|
|
)}
|
|
<noscript>
|
|
<picture className={pictureClassName} style={{ ...pictureStyle }}>
|
|
{webpSource}
|
|
{regularSource}
|
|
{data.src && <img src={data.src} alt={data.alt} title={data.title} />}
|
|
</picture>
|
|
</noscript>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Image;
|