Skip to content

Commit 3f5ce73

Browse files
committed
feat: add getImageProps function for responsive image handling
1 parent 48c5691 commit 3f5ce73

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

src/getImageProps.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { buildSrc } from './url'
2+
import type { SrcOptions } from './interfaces'
3+
4+
const DEVICE_SIZES = [640, 750, 828, 1080, 1200, 1920, 2048, 3840] as const
5+
const IMAGE_SIZES = [16, 32, 48, 64, 96, 128, 256, 320, 420] as const
6+
7+
export interface ResponsiveOptions extends SrcOptions {
8+
width?: number
9+
sizes?: string
10+
deviceSizes?: number[]
11+
imageSizes?: number[]
12+
}
13+
14+
export interface ImageProps {
15+
src: string
16+
srcSet?: string
17+
sizes?: string
18+
width?: number
19+
height?: number
20+
}
21+
22+
export function getImageProps(opts: ResponsiveOptions): ImageProps {
23+
const {
24+
src,
25+
urlEndpoint,
26+
transformation = [],
27+
queryParameters,
28+
transformationPosition,
29+
sizes,
30+
width,
31+
deviceSizes = DEVICE_SIZES as unknown as number[],
32+
imageSizes = IMAGE_SIZES as unknown as number[],
33+
} = opts
34+
35+
const allBreakpoints = [...imageSizes, ...deviceSizes].sort((a, b) => a - b)
36+
37+
const { widths, kind } = pickWidths({
38+
all: allBreakpoints,
39+
device: deviceSizes,
40+
explicit: width,
41+
sizesAttr: sizes,
42+
})
43+
44+
const build = (w: number) =>
45+
buildSrc({
46+
src,
47+
urlEndpoint,
48+
queryParameters,
49+
transformationPosition,
50+
transformation: [
51+
...transformation,
52+
{ width: w, crop: "at_max" } // Should never upscale beyond the original width
53+
],
54+
})
55+
56+
const srcSet =
57+
widths
58+
.map((w, i) => `${build(w)} ${kind === 'w' ? w : i + 1}${kind}`)
59+
.join(', ') || undefined
60+
61+
return {
62+
sizes: sizes ?? (kind === 'w' ? '100vw' : undefined),
63+
srcSet,
64+
src: build(widths[widths.length - 1]),
65+
width,
66+
}
67+
}
68+
69+
function pickWidths({
70+
all,
71+
device,
72+
explicit,
73+
sizesAttr,
74+
}: {
75+
all: number[]
76+
device: number[]
77+
explicit?: number
78+
sizesAttr?: string
79+
}): { widths: number[]; kind: 'w' | 'x' } {
80+
if (sizesAttr) {
81+
const vwMatches = sizesAttr.match(/(^|\s)(1?\d{1,2})vw/g) || []
82+
const percents = vwMatches.map((m) => parseInt(m, 10))
83+
84+
if (percents.length) {
85+
const smallest = Math.min(...percents) / 100
86+
const cutOff = device[0] * smallest
87+
return { widths: all.filter((w) => w >= cutOff), kind: 'w' }
88+
}
89+
90+
return { widths: all, kind: 'w' } // ← return allSizes when no vw tokens
91+
}
92+
93+
// If sizes is not defined, we need to check if the explicit width is defined. If no width is defined, we can use the deviceSizes as the default.
94+
if (typeof explicit !== 'number') {
95+
return { widths: device, kind: 'w' }
96+
}
97+
98+
const nearest = (t: number) =>
99+
all.find((v) => v >= t) || all[all.length - 1]
100+
const list = Array.from(new Set([nearest(explicit), nearest(explicit * 2)]))
101+
return { widths: list, kind: 'x' }
102+
}

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { SrcOptions, Transformation, UploadOptions, UploadResponse } from "./interfaces";
22
import { ImageKitAbortError, ImageKitInvalidRequestError, ImageKitServerError, ImageKitUploadNetworkError, upload } from "./upload";
33
import { buildSrc, buildTransformationString } from "./url";
4+
import { getImageProps } from "./getImageProps";
45

5-
export { buildSrc, buildTransformationString, upload, ImageKitInvalidRequestError, ImageKitAbortError, ImageKitServerError, ImageKitUploadNetworkError };
6+
export { buildSrc, buildTransformationString, upload, getImageProps, ImageKitInvalidRequestError, ImageKitAbortError, ImageKitServerError, ImageKitUploadNetworkError };
67
export type {
78
Transformation,
89
SrcOptions,

0 commit comments

Comments
 (0)