Skip to content

Commit

Permalink
feat: add the ability to forward a ref to the image div wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
dbismut committed Jan 2, 2022
1 parent ed84a35 commit 795dcd1
Showing 1 changed file with 155 additions and 142 deletions.
297 changes: 155 additions & 142 deletions src/Image/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, forwardRef, useCallback } from "react";
import "intersection-observer";
import { useInView } from "react-intersection-observer";

Expand Down Expand Up @@ -101,144 +101,157 @@ const imageShowStrategy = ({ lazyLoad, loaded }: State) => {
return true;
};

export const Image: React.FC<ImagePropTypes> = function ({
className,
fadeInDuration = 500,
intersectionTreshold,
intersectionThreshold,
intersectionMargin,
pictureClassName,
lazyLoad = true,
style,
pictureStyle,
explicitWidth,
data,
onLoad,
}) {
const [loaded, setLoaded] = useState(false);

const handleLoad = () => {
onLoad?.();
setLoaded(true);
};

const { ref, inView } = useInView({
threshold: intersectionThreshold || intersectionTreshold || 0,
rootMargin: intersectionMargin || "0px 0px 0px 0px",
triggerOnce: true,
});

const absolutePositioning: React.CSSProperties = {
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
};

const addImage = imageAddStrategy({
lazyLoad,
inView,
loaded,
});
const showImage = imageShowStrategy({
lazyLoad,
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 transition =
fadeInDuration > 0 ? `opacity ${fadeInDuration}ms` : undefined;

const placeholder = (
<div
style={{
backgroundImage: data.base64 ? `url(${data.base64})` : undefined,
backgroundColor: data.bgColor,
backgroundSize: "cover",
opacity: showImage ? 0 : 1,
transition,
...absolutePositioning,
}}
/>
);

const { width, aspectRatio } = data;
const height = data.height || width / aspectRatio;

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`;

const sizer = (
<img
className={pictureClassName}
style={{
display: "block",
width: explicitWidth ? `${width}px` : "100%",
...pictureStyle,
}}
src={`data:image/svg+xml;base64,${universalBtoa(svg)}`}
role="presentation"
/>
);

return (
<div
ref={ref}
className={className}
style={{
display: explicitWidth ? "inline-block" : "block",
overflow: "hidden",
position: "relative",
...style,
}}
>
{sizer}
{placeholder}
{addImage && (
<picture>
{webpSource}
{regularSource}
{data.src && (
<img
src={data.src}
alt={data.alt}
title={data.title}
onLoad={handleLoad}
className={pictureClassName}
style={{
...absolutePositioning,
...pictureStyle,
opacity: showImage ? 1 : 0,
transition,
}}
/>
)}
</picture>
)}
<noscript>
<picture>
{webpSource}
{regularSource}
{data.src && (
<img
src={data.src}
alt={data.alt ?? ''}
title={data.title}
className={pictureClassName}
style={{ ...absolutePositioning, ...pictureStyle }}
loading="lazy"
/>
)}
</picture>
</noscript>
</div>
);
};
export const Image = forwardRef<HTMLDivElement, ImagePropTypes>(
(
{
className,
fadeInDuration = 500,
intersectionTreshold,
intersectionThreshold,
intersectionMargin,
pictureClassName,
lazyLoad = true,
style,
pictureStyle,
explicitWidth,
data,
onLoad,
},
ref
) => {
const [loaded, setLoaded] = useState(false);

const handleLoad = () => {
onLoad?.();
setLoaded(true);
};

const [viewRef, inView] = useInView({
threshold: intersectionThreshold || intersectionTreshold || 0,
rootMargin: intersectionMargin || "0px 0px 0px 0px",
triggerOnce: true,
});

const callbackRef = useCallback(
(_ref: HTMLDivElement) => {
viewRef(_ref);
if (ref) (ref as React.MutableRefObject<HTMLDivElement>).current = _ref;
},
[viewRef]
);

const absolutePositioning: React.CSSProperties = {
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
};

const addImage = imageAddStrategy({
lazyLoad,
inView,
loaded,
});
const showImage = imageShowStrategy({
lazyLoad,
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 transition =
fadeInDuration > 0 ? `opacity ${fadeInDuration}ms` : undefined;

const placeholder = (
<div
style={{
backgroundImage: data.base64 ? `url(${data.base64})` : undefined,
backgroundColor: data.bgColor,
backgroundSize: "cover",
opacity: showImage ? 0 : 1,
transition,
...absolutePositioning,
}}
/>
);

const { width, aspectRatio } = data;
const height = data.height || width / aspectRatio;

const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}"></svg>`;

const sizer = (
<img
className={pictureClassName}
style={{
display: "block",
width: explicitWidth ? `${width}px` : "100%",
...pictureStyle,
}}
src={`data:image/svg+xml;base64,${universalBtoa(svg)}`}
role="presentation"
/>
);

return (
<div
ref={callbackRef}
className={className}
style={{
display: explicitWidth ? "inline-block" : "block",
overflow: "hidden",
position: "relative",
...style,
}}
>
{sizer}
{placeholder}
{addImage && (
<picture>
{webpSource}
{regularSource}
{data.src && (
<img
src={data.src}
alt={data.alt}
title={data.title}
onLoad={handleLoad}
className={pictureClassName}
style={{
...absolutePositioning,
...pictureStyle,
opacity: showImage ? 1 : 0,
transition,
}}
/>
)}
</picture>
)}
<noscript>
<picture>
{webpSource}
{regularSource}
{data.src && (
<img
src={data.src}
alt={data.alt ?? ""}
title={data.title}
className={pictureClassName}
style={{ ...absolutePositioning, ...pictureStyle }}
loading="lazy"
/>
)}
</picture>
</noscript>
</div>
);
}
);

0 comments on commit 795dcd1

Please sign in to comment.