Skip to content

Commit

Permalink
Add fit and aspect props (#16)
Browse files Browse the repository at this point in the history
* Add `fit` and `aspect` props
  • Loading branch information
deebov authored Jun 4, 2024
1 parent f81fb4b commit 85f4a6b
Showing 1 changed file with 49 additions and 23 deletions.
72 changes: 49 additions & 23 deletions src/components/image.client.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
"use client";

import React, { useCallback, useRef, forwardRef } from "react";
import React, {
useCallback,
useRef,
forwardRef,
type CSSProperties,
} from "react";
import type { StoredObject } from "ronin/types";

const supportedFitValues = ["fill", "contain", "cover"];

export interface ImageProps {
/**
* Defines text that can replace the image in the page.
Expand Down Expand Up @@ -35,6 +42,14 @@ export interface ImageProps {
* a unit.
*/
height?: number;
/**
* Specifies how the image should be resized to fit its container.
*/
fit?: CSSProperties["objectFit"];
/**
* The aspect ratio of the image. Can be "square", "video", or a custom string.
*/
aspect?: "square" | "video" | string;
/**
* Indicates how the browser should load the image.
*
Expand All @@ -54,7 +69,7 @@ export interface ImageProps {
/**
* The inline style for the image container (not the image itself).
*/
style?: React.CSSProperties;
style?: CSSProperties;
}

const Image = forwardRef<HTMLDivElement, ImageProps>(
Expand All @@ -65,6 +80,8 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
size: defaultSize,
width: defaultWidth,
height: defaultHeight,
fit = "cover",
aspect,
quality,
loading,
style,
Expand All @@ -79,6 +96,25 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
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`.",
Expand All @@ -93,12 +129,14 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
const optimizationParams = new URLSearchParams({
...(width ? { w: width.toString() } : {}),
...(height ? { h: height.toString() } : {}),
fit: supportedFitValues.includes(fit) ? fit : "cover",
q: quality ? quality.toString() : "100",
});

const responsiveOptimizationParams = new URLSearchParams({
...(width ? { h: (width * 2).toString() } : {}),
...(height ? { h: (height * 2).toString() } : {}),
fit: supportedFitValues.includes(fit) ? fit : "cover",
q: quality ? quality.toString() : "100",
});

Expand All @@ -112,25 +150,6 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
const placeholder =
input && typeof input !== "string" ? input.placeholder?.base64 : null;

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,
},
);
}
}, []);

return (
<div
ref={ref}
Expand All @@ -141,13 +160,20 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
flexShrink: 0,
width: width || "100%",
height: height || "100%",
aspectRatio:
aspect === "video" ? "16/9" : aspect === "square" ? "1/1" : "auto",
...style,
}}
>
{/* Blurred preview being displayed until the actual image is loaded. */}
{placeholder && (
<img
style={{ position: "absolute", width: "100%", height: "100%" }}
style={{
position: "absolute",
width: "100%",
height: "100%",
objectFit: fit,
}}
src={placeholder}
alt={alt}
/>
Expand All @@ -160,7 +186,7 @@ const Image = forwardRef<HTMLDivElement, ImageProps>(
position: "absolute",
width: "100%",
height: "100%",
objectFit: "cover",
objectFit: fit,
}}
decoding="async"
onLoad={onLoad}
Expand Down

0 comments on commit 85f4a6b

Please sign in to comment.