react-pro-image 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +391 -0
- package/dist/react-pro-image.cjs.js +330 -0
- package/dist/react-pro-image.cjs.js.map +1 -0
- package/dist/react-pro-image.es.js +326 -0
- package/dist/react-pro-image.es.js.map +1 -0
- package/dist/src/components/OptimizedImage.d.ts +28 -0
- package/dist/src/hooks/useImageFormatSupport.d.ts +5 -0
- package/dist/src/hooks/useImageLoader.d.ts +23 -0
- package/dist/src/hooks/useInView.d.ts +21 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/interfaces/index.d.ts +145 -0
- package/dist/src/types/index.d.ts +1 -0
- package/package.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# react-pro-image
|
|
4
|
+
|
|
5
|
+
**A performance-focused React image component with lazy loading, AVIF/WebP format negotiation, placeholder crossfade, and error fallback — all in a single drop-in `<img>` replacement.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/react-pro-image)
|
|
8
|
+
[](https://bundlephobia.com/package/react-pro-image)
|
|
9
|
+
[](https://github.com/MohamedAlfeky1/react-pro-image/blob/main/LICENSE)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
| Feature | Description |
|
|
19
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| 🚀 **Lazy Loading** | Images load only when they enter the viewport via `IntersectionObserver` — zero layout shift, zero wasted bandwidth. |
|
|
21
|
+
| 🎨 **AVIF / WebP Negotiation** | Automatically detects browser support and serves the smallest modern format. Results are cached in `localStorage`. |
|
|
22
|
+
| 🌄 **Placeholder Crossfade** | Show a low-res or blurred placeholder that smoothly fades out once the full image loads. |
|
|
23
|
+
| 💥 **Error Fallback** | Gracefully display a fallback image if the primary source fails to load. |
|
|
24
|
+
| 🔗 **CDN Auto-Format** | Works with Unsplash, Imgix, Cloudinary, and any CDN that accepts a format query parameter. |
|
|
25
|
+
| 📦 **Tree-Shakeable** | ESM + CJS dual builds. Import only what you use. |
|
|
26
|
+
| 🔒 **Fully Typed** | Written in TypeScript with strict, exported types for every prop and hook. |
|
|
27
|
+
| ⚡ **Zero Dependencies** | Only `react` (≥ 17) as a peer dependency. |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📦 Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# npm
|
|
35
|
+
npm install react-pro-image
|
|
36
|
+
|
|
37
|
+
# yarn
|
|
38
|
+
yarn add react-pro-image
|
|
39
|
+
|
|
40
|
+
# pnpm
|
|
41
|
+
pnpm add react-pro-image
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> **Peer dependency:** React ≥ 17.0.0
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🚀 How to use (Quick Start)
|
|
49
|
+
|
|
50
|
+
The easiest and recommended way to use `react-pro-image` is with **CDN Auto-Format**.
|
|
51
|
+
|
|
52
|
+
If your images are hosted on a CDN (like Unsplash, Imgix, or Cloudinary), you don't need to manually create different image formats. Just give the component your image URL, and it will automatically ask the CDN for the best format (AVIF or WebP) that the user's browser supports!
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { OptimizedImage } from "react-pro-image";
|
|
56
|
+
|
|
57
|
+
function Hero() {
|
|
58
|
+
return (
|
|
59
|
+
<OptimizedImage
|
|
60
|
+
autoSrc="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=800"
|
|
61
|
+
autoFormat={{ formatKey: "fm", formats: ["avif", "webp"] }}
|
|
62
|
+
autoPlaceholder="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=20&blur=10"
|
|
63
|
+
alt="Mountain landscape"
|
|
64
|
+
width={800}
|
|
65
|
+
height={400}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 💡 What do these props mean?
|
|
72
|
+
|
|
73
|
+
| Prop | Simple Explanation |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `autoSrc` | The main link to your image on the CDN. The component will automatically add the format parameter to the end of this link. |
|
|
76
|
+
| `autoFormat` | Tells the component how your CDN expects the format request. For example, Unsplash uses `fm` (so it becomes `&fm=avif`). We also tell it to try `"avif"` first, then `"webp"`. |
|
|
77
|
+
| `autoPlaceholder` | A link to a very tiny, blurry version of the same image. This loads instantly and looks nice while the user waits for the big image to download. It smoothly fades out when the real image is ready. |
|
|
78
|
+
| `alt` | Text that describes the image. Important for accessibility (screen readers) and SEO. |
|
|
79
|
+
| `width` / `height` | The size of the image container in pixels. |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 📖 More Ways to Use
|
|
84
|
+
|
|
85
|
+
### 1. Manual Sources (if you host the images yourself)
|
|
86
|
+
|
|
87
|
+
If you aren't using a CDN and instead have your images saved in your project (like in a `public` folder), you can pass each format manually.
|
|
88
|
+
|
|
89
|
+
The component will automatically check the browser's capabilities and pick the best one:
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<OptimizedImage
|
|
93
|
+
src="/photo.jpg"
|
|
94
|
+
avifSrc="/photo.avif"
|
|
95
|
+
webpSrc="/photo.webp"
|
|
96
|
+
placeholder="/photo-tiny.jpg"
|
|
97
|
+
fallback="/photo-fallback.jpg"
|
|
98
|
+
alt="A beautiful scene"
|
|
99
|
+
width={800}
|
|
100
|
+
height={400}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**How the component chooses the best image:**
|
|
105
|
+
1. Does the browser support **AVIF** and did you provide `avifSrc`? -> It uses **AVIF**.
|
|
106
|
+
2. Does the browser support **WebP** and did you provide `webpSrc`? -> It uses **WebP**.
|
|
107
|
+
3. Otherwise? -> It falls back to the standard `src` (JPEG/PNG).
|
|
108
|
+
|
|
109
|
+
*(You only need to provide the formats you have — `avifSrc` and `webpSrc` are completely optional).*
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### 2. Disabling Lazy Loading
|
|
114
|
+
|
|
115
|
+
By default, all images are "lazy-loaded". This means they won't even start downloading until the user scrolls down and the image enters the screen. This saves a lot of data!
|
|
116
|
+
|
|
117
|
+
However, for images at the very top of your page (like a hero image), you want them to load immediately. Set `lazy={false}`:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<OptimizedImage
|
|
121
|
+
src="/hero.jpg"
|
|
122
|
+
alt="Above the fold hero"
|
|
123
|
+
lazy={false}
|
|
124
|
+
width={1920}
|
|
125
|
+
height={800}
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 3. Adjusting the Viewport Trigger (When to start loading)
|
|
132
|
+
|
|
133
|
+
You can control exactly *when* the lazy loading starts using `threshold` and `rootMargin`:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<OptimizedImage
|
|
137
|
+
src="/gallery-item.jpg"
|
|
138
|
+
alt="Gallery item"
|
|
139
|
+
threshold={0.1}
|
|
140
|
+
rootMargin="200px"
|
|
141
|
+
width={400}
|
|
142
|
+
height={300}
|
|
143
|
+
/>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **`threshold={0.1}`**: Start loading when just **10%** of the image area becomes visible on screen. (The default is `0.25` or 25%).
|
|
147
|
+
- **`rootMargin="200px"`**: Start loading **200 pixels before** the image even reaches the screen. This is great for making sure images are already downloaded by the time the user scrolls to them!
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 📚 API Reference
|
|
152
|
+
|
|
153
|
+
### `<OptimizedImage />` — Props
|
|
154
|
+
|
|
155
|
+
#### Source Props
|
|
156
|
+
|
|
157
|
+
> You must provide **either** `src` **or** `autoSrc` — never both.
|
|
158
|
+
|
|
159
|
+
| Prop | Type | Default | Description |
|
|
160
|
+
| ------------ | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `src` | `string` | — | Standard image URL (JPEG, PNG, etc.). **Required** if `autoSrc` is not used. |
|
|
162
|
+
| `autoSrc` | `string` | — | CDN image URL. The component appends the format query param automatically. **Required** if `src` is not used. |
|
|
163
|
+
| `autoFormat` | `AutoFormatConfig` | — | Format negotiation config. **Required** when using `autoSrc`. |
|
|
164
|
+
| `avifSrc` | `string` | — | Optional AVIF source URL. Served if the browser supports AVIF. |
|
|
165
|
+
| `webpSrc` | `string` | — | Optional WebP source URL. Served if the browser supports WebP. |
|
|
166
|
+
|
|
167
|
+
#### Placeholder Props
|
|
168
|
+
|
|
169
|
+
> You may provide **either** `placeholder` **or** `autoPlaceholder` — never both.
|
|
170
|
+
|
|
171
|
+
| Prop | Type | Default | Description |
|
|
172
|
+
| ----------------- | -------- | ------- | ----------------------------------------------------------------------------------- |
|
|
173
|
+
| `placeholder` | `string` | — | URL of a low-res or blurred placeholder image. Fades out once the full image loads. |
|
|
174
|
+
| `autoPlaceholder` | `string` | — | CDN-generated placeholder URL. |
|
|
175
|
+
|
|
176
|
+
#### Fallback Props
|
|
177
|
+
|
|
178
|
+
> You may provide **either** `fallback` **or** `autoFallback` — never both.
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Default | Description |
|
|
181
|
+
| -------------- | -------- | ------- | -------------------------------------------------------------------------- |
|
|
182
|
+
| `fallback` | `string` | — | Image URL displayed if the primary source fails to load. |
|
|
183
|
+
| `autoFallback` | `string` | — | CDN fallback URL. Format param is appended automatically via `autoFormat`. |
|
|
184
|
+
| `avifFallback` | `string` | — | AVIF override for the fallback image. |
|
|
185
|
+
| `webpFallback` | `string` | — | WebP override for the fallback image. |
|
|
186
|
+
|
|
187
|
+
#### Layout and Behavior Props
|
|
188
|
+
|
|
189
|
+
| Prop | Type | Default | Description |
|
|
190
|
+
| ------------ | --------- | ------- | ---------------------------------------------------------- |
|
|
191
|
+
| `alt` | `string` | — | Accessible alt text for the image. |
|
|
192
|
+
| `width` | `number` | — | Display width of the container in pixels. |
|
|
193
|
+
| `height` | `number` | — | Display height of the container in pixels. |
|
|
194
|
+
| `className` | `string` | — | CSS class names applied to the outer wrapper div. |
|
|
195
|
+
| `lazy` | `boolean` | `true` | Enable or disable lazy loading via IntersectionObserver. |
|
|
196
|
+
| `threshold` | `number` | `0.25` | Visibility ratio (0 to 1) required to trigger loading. |
|
|
197
|
+
| `rootMargin` | `string` | `"0px"` | CSS-style margin to expand or shrink the observation area. |
|
|
198
|
+
|
|
199
|
+
> The component also spreads any additional `HTMLDivElement` attributes onto the outer wrapper.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### AutoFormatConfig
|
|
204
|
+
|
|
205
|
+
Configuration object for CDN format negotiation.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
interface AutoFormatConfig {
|
|
209
|
+
formatKey: string;
|
|
210
|
+
formats: ("avif" | "webp")[];
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- **formatKey** — The query parameter key used by the CDN (e.g. `"fm"`, `"f"`, `"format"`).
|
|
215
|
+
- **formats** — Ordered list of modern formats to try, from most preferred to least (e.g. `["avif", "webp"]`).
|
|
216
|
+
|
|
217
|
+
**CDN examples:**
|
|
218
|
+
|
|
219
|
+
| CDN | formatKey | Example URL |
|
|
220
|
+
| ---------------- | ---------- | ----------------- |
|
|
221
|
+
| Unsplash / Imgix | `"fm"` | `...?fm=avif` |
|
|
222
|
+
| Cloudinary | `"f"` | `...&f=webp` |
|
|
223
|
+
| Custom | `"format"` | `...?format=avif` |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 🪝 Hooks
|
|
228
|
+
|
|
229
|
+
The package exports three composable hooks you can use independently in custom components.
|
|
230
|
+
|
|
231
|
+
### useImageFormatSupport()
|
|
232
|
+
|
|
233
|
+
Detects AVIF and WebP support by loading tiny test images. Results are cached in `localStorage` so detection runs only once per browser.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
import { useImageFormatSupport } from "react-pro-image";
|
|
237
|
+
|
|
238
|
+
function MyComponent() {
|
|
239
|
+
const { avif, webp, ready } = useImageFormatSupport();
|
|
240
|
+
|
|
241
|
+
if (!ready) return <p>Checking format support...</p>;
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<img
|
|
245
|
+
src={avif ? "/photo.avif" : webp ? "/photo.webp" : "/photo.jpg"}
|
|
246
|
+
alt="example"
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Returns:**
|
|
253
|
+
|
|
254
|
+
| Property | Type | Description |
|
|
255
|
+
| -------- | --------- | -------------------------------------- |
|
|
256
|
+
| `avif` | `boolean` | `true` if the browser can decode AVIF. |
|
|
257
|
+
| `webp` | `boolean` | `true` if the browser can decode WebP. |
|
|
258
|
+
| `ready` | `boolean` | `true` once detection is complete. |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### useImageLoader(options)
|
|
263
|
+
|
|
264
|
+
Preloads an image off-screen and tracks its load state. Loading is deferred until `isInView` is `true`, enabling lazy-load patterns.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { useImageLoader } from "react-pro-image";
|
|
268
|
+
|
|
269
|
+
function MyComponent() {
|
|
270
|
+
const state = useImageLoader({
|
|
271
|
+
src: "/photo.jpg",
|
|
272
|
+
avifSrc: "/photo.avif",
|
|
273
|
+
isInView: true,
|
|
274
|
+
});
|
|
275
|
+
// state: "idle" -> "loading" -> "loaded" | "error"
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Options (UseImageLoaderOptions):**
|
|
280
|
+
|
|
281
|
+
| Option | Type | Default | Description |
|
|
282
|
+
| ------------ | ------------------ | ------- | ---------------------------------------- |
|
|
283
|
+
| `src` | `string` | — | Baseline image source. |
|
|
284
|
+
| `autoSrc` | `string` | — | CDN image URL for auto-format mode. |
|
|
285
|
+
| `autoFormat` | `AutoFormatConfig` | — | Format config (required with `autoSrc`). |
|
|
286
|
+
| `avifSrc` | `string` | — | Optional AVIF source (highest priority). |
|
|
287
|
+
| `webpSrc` | `string` | — | Optional WebP source (second priority). |
|
|
288
|
+
| `isInView` | `boolean` | `false` | When `true`, triggers the preload. |
|
|
289
|
+
|
|
290
|
+
**Returns:** `ImageLoadState` — `"idle"` or `"loading"` or `"loaded"` or `"error"`
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
### useInView(options?)
|
|
295
|
+
|
|
296
|
+
Tracks whether a DOM element has entered the viewport using `IntersectionObserver`. One-shot: the observer disconnects after the first intersection.
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
import { useInView } from "react-pro-image";
|
|
300
|
+
|
|
301
|
+
function MyComponent() {
|
|
302
|
+
const { ref, isInView } = useInView({ threshold: 0.25 });
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<div ref={ref}>{isInView && <img src="/photo.jpg" alt="example" />}</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Options (UseInViewOptions):**
|
|
311
|
+
|
|
312
|
+
| Option | Type | Default | Description |
|
|
313
|
+
| ------------ | -------- | ------- | ---------------------------------------------------- |
|
|
314
|
+
| `threshold` | `number` | `0.25` | Visibility ratio (0 to 1) required to trigger. |
|
|
315
|
+
| `rootMargin` | `string` | `"0px"` | CSS margin to expand or shrink the observation area. |
|
|
316
|
+
|
|
317
|
+
**Returns:**
|
|
318
|
+
|
|
319
|
+
| Property | Type | Description |
|
|
320
|
+
| ---------- | --------------------------- | -------------------------------------------- |
|
|
321
|
+
| `ref` | `RefObject<HTMLDivElement>` | Attach to the target element. |
|
|
322
|
+
| `isInView` | `boolean` | `true` once the element meets the threshold. |
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 🔤 Exported Types
|
|
327
|
+
|
|
328
|
+
All types are exported and available for use in your own components:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import type {
|
|
332
|
+
OptimizedImageProps,
|
|
333
|
+
AutoFormatConfig,
|
|
334
|
+
ImageWithFormatsProps,
|
|
335
|
+
UseImageLoaderOptions,
|
|
336
|
+
UseInViewOptions,
|
|
337
|
+
ImageLoadState,
|
|
338
|
+
} from "react-pro-image";
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
| Type | Description |
|
|
342
|
+
| ----------------------- | -------------------------------------------------------------- |
|
|
343
|
+
| `OptimizedImageProps` | Full prop type for the OptimizedImage component. |
|
|
344
|
+
| `AutoFormatConfig` | Configuration for CDN format query parameters. |
|
|
345
|
+
| `ImageWithFormatsProps` | Props for the internal format-resolving image renderer. |
|
|
346
|
+
| `UseImageLoaderOptions` | Options for the useImageLoader hook. |
|
|
347
|
+
| `UseInViewOptions` | Options for the useInView hook. |
|
|
348
|
+
| `ImageLoadState` | Union type: `"idle"` or `"loading"` or `"loaded"` or `"error"` |
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## ⚙️ How It Works
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
┌─────────────────────────────────────────────────────────┐
|
|
356
|
+
│ OptimizedImage │
|
|
357
|
+
│ │
|
|
358
|
+
│ 1. useInView() │
|
|
359
|
+
│ - IntersectionObserver watches the container │
|
|
360
|
+
│ - Flips isInView to true when threshold is met │
|
|
361
|
+
│ - Disconnects after first trigger (one-shot) │
|
|
362
|
+
│ │
|
|
363
|
+
│ 2. useImageLoader() │
|
|
364
|
+
│ - Waits until isInView is true │
|
|
365
|
+
│ - useImageFormatSupport() detects AVIF/WebP │
|
|
366
|
+
│ - Creates off-screen Image() to preload best format │
|
|
367
|
+
│ - State: idle -> loading -> loaded or error │
|
|
368
|
+
│ │
|
|
369
|
+
│ 3. Render │
|
|
370
|
+
│ - Placeholder layer (opacity: 1 -> 0 on load) │
|
|
371
|
+
│ - Real image layer (mounted after entering view) │
|
|
372
|
+
│ - Fallback layer (shown only on error) │
|
|
373
|
+
└─────────────────────────────────────────────────────────┘
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 🌐 Browser Support
|
|
379
|
+
|
|
380
|
+
| Feature | Requirement |
|
|
381
|
+
| ------------ | ------------------------------------------------------- |
|
|
382
|
+
| Lazy Loading | IntersectionObserver — supported in all modern browsers |
|
|
383
|
+
| AVIF | Chrome 85+, Firefox 93+, Safari 16.4+ |
|
|
384
|
+
| WebP | Chrome 32+, Firefox 65+, Safari 14+ |
|
|
385
|
+
| Fallback | Automatic — gracefully falls back to src (JPEG/PNG) |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 📄 License
|
|
390
|
+
|
|
391
|
+
[MIT](https://github.com/MohamedAlfeky1/react-pro-image/blob/main/LICENSE) © [MohamedAlfeky1](https://github.com/MohamedAlfeky1)
|