Files
Arian Tron 61f56f997c
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
first commit
2026-03-10 19:37:31 +03:30

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;