Skip to content

Commit e49a31a

Browse files
authored
Merge pull request #101 from imagekit-developer/responsive-images
Responsive images
2 parents 48c5691 + ec15969 commit e49a31a

7 files changed

+355
-7
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## Version 5.1.0
4+
5+
1. **New helper** `getResponsiveImageAttributes()`
6+
Generates ready‑to‑use `src`, `srcSet`, and `sizes` for responsive `<img>` tags (breakpoint pruning, DPR 1×/2×, custom breakpoints, no up‑scaling).
7+
2. Added exports:
8+
`getResponsiveImageAttributes`, `GetImageAttributesOptions`, `ResponsiveImageAttributes`.
9+
10+
_No breaking changes from 5.0.x._
11+
312
## Version 5.0.0
413

514
This version introduces major breaking changes, for usage examples, refer to the [official documentation](https://imagekit.io/docs/integration/javascript).

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@imagekit/javascript",
3-
"version": "5.0.0",
3+
"version": "5.1.0-beta.1",
44
"description": "ImageKit Javascript SDK",
55
"main": "dist/imagekit.cjs.js",
66
"module": "dist/imagekit.esm.js",

src/getResponsiveImageAttributes.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import type { SrcOptions } from './interfaces'
2+
import { buildSrc } from './url'
3+
4+
/* Default break‑point pools */
5+
const DEFAULT_DEVICE_BREAKPOINTS = [640, 750, 828, 1080, 1200, 1920, 2048, 3840] as const
6+
const DEFAULT_IMAGE_BREAKPOINTS = [16, 32, 48, 64, 96, 128, 256, 384] as const
7+
8+
export interface GetImageAttributesOptions extends SrcOptions {
9+
/**
10+
* The intended display width of the image in pixels,
11+
* used **only when the `sizes` attribute is not provided**.
12+
*
13+
* Triggers a DPR-based strategy (1x and 2x variants) and generates `x` descriptors in `srcSet`.
14+
*
15+
* Ignored if `sizes` is present.
16+
*/
17+
width?: number
18+
19+
/**
20+
* The value for the HTML `sizes` attribute
21+
* (e.g., `"100vw"` or `"(min-width:768px) 50vw, 100vw"`).
22+
*
23+
* - If it includes one or more `vw` units, breakpoints smaller than the corresponding percentage of the smallest device width are excluded.
24+
* - If it contains no `vw` units, the full breakpoint list is used.
25+
*
26+
* Enables a width-based strategy and generates `w` descriptors in `srcSet`.
27+
*/
28+
sizes?: string
29+
30+
/**
31+
* Custom list of **device-width breakpoints** in pixels.
32+
* These define common screen widths for responsive image generation.
33+
*
34+
* Defaults to `[640, 750, 828, 1080, 1200, 1920, 2048, 3840]`.
35+
* Sorted automatically.
36+
*/
37+
deviceBreakpoints?: number[]
38+
39+
/**
40+
* Custom list of **image-specific breakpoints** in pixels.
41+
* Useful for generating small variants (e.g., placeholders or thumbnails).
42+
*
43+
* Merged with `deviceBreakpoints` before calculating `srcSet`.
44+
* Defaults to `[16, 32, 48, 64, 96, 128, 256, 384]`.
45+
* Sorted automatically.
46+
*/
47+
imageBreakpoints?: number[]
48+
}
49+
50+
/**
51+
* Resulting set of attributes suitable for an HTML `<img>` element.
52+
* Useful for enabling responsive image loading.
53+
*/
54+
export interface ResponsiveImageAttributes {
55+
/** URL for the *largest* candidate (assigned to plain `src`). */
56+
src: string
57+
/** Candidate set with `w` or `x` descriptors. */
58+
srcSet?: string
59+
/** `sizes` returned (or synthesised as `100vw`). */
60+
sizes?: string
61+
/** Width as a number (if `width` was provided). */
62+
width?: number
63+
}
64+
65+
/**
66+
* Generates a responsive image URL, `srcSet`, and `sizes` attributes
67+
* based on the input options such as `width`, `sizes`, and breakpoints.
68+
*/
69+
export function getResponsiveImageAttributes(
70+
opts: GetImageAttributesOptions
71+
): ResponsiveImageAttributes {
72+
const {
73+
src,
74+
urlEndpoint,
75+
transformation = [],
76+
queryParameters,
77+
transformationPosition,
78+
sizes,
79+
width,
80+
deviceBreakpoints = DEFAULT_DEVICE_BREAKPOINTS as unknown as number[],
81+
imageBreakpoints = DEFAULT_IMAGE_BREAKPOINTS as unknown as number[],
82+
} = opts
83+
84+
const sortedDeviceBreakpoints = [...deviceBreakpoints].sort((a, b) => a - b);
85+
const sortedImageBreakpoints = [...imageBreakpoints].sort((a, b) => a - b);
86+
const allBreakpoints = [...sortedImageBreakpoints, ...sortedDeviceBreakpoints].sort((a, b) => a - b);
87+
88+
const { candidates, descriptorKind } = computeCandidateWidths({
89+
allBreakpoints,
90+
deviceBreakpoints: sortedDeviceBreakpoints,
91+
explicitWidth: width,
92+
sizesAttr: sizes,
93+
})
94+
95+
/* helper to build a single ImageKit URL */
96+
const buildURL = (w: number) =>
97+
buildSrc({
98+
src,
99+
urlEndpoint,
100+
queryParameters,
101+
transformationPosition,
102+
transformation: [
103+
...transformation,
104+
{ width: w, crop: 'at_max' }, // never upscale beyond original
105+
],
106+
})
107+
108+
/* build srcSet */
109+
const srcSet =
110+
candidates
111+
.map((w, i) => `${buildURL(w)} ${descriptorKind === 'w' ? w : i + 1}${descriptorKind}`)
112+
.join(', ') || undefined
113+
114+
const finalSizes = sizes ?? (descriptorKind === 'w' ? '100vw' : undefined)
115+
116+
return {
117+
src: buildURL(candidates[candidates.length - 1]), // largest candidate
118+
srcSet,
119+
...(finalSizes ? { sizes: finalSizes } : {}), // include only when defined
120+
...(width !== undefined ? { width } : {}), // include only when defined
121+
}
122+
}
123+
124+
function computeCandidateWidths(params: {
125+
allBreakpoints: number[]
126+
deviceBreakpoints: number[]
127+
explicitWidth?: number
128+
sizesAttr?: string
129+
}): { candidates: number[]; descriptorKind: 'w' | 'x' } {
130+
const { allBreakpoints, deviceBreakpoints, explicitWidth, sizesAttr } = params
131+
132+
// Strategy 1: Width-based srcSet (`w`) using viewport `vw` hints
133+
if (sizesAttr) {
134+
const vwTokens = sizesAttr.match(/(^|\s)(1?\d{1,2})vw/g) || []
135+
const vwPercents = vwTokens.map((t) => parseInt(t, 10))
136+
137+
if (vwPercents.length) {
138+
const smallestRatio = Math.min(...vwPercents) / 100
139+
const minRequiredPx = deviceBreakpoints[0] * smallestRatio
140+
return {
141+
candidates: allBreakpoints.filter((w) => w >= minRequiredPx),
142+
descriptorKind: 'w',
143+
}
144+
}
145+
146+
// No usable `vw` found: fallback to all breakpoints
147+
return { candidates: allBreakpoints, descriptorKind: 'w' }
148+
}
149+
150+
// Strategy 2: Fallback using explicit image width using device breakpoints
151+
if (typeof explicitWidth !== 'number') {
152+
return { candidates: deviceBreakpoints, descriptorKind: 'w' }
153+
}
154+
155+
// Strategy 3: Use 1x and 2x nearest breakpoints for `x` descriptor
156+
const nearest = (t: number) =>
157+
allBreakpoints.find((n) => n >= t) || allBreakpoints[allBreakpoints.length - 1]
158+
159+
const unique = Array.from(
160+
new Set([nearest(explicitWidth), nearest(explicitWidth * 2)]),
161+
)
162+
163+
return { candidates: unique, descriptorKind: 'x' }
164+
}

src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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 { getResponsiveImageAttributes } from "./getResponsiveImageAttributes";
5+
import type { GetImageAttributesOptions, ResponsiveImageAttributes } from "./getResponsiveImageAttributes";
46

5-
export { buildSrc, buildTransformationString, upload, ImageKitInvalidRequestError, ImageKitAbortError, ImageKitServerError, ImageKitUploadNetworkError };
7+
export { buildSrc, buildTransformationString, upload, getResponsiveImageAttributes, ImageKitInvalidRequestError, ImageKitAbortError, ImageKitServerError, ImageKitUploadNetworkError };
68
export type {
79
Transformation,
810
SrcOptions,
911
UploadOptions,
10-
UploadResponse
12+
UploadResponse,
13+
GetImageAttributesOptions, ResponsiveImageAttributes
1114
};
12-
13-

src/interfaces/Transformation.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,4 +720,19 @@ export type SolidColorOverlayTransformation = Pick<Transformation, "width" | "he
720720
* Specifies the transparency level of the overlaid solid color layer. Supports integers from `1` to `9`.
721721
*/
722722
alpha?: number;
723+
724+
/**
725+
* Specifies the background color of the solid color overlay.
726+
* Accepts an RGB hex code, an RGBA code, or a color name.
727+
*/
728+
background?: string;
729+
730+
/**
731+
* Only works if base asset is an image.
732+
*
733+
* Creates a linear gradient with two colors. Pass `true` for a default gradient, or provide a string for a custom gradient.
734+
*
735+
* [Effects and Enhancements - Gradient](https://imagekit.io/docs/effects-and-enhancements#gradient---e-gradient)
736+
*/
737+
gradient?: Transformation["gradient"]
723738
}

0 commit comments

Comments
 (0)