react-lazy-img-observer 1.4.0 → 1.5.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 CHANGED
@@ -1,4 +1,10 @@
1
- # 📷 ImageLazy (v1.4.0)
1
+ # 📷 ImageLazy (v1.5.0)
2
+
3
+ ![npm](https://img.shields.io/npm/v/react-lazy-img-observer)
4
+ ![downloads](https://img.shields.io/npm/dw/react-lazy-img-observer)
5
+ ![license](https://img.shields.io/npm/l/react-lazy-img-observer)
6
+
7
+ ---
2
8
 
3
9
  **ImageLazy** is a lightweight and customizable React component for lazy loading images using the Intersection Observer API. It includes fallback handling, visual effects, and support for custom loaders and transitions — perfect for performance-oriented applications.
4
10
 
@@ -6,12 +12,70 @@
6
12
 
7
13
  ---
8
14
 
15
+ ## 📚 Contenido
16
+
17
+ - [📦 Instalación](#-installation--instalación)
18
+ - [🚀 Uso Básico](#-usage--uso-básico)
19
+ - [📃 Tabla de Props](#-props-table--tabla-de-props)
20
+ - [🎨 Ejemplos Avanzados](#-advanced-example-with-spinner-and-fallback--ejemplo-avanzado-con-spinner-y-fallback)
21
+ - [💡 Uso de Props](#-prop-usage-with-examples--uso-de-props-con-ejemplos)
22
+ - [🔁 Efectos Visuales](#-effects--efectos-visuales)
23
+ - [🔧 Cómo funciona](#-how-it-works--cómo-funciona)
24
+ - [❓ FAQ](#-faq)
25
+ - [📊 Comparación](#-comparison-with-react-lazy-load-image-component--comparación-con-react-lazy-load-image-component)
26
+ - [📄 Licencia](#-license--licencia)
27
+ - [👤 Autor](#-author--autor)
28
+
29
+ ---
30
+
9
31
  ## 📦 Installation / Instalación
10
32
 
11
33
  ```bash
12
34
  npm install react-lazy-img-observer
13
35
  ```
14
36
 
37
+ ## 📃 Props Table / Tabla de Props
38
+
39
+ | Prop | Tipo | Por Defecto | Descripción (ES) / Description (EN) |
40
+ | -------------------- | ----------------------------------------- | ----------- | --------------------------------------------------------------- |
41
+ | `src` | `string` | – | URL de la imagen / Image URL |
42
+ | `alt` | `string` | – | Texto alternativo / Alt text |
43
+ | `srcSet` | `string` | – | Set de imágenes responsive / Responsive image set |
44
+ | `sizes` | `string` | – | Tamaños para srcSet / Responsive sizes |
45
+ | `width` | `number` | – | Ancho fijo / Fixed width |
46
+ | `height` | `number` | – | Alto fijo / Fixed height |
47
+ | `id` | `string \| number` | – | ID del elemento / Element ID |
48
+ | `className` | `string` | – | Clase CSS personalizada / Custom CSS class |
49
+ | `title` | `string` | – | Atributo title del tag img / Tooltip text |
50
+ | `useTitleFromAlt` | `boolean` | `false` | Usa el alt como title / Use `alt` value as `title` |
51
+ | `extraData` | `ImgHTMLAttributes<HTMLImageElement>` | – | Props adicionales para img / Extra native props |
52
+ | `viewTransitionName` | `string` | – | Nombre para View Transitions API |
53
+ | `style` | `CSSProperties` | – | Estilos inline / Inline styles |
54
+ | `backgroundColor` | `string` | – | Color de fondo mientras carga / Background while loading |
55
+ | `animationDuration` | `string` | `0.9s` | Duración de la transición / Animation duration |
56
+ | `blurAmount` | `string` | `20px` | Desenfoque inicial / Initial blur effect |
57
+ | `fallbackSrc` | `string` | – | Imagen fallback si falla / Fallback image if loading fails |
58
+ | `threshold` | `number` | `0.5` | Umbral de visibilidad / Intersection threshold |
59
+ | `transitionType` | `'blur' \| 'fade' \| 'scale' \| 'custom'` | `'blur'` | Tipo de transición visual / Type of transition |
60
+ | `onLoadComplete` | `() => void` | – | Callback al cargar / Callback after image is fully loaded |
61
+ | `visibleByDefault` | `boolean` | `false` | Cargar sin lazy / Load immediately without waiting for viewport |
62
+ | `loadingComponent` | `ReactNode` | – | Componente mientras carga / Custom loader component |
63
+
64
+ ---
65
+
66
+ ---
67
+
68
+ ## 🔁 Effects / Efectos Visuales
69
+
70
+ | Effect | Description |
71
+ | -------- | ----------------------------------------------------------------------------------------- |
72
+ | `blur` | Apply blur and remove it after load / Aplicar desenfoque y eliminarlo después de cargar |
73
+ | `fade` | Fade in the image / Desvanecimiento de la imagen |
74
+ | `scale` | Slight zoom effect / Ligero efecto de zoom |
75
+ | `custom` | Use your own CSS (add class to image) / Utilice su propio CSS (agregue clase a la imagen) |
76
+
77
+ ---
78
+
15
79
  ---
16
80
 
17
81
  ## 🚀 Usage / Uso Básico
@@ -31,6 +95,12 @@ function App() {
31
95
  }
32
96
  ```
33
97
 
98
+ ### ✅ Explicación:
99
+
100
+ - `src`: URL de la imagen / Image URL.
101
+ - `alt`: Texto descriptivo para accesibilidad y SEO / Descriptive text for accessibility and SEO.
102
+ - `width` / `height`: Definen tamaño fijo (opcional pero recomendable para evitar reflow / Define fixed size (optional but recommended to avoid reflow).
103
+
34
104
  ---
35
105
 
36
106
  ## 🎨 Advanced Example with Spinner and Fallback / Ejemplo Avanzado con Spinner y Fallback
@@ -60,142 +130,271 @@ function GalleryImage() {
60
130
 
61
131
  ---
62
132
 
63
- ## 💫 Custom Transition with Your Own CSS / Transiciones Personalizadas con CSS Propio
133
+ ## 💡 Prop Usage with Examples / Uso de Props con Ejemplos
134
+
135
+ ### 📍 `fallbackSrc`
136
+
137
+ ```tsx
138
+ <ImageLazy
139
+ src="/img/main.jpg"
140
+ fallbackSrc="/img/fallback.jpg"
141
+ alt="Imagen principal"
142
+ />
143
+ ```
144
+
145
+ 👉 Si la imagen original falla, se muestra automáticamente la alternativa / If the original image fails, the alternative is automatically displayed.
146
+
147
+ ---
148
+
149
+ ### 🎨 `backgroundColor`
150
+
151
+ ```tsx
152
+ <ImageLazy
153
+ src="/img/pic.jpg"
154
+ alt="Imagen con fondo gris"
155
+ backgroundColor="#f0f0f0"
156
+ />
157
+ ```
158
+
159
+ 👉 Color visible mientras la imagen carga (mejora UX) / Color visible while image loads (UX improvement).
160
+
161
+ ---
162
+
163
+ ### 💫 `blurAmount` y `animationDuration`
64
164
 
65
165
  ```tsx
66
166
  <ImageLazy
67
- src="/img/circle.jpg"
68
- alt="Spinning Image"
69
- transitionType="custom"
70
- className="my-custom-transition"
167
+ src="/img/blurred.jpg"
168
+ alt="Efecto blur personalizado"
169
+ blurAmount="5px"
170
+ animationDuration="2s"
71
171
  />
72
172
  ```
73
173
 
74
- **CSS**
174
+ 👉 Controla el desenfoque inicial y duración del efecto / Controls the initial blur and duration of the effect.
175
+
176
+ ---
177
+
178
+ ### 🌀 `transitionType`
179
+
180
+ ```tsx
181
+ <ImageLazy src="/img/pic.jpg" alt="Fade in" transitionType="fade" />
182
+ <ImageLazy src="/img/pic.jpg" alt="Scale in" transitionType="scale" />
183
+ <ImageLazy src="/img/pic.jpg" alt="Custom CSS" transitionType="custom" className="img-fx" />
184
+ ```
185
+
186
+ **CSS (ejemplo avanzado):**
75
187
 
76
188
  ```css
77
- .my-custom-transition {
189
+ .img-fx {
78
190
  opacity: 0;
79
- transform: rotate(10deg) scale(0.95);
80
- transition: all 0.8s ease-in-out;
191
+ transform: scale(0.8) rotate(-2deg);
192
+ transition: all 0.8s ease;
81
193
  }
82
-
83
- .my-custom-transition.loaded {
194
+ .img-fx.loaded {
84
195
  opacity: 1;
85
- transform: rotate(0deg) scale(1);
196
+ transform: scale(1) rotate(0deg);
86
197
  }
87
198
  ```
88
199
 
200
+ 👉 Usa `custom` para controlar completamente la animación con tu propio CSS / Use `custom` to fully control the animation with your own CSS.
201
+
89
202
  ---
90
203
 
91
- ## 🧩 Props
204
+ ### 🔁 `visibleByDefault`
205
+
206
+ ```tsx
207
+ <ImageLazy src="/img/pic.jpg" alt="Carga inmediata" visibleByDefault={true} />
208
+ ```
92
209
 
93
- | Prop | Type | Default | Description / Descripción |
94
- | -------------------- | ----------------------------------- | ------- | ---------------------------------------------------------- |
95
- | `src` | `string` | - | Image URL / URL de imagen ✅ |
96
- | `alt` | `string` | - | Alt text / Texto alternativo ✅ |
97
- | `width` | `number` | - | Width / Ancho |
98
- | `height` | `number` | - | Height / Alto |
99
- | `srcSet` | `string` | - | Responsive image set |
100
- | `sizes` | `string` | - | Responsive sizes |
101
- | `fallbackSrc` | `string` | - | Fallback if image fails / Imagen alternativa si falla |
102
- | `backgroundColor` | `string` | - | Background color before load |
103
- | `blurAmount` | `string` | `20px` | Initial blur / Desenfoque inicial |
104
- | `animationDuration` | `string` | `0.9s` | Transition duration / Duración de transición |
105
- | `threshold` | `number` | `0.5` | Intersection threshold / Umbral de visibilidad |
106
- | `transitionType` | `"blur"\|"fade"\|"scale"\|"custom"` | `blur` | Type of transition / Tipo de transición |
107
- | `loadingComponent` | `ReactNode` | - | Custom loader / Componente de carga personalizado ✅ |
108
- | `onLoadComplete` | `() => void` | - | Callback when loaded / Al terminar de cargar |
109
- | `visibleByDefault` | `boolean` | `false` | Skip lazy load / Saltar carga diferida si ya está en caché |
110
- | `viewTransitionName` | `string` | - | View Transitions API |
111
- | `extraData` | `ImgHTMLAttributes` | - | Additional attributes |
112
- | `style` | `CSSProperties` | - | Inline styles |
113
- | `className` | `string` | - | Custom CSS class |
114
- | `id` | `string`\|`number` | - | Element ID |
210
+ 👉 Útil cuando no deseas esperar a que la imagen entre al viewport (ej: SSR o cuando ya está precargada / Useful when you don't want to wait for the image to enter the viewport (e.g. SSR or when it is already pre-loaded).
115
211
 
116
212
  ---
117
213
 
118
214
  ## 🔧 How it works / ¿Cómo funciona?
119
215
 
120
- - When `visibleByDefault` is `false` (default), the image loads only when it's in the viewport (IntersectionObserver).
121
- - If `visibleByDefault` is `true`, the image is loaded immediately (useful for SSR or cached images).
122
- - You can add a custom CSS transition, fallback, background color, and even a loading spinner.
216
+ - When `visibleByDefault` is `false` (default), the image loads only when it's in the viewport (IntersectionObserver)/Cuando `visibleByDefault` es `false` (predeterminado), la imagen se carga solo cuando está en la ventana gráfica (IntersectionObserver).
217
+ - If `visibleByDefault` is `true`, the image is loaded immediately (useful for SSR or cached images)/Si `visibleByDefault` es `true`, la imagen se carga inmediatamente (útil para SSR o imágenes almacenadas en caché).
218
+ - You can add a custom CSS transition, fallback, background color, and even a loading spinner/Puede agregar una transición CSS personalizada, una alternativa, un color de fondo e incluso un indicador de carga.
123
219
 
124
220
  ---
125
221
 
126
- ## 🔁 Effects / Efectos Visuales
222
+ ### 🧭 `loadingComponent`
223
+
224
+ ```tsx
225
+ <ImageLazy
226
+ src="/img/loading.jpg"
227
+ alt="Con spinner"
228
+ loadingComponent={<div className="spinner">Cargando...</div>}
229
+ />
230
+ ```
127
231
 
128
- | Effect | Description |
129
- | -------- | ------------------------------------- |
130
- | `blur` | Apply blur and remove it after load |
131
- | `fade` | Fade in the image |
132
- | `scale` | Slight zoom effect |
133
- | `custom` | Use your own CSS (add class to image) |
232
+ 👉 Muestra un componente personalizado mientras se carga la imagen / Display a custom component while the image is loading.
134
233
 
135
234
  ---
136
235
 
137
- ## 🌐 SSR Compatible / Compatible con SSR
236
+ ### 🧪 `onLoadComplete`
138
237
 
139
- ImageLazy is safe to use in SSR environments (like Next.js). It checks for `window` and disables IntersectionObserver logic when rendering on the server.
238
+ ```tsx
239
+ function handleLoad() {
240
+ console.log("¡Imagen completamente cargada!");
241
+ }
140
242
 
141
- ImageLazy es seguro para entornos con renderizado del lado del servidor como Next.js. Detecta si `window` está disponible antes de usar IntersectionObserver.
243
+ <ImageLazy
244
+ src="/img/event.jpg"
245
+ alt="Imagen con evento"
246
+ onLoadComplete={handleLoad}
247
+ />;
248
+ ```
249
+
250
+ ### 🔁 `onLoadComplete` con lógica avanzada / with advanced logic
251
+
252
+ ```tsx
253
+ const [estado, setEstado] = useState("cargando");
254
+
255
+ <ImageLazy
256
+ src="/img/camara.jpg"
257
+ alt="Avanzado"
258
+ onLoadComplete={() => setEstado("cargada")}
259
+ />
260
+ <p>Estado de imagen: {estado}</p>
261
+ ```
262
+
263
+ ### ✅ `onLoadComplete` con múltiples imágenes / with multiple images
264
+
265
+ ```tsx
266
+ const [cargas, setCargas] = useState(0);
267
+
268
+ const incrementar = () => setCargas((prev) => prev + 1);
269
+
270
+ <ImageLazy src="/img/uno.jpg" alt="uno" onLoadComplete={incrementar} />
271
+ <ImageLazy src="/img/dos.jpg" alt="dos" onLoadComplete={incrementar} />
272
+ <p>Imágenes cargadas: {cargas}</p>
273
+ ```
274
+
275
+ ---
276
+
277
+ ### 🧷 `useTitleFromAlt`
278
+
279
+ ```tsx
280
+ <ImageLazy
281
+ src="/img/title.jpg"
282
+ alt="Una imagen informativa"
283
+ useTitleFromAlt={true}
284
+ />
285
+ ```
286
+
287
+ 👉 Usa el valor de `alt` también como atributo `title` automáticamente. Útil para tooltips nativos / Automatically uses the `alt` value as the `title` attribute as well. Useful for native tooltips..
142
288
 
143
289
  ---
144
290
 
145
- ## 🖼 Spinner / Carga Personalizada
291
+ ### 🖼 `viewTransitionName`
292
+
293
+ ```tsx
294
+ <ImageLazy
295
+ src="/img/view.jpg"
296
+ alt="Transición de vista"
297
+ viewTransitionName="fade-image"
298
+ />
299
+ ```
300
+
301
+ 👉 Para integrar con la API de `view-transition` (Chrome ≥ 111) y hacer animaciones entre páginas con `<ViewTransition>` / To integrate with the `view-transition` API (Chrome ≥ 111) and make animations between pages with `<ViewTransition>`.
302
+
303
+ ---
146
304
 
147
- You can use `loadingComponent` to display a spinner while the image loads:
305
+ ### 🏷️ `id` (uso en scroll o testing)
148
306
 
149
307
  ```tsx
150
308
  <ImageLazy
151
- src="/img/pic.webp"
152
- alt="Custom Spinner"
153
- loadingComponent={<div className="my-spinner" />}
309
+ src="/img/anchor.jpg"
310
+ alt="Imagen anclada"
311
+ id="fotoPrincipal"
154
312
  />
313
+ <a href="#fotoPrincipal">Ir a la imagen</a>
155
314
  ```
156
315
 
157
- ```css
158
- .my-spinner {
159
- width: 40px;
160
- height: 40px;
161
- border: 4px solid #ccc;
162
- border-top-color: #000;
163
- border-radius: 50%;
164
- animation: spin 1s linear infinite;
165
- }
166
- @keyframes spin {
167
- to { transform: rotate(360deg); }
168
- }
316
+ 👉 Permite identificar la imagen fácilmente para navegación, testing u operaciones DOM / Allows you to easily identify the image for navigation, testing, or DOM operations..
317
+
318
+ ---
319
+
320
+ ### 📐 `srcSet` y `sizes`
321
+
322
+ ```tsx
323
+ <ImageLazy
324
+ src="/img/400.jpg"
325
+ srcSet="/img/400.jpg 400w, /img/800.jpg 800w"
326
+ sizes="(max-width: 600px) 400px, 800px"
327
+ alt="Imagen responsive"
328
+ />
329
+ ```
330
+
331
+ 👉 Mejora el rendimiento en dispositivos móviles cargando imágenes más ligeras según el ancho de pantalla / Improves performance on mobile devices by loading images smaller based on screen width.
332
+
333
+ ---
334
+
335
+ ### 🧩 `extraData`, `className`, `style`
336
+
337
+ ```tsx
338
+ <ImageLazy
339
+ src="/img/extra.jpg"
340
+ alt="Con extras"
341
+ className="rounded shadow"
342
+ style={{ objectFit: "cover" }}
343
+ extraData={{ draggable: false, crossOrigin: "anonymous" }}
344
+ />
169
345
  ```
170
346
 
347
+ 👉 Puedes personalizar todo el comportamiento nativo de `<img>` sin limitaciones / You can customize all native behavior of `<img>` without limitations.
348
+
349
+ ---
350
+
351
+ ## 🌐 SSR Compatible / Compatible con SSR
352
+
353
+ ImageLazy is safe to use in SSR environments (like Next.js). It checks for `window` and disables IntersectionObserver logic when rendering on the server.
354
+
355
+ ImageLazy es seguro para entornos con renderizado del lado del servidor como Next.js. Detecta si `window` está disponible antes de usar IntersectionObserver.
356
+
171
357
  ---
172
358
 
173
359
  ## 📊 Comparison with react-lazy-load-image-component / Comparación con react-lazy-load-image-component
174
360
 
175
- | Feature / Característica | `ImageLazy` ✅ | `react-lazy-load-image-component` ❌ |
176
- |----------------------------------------------|------------------------------------|--------------------------------------------------|
177
- | Lightweight & no external dependencies | ✅ Yes / Sí | ❌ No (más pesado y con múltiples componentes) |
178
- | SSR Friendly (Next.js compatible) | ✅ Yes / Sí | ✅ Yes / Sí |
179
- | Custom transitions via CSS (`custom`) | ✅ Yes / Sí | ❌ No |
180
- | Native spinner support (`loadingComponent`) | ✅ Yes / Sí | ❌ No (solo con workarounds) |
181
- | Fallback support (`fallbackSrc`) | ✅ Yes / Sí | ✅ Yes / Sí |
182
- | Responsive images (`srcSet` and `sizes`) | ✅ Yes / Sí | ✅ Yes / Sí |
183
- | Load other elements lazily | ❌ Images only / Solo imágenes | ✅ Yes (via `LazyLoadComponent`) |
184
- | Built-in effects | ✅ `blur`, `fade`, `scale`, custom | ✅ `blur`, `opacity`, `bw` |
185
- | Transition customization | ✅ Total freedom with CSS | ❌ Limited |
361
+ | Feature / Característica | `ImageLazy` ✅ | `react-lazy-load-image-component` ❌ |
362
+ | ------------------------------------------- | ---------------------------------- | ---------------------------------------------- |
363
+ | Lightweight & no external dependencies | ✅ Yes / Sí | ❌ No (más pesado y con múltiples componentes) |
364
+ | SSR Friendly (Next.js compatible) | ✅ Yes / Sí | ✅ Yes / Sí |
365
+ | Custom transitions via CSS (`custom`) | ✅ Yes / Sí | ❌ No |
366
+ | Native spinner support (`loadingComponent`) | ✅ Yes / Sí | ❌ No (solo con workarounds) |
367
+ | Fallback support (`fallbackSrc`) | ✅ Yes / Sí | ✅ Yes / Sí |
368
+ | Responsive images (`srcSet` and `sizes`) | ✅ Yes / Sí | ✅ Yes / Sí |
369
+ | Load other elements lazily | ❌ Images only / Solo imágenes | ✅ Yes (via `LazyLoadComponent`) |
370
+ | Built-in effects | ✅ `blur`, `fade`, `scale`, custom | ✅ `blur`, `opacity`, `bw` |
371
+ | Transition customization | ✅ Total freedom with CSS | ❌ Limited |
372
+
373
+ ---
374
+
375
+ ## ❓ FAQ
376
+
377
+ **¿Por qué mi imagen no se muestra?** / **Why isn't my image showing?**
378
+ Asegúrate de que `src` no sea `null`, y revisa si `visibleByDefault` está en `true` / Make sure `src` is not `null`, and check if `visibleByDefault` is set to `true`..
379
+
380
+ **¿Puedo usarlo en SSR como Next.js?** / **Can I use it in SSR like Next.js?**
381
+ Sí. Internamente revisa si `window` existe antes de ejecutar el observer / Yes. Internally it checks if `window` exists before running the observer.
382
+
383
+ **¿Cómo puedo aplicar una animación personalizada?** / **How ​​can I apply a custom animation?**
384
+ Usa `transitionType="custom"` y define tu clase con efectos CSS / Use `transitionType="custom"` and define your class with CSS effects.
186
385
 
187
386
  ---
188
387
 
189
388
  ## 📄 License / Licencia
190
389
 
191
- [ISC License](./LICENSE)
390
+ [MIT License](./LICENSE)
192
391
 
193
392
  ---
194
393
 
195
394
  ## 👤 Author / Autor
196
395
 
197
396
  **Percy Chuzon**\
198
- 📧 [contacto@percychuzon.com](mailto\:contacto@percychuzon.com)\
397
+ 📧 [contacto@percychuzon.com](mailto:contacto@percychuzon.com)\
199
398
  🌐 [https://percychuzon.com](https://percychuzon.com)
200
399
 
201
400
  ---
@@ -209,4 +408,3 @@ Si deseas más control, usa `transitionType="custom"` y aplica tus estilos con C
209
408
  ---
210
409
 
211
410
  Happy loading! 🎉 / ¡Carga feliz! 🎉
212
-
@@ -3,12 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ImageLazy = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, viewTransitionName, style, backgroundColor, animationDuration = "0.9s", blurAmount = "20px", fallbackSrc, threshold = 0.5, transitionType = "blur", onLoadComplete, visibleByDefault = false, loadingComponent, }) => {
6
+ const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, useTitleFromAlt = false, viewTransitionName, style, backgroundColor, animationDuration = "0.9s", blurAmount = "20px", fallbackSrc, threshold = 0.5, transitionType = "blur", onLoadComplete, visibleByDefault = false, loadingComponent, }) => {
7
7
  const imageRef = (0, react_1.useRef)(null);
8
8
  const [realSrc, setRealSrc] = (0, react_1.useState)(visibleByDefault ? src : null);
9
9
  const [loaded, setLoaded] = (0, react_1.useState)(false);
10
10
  const [hasError, setHasError] = (0, react_1.useState)(false);
11
11
  (0, react_1.useEffect)(() => {
12
+ const isDev = typeof window !== "undefined" &&
13
+ window?.location?.hostname?.includes("localhost");
14
+ if (isDev) {
15
+ if (!alt) {
16
+ console.warn("[ImageLazy] ⚠️ Se recomienda definir el atributo 'alt' por accesibilidad.");
17
+ }
18
+ if (!src) {
19
+ console.warn("[ImageLazy] ⚠️ La prop 'src' está vacía o nula. La imagen no se cargará.");
20
+ }
21
+ }
22
+ }, [alt, src]);
23
+ (0, react_1.useLayoutEffect)(() => {
12
24
  if (typeof window === "undefined" || visibleByDefault)
13
25
  return;
14
26
  if (typeof IntersectionObserver === "undefined" || !imageRef.current)
@@ -54,7 +66,7 @@ const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extr
54
66
  filter: loaded ? "none" : `blur(${blurAmount})`,
55
67
  transition: `filter ${animationDuration}`,
56
68
  };
57
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!loaded && loadingComponent, !hasError && ((0, jsx_runtime_1.jsx)("img", { ref: imageRef, src: realSrc ?? "", alt: alt, title: title, className: className, width: width, height: height, loading: "lazy", id: id?.toString(), onLoad: handleLoad, onError: handleError, srcSet: srcSet, sizes: sizes, style: {
69
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!loaded && loadingComponent, !hasError && ((0, jsx_runtime_1.jsx)("img", { ref: imageRef, src: realSrc ?? undefined, alt: alt, title: title ?? (useTitleFromAlt ? alt : undefined), className: className, width: width, height: height, loading: "lazy", id: id?.toString(), onLoad: handleLoad, onError: handleError, srcSet: realSrc ? srcSet : undefined, sizes: realSrc ? sizes : undefined, style: {
58
70
  backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined,
59
71
  backgroundSize: "cover",
60
72
  backgroundPosition: "center",
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ImageLazy=void 0;const jsx_runtime_1=require("react/jsx-runtime"),react_1=require("react"),ImageLazy=({src:e,alt:t,srcSet:r,sizes:n,width:a,height:o,className:i,id:s,extraData:c,title:u,viewTransitionName:l,style:d,backgroundColor:m,animationDuration:f="0.9s",blurAmount:g="20px",fallbackSrc:y,threshold:p=.5,transitionType:_="blur",onLoadComplete:b,visibleByDefault:x=!1,loadingComponent:h})=>{const v=(0,react_1.useRef)(null),[z,j]=(0,react_1.useState)(x?e:null),[I,S]=(0,react_1.useState)(!1),[w,L]=(0,react_1.useState)(!1);(0,react_1.useEffect)((()=>{if("undefined"==typeof window||x)return;if("undefined"==typeof IntersectionObserver||!v.current)return;const t=new IntersectionObserver((r=>{r.forEach((r=>{r.isIntersecting&&(j(e),t.unobserve(r.target))}))}),{root:null,rootMargin:"0px",threshold:p});return t.observe(v.current),()=>t.disconnect()}),[e,p,x]);const k="custom"===_?{}:"fade"===_?{opacity:I?1:0,transition:`opacity ${f}`}:"scale"===_?{transform:I?"scale(1)":"scale(1.05)",opacity:I?1:0,transition:`transform ${f}, opacity ${f}`}:{filter:I?"none":`blur(${g})`,transition:`filter ${f}`};return(0,jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment,{children:[!I&&h,!w&&(0,jsx_runtime_1.jsx)("img",{ref:v,src:z??"",alt:t,title:u,className:i,width:a,height:o,loading:"lazy",id:s?.toString(),onLoad:()=>{S(!0),b?.()},onError:()=>{y&&z!==y?j(y):L(!0)},srcSet:r,sizes:n,style:{backgroundColor:!I&&m?m:void 0,backgroundSize:"cover",backgroundPosition:"center",...l?{viewTransitionName:l}:{},...k,...d},...c})]})};exports.ImageLazy=ImageLazy,exports.default=ImageLazy;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ImageLazy=void 0;const jsx_runtime_1=require("react/jsx-runtime"),react_1=require("react"),ImageLazy=({src:e,alt:t,srcSet:r,sizes:a,width:o,height:n,className:i,id:s,extraData:c,title:l,useTitleFromAlt:u=!1,viewTransitionName:d,style:m,backgroundColor:f,animationDuration:g="0.9s",blurAmount:y="20px",fallbackSrc:p,threshold:_=.5,transitionType:b="blur",onLoadComplete:v,visibleByDefault:h=!1,loadingComponent:x})=>{const w=(0,react_1.useRef)(null),[L,z]=(0,react_1.useState)(h?e:null),[I,S]=(0,react_1.useState)(!1),[j,k]=(0,react_1.useState)(!1);(0,react_1.useEffect)((()=>{"undefined"!=typeof window&&window?.location?.hostname?.includes("localhost")&&(t||console.warn("[ImageLazy] ⚠️ Se recomienda definir el atributo 'alt' por accesibilidad."),e||console.warn("[ImageLazy] ⚠️ La prop 'src' está vacía o nula. La imagen no se cargará."))}),[t,e]),(0,react_1.useLayoutEffect)((()=>{if("undefined"==typeof window||h)return;if("undefined"==typeof IntersectionObserver||!w.current)return;const t=new IntersectionObserver((r=>{r.forEach((r=>{r.isIntersecting&&(z(e),t.unobserve(r.target))}))}),{root:null,rootMargin:"0px",threshold:_});return t.observe(w.current),()=>t.disconnect()}),[e,_,h]);const $="custom"===b?{}:"fade"===b?{opacity:I?1:0,transition:`opacity ${g}`}:"scale"===b?{transform:I?"scale(1)":"scale(1.05)",opacity:I?1:0,transition:`transform ${g}, opacity ${g}`}:{filter:I?"none":`blur(${y})`,transition:`filter ${g}`};return(0,jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment,{children:[!I&&x,!j&&(0,jsx_runtime_1.jsx)("img",{ref:w,src:L??void 0,alt:t,title:l??(u?t:void 0),className:i,width:o,height:n,loading:"lazy",id:s?.toString(),onLoad:()=>{S(!0),v?.()},onError:()=>{p&&L!==p?z(p):k(!0)},srcSet:L?r:void 0,sizes:L?a:void 0,style:{backgroundColor:!I&&f?f:void 0,backgroundSize:"cover",backgroundPosition:"center",...d?{viewTransitionName:d}:{},...$,...m},...c})]})};exports.ImageLazy=ImageLazy,exports.default=ImageLazy;
@@ -1,11 +1,23 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, viewTransitionName, style, backgroundColor, animationDuration = "0.9s", blurAmount = "20px", fallbackSrc, threshold = 0.5, transitionType = "blur", onLoadComplete, visibleByDefault = false, loadingComponent, }) => {
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, useTitleFromAlt = false, viewTransitionName, style, backgroundColor, animationDuration = "0.9s", blurAmount = "20px", fallbackSrc, threshold = 0.5, transitionType = "blur", onLoadComplete, visibleByDefault = false, loadingComponent, }) => {
4
4
  const imageRef = useRef(null);
5
5
  const [realSrc, setRealSrc] = useState(visibleByDefault ? src : null);
6
6
  const [loaded, setLoaded] = useState(false);
7
7
  const [hasError, setHasError] = useState(false);
8
8
  useEffect(() => {
9
+ const isDev = typeof window !== "undefined" &&
10
+ window?.location?.hostname?.includes("localhost");
11
+ if (isDev) {
12
+ if (!alt) {
13
+ console.warn("[ImageLazy] ⚠️ Se recomienda definir el atributo 'alt' por accesibilidad.");
14
+ }
15
+ if (!src) {
16
+ console.warn("[ImageLazy] ⚠️ La prop 'src' está vacía o nula. La imagen no se cargará.");
17
+ }
18
+ }
19
+ }, [alt, src]);
20
+ useLayoutEffect(() => {
9
21
  if (typeof window === "undefined" || visibleByDefault)
10
22
  return;
11
23
  if (typeof IntersectionObserver === "undefined" || !imageRef.current)
@@ -51,7 +63,7 @@ const ImageLazy = ({ src, alt, srcSet, sizes, width, height, className, id, extr
51
63
  filter: loaded ? "none" : `blur(${blurAmount})`,
52
64
  transition: `filter ${animationDuration}`,
53
65
  };
54
- return (_jsxs(_Fragment, { children: [!loaded && loadingComponent, !hasError && (_jsx("img", { ref: imageRef, src: realSrc ?? "", alt: alt, title: title, className: className, width: width, height: height, loading: "lazy", id: id?.toString(), onLoad: handleLoad, onError: handleError, srcSet: srcSet, sizes: sizes, style: {
66
+ return (_jsxs(_Fragment, { children: [!loaded && loadingComponent, !hasError && (_jsx("img", { ref: imageRef, src: realSrc ?? undefined, alt: alt, title: title ?? (useTitleFromAlt ? alt : undefined), className: className, width: width, height: height, loading: "lazy", id: id?.toString(), onLoad: handleLoad, onError: handleError, srcSet: realSrc ? srcSet : undefined, sizes: realSrc ? sizes : undefined, style: {
55
67
  backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined,
56
68
  backgroundSize: "cover",
57
69
  backgroundPosition: "center",
@@ -1 +1 @@
1
- import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useRef,useState}from"react";const ImageLazy=({src:e,alt:t,srcSet:r,sizes:n,width:o,height:s,className:a,id:i,extraData:c,title:l,viewTransitionName:u,style:d,backgroundColor:f,animationDuration:m="0.9s",blurAmount:g="20px",fallbackSrc:p,threshold:y=.5,transitionType:b="blur",onLoadComplete:h,visibleByDefault:x=!1,loadingComponent:v})=>{const S=useRef(null),[j,w]=useState(x?e:null),[z,I]=useState(!1),[_,k]=useState(!1);useEffect((()=>{if("undefined"==typeof window||x)return;if("undefined"==typeof IntersectionObserver||!S.current)return;const t=new IntersectionObserver((r=>{r.forEach((r=>{r.isIntersecting&&(w(e),t.unobserve(r.target))}))}),{root:null,rootMargin:"0px",threshold:y});return t.observe(S.current),()=>t.disconnect()}),[e,y,x]);const L="custom"===b?{}:"fade"===b?{opacity:z?1:0,transition:`opacity ${m}`}:"scale"===b?{transform:z?"scale(1)":"scale(1.05)",opacity:z?1:0,transition:`transform ${m}, opacity ${m}`}:{filter:z?"none":`blur(${g})`,transition:`filter ${m}`};return _jsxs(_Fragment,{children:[!z&&v,!_&&_jsx("img",{ref:S,src:j??"",alt:t,title:l,className:a,width:o,height:s,loading:"lazy",id:i?.toString(),onLoad:()=>{I(!0),h?.()},onError:()=>{p&&j!==p?w(p):k(!0)},srcSet:r,sizes:n,style:{backgroundColor:!z&&f?f:void 0,backgroundSize:"cover",backgroundPosition:"center",...u?{viewTransitionName:u}:{},...L,...d},...c})]})};export default ImageLazy;export{ImageLazy};
1
+ import{jsx as _jsx,Fragment as _Fragment,jsxs as _jsxs}from"react/jsx-runtime";import{useEffect,useLayoutEffect,useRef,useState}from"react";const ImageLazy=({src:e,alt:t,srcSet:o,sizes:a,width:r,height:n,className:s,id:i,extraData:c,title:l,useTitleFromAlt:u=!1,viewTransitionName:d,style:f,backgroundColor:m,animationDuration:g="0.9s",blurAmount:p="20px",fallbackSrc:y,threshold:b=.5,transitionType:h="blur",onLoadComplete:v,visibleByDefault:w=!1,loadingComponent:x})=>{const L=useRef(null),[S,z]=useState(w?e:null),[I,j]=useState(!1),[E,_]=useState(!1);useEffect((()=>{"undefined"!=typeof window&&window?.location?.hostname?.includes("localhost")&&(t||console.warn("[ImageLazy] ⚠️ Se recomienda definir el atributo 'alt' por accesibilidad."),e||console.warn("[ImageLazy] ⚠️ La prop 'src' está vacía o nula. La imagen no se cargará."))}),[t,e]),useLayoutEffect((()=>{if("undefined"==typeof window||w)return;if("undefined"==typeof IntersectionObserver||!L.current)return;const t=new IntersectionObserver((o=>{o.forEach((o=>{o.isIntersecting&&(z(e),t.unobserve(o.target))}))}),{root:null,rootMargin:"0px",threshold:b});return t.observe(L.current),()=>t.disconnect()}),[e,b,w]);const k="custom"===h?{}:"fade"===h?{opacity:I?1:0,transition:`opacity ${g}`}:"scale"===h?{transform:I?"scale(1)":"scale(1.05)",opacity:I?1:0,transition:`transform ${g}, opacity ${g}`}:{filter:I?"none":`blur(${p})`,transition:`filter ${g}`};return _jsxs(_Fragment,{children:[!I&&x,!E&&_jsx("img",{ref:L,src:S??void 0,alt:t,title:l??(u?t:void 0),className:s,width:r,height:n,loading:"lazy",id:i?.toString(),onLoad:()=>{j(!0),v?.()},onError:()=>{y&&S!==y?z(y):_(!0)},srcSet:S?o:void 0,sizes:S?a:void 0,style:{backgroundColor:!I&&m?m:void 0,backgroundSize:"cover",backgroundPosition:"center",...d?{viewTransitionName:d}:{},...k,...f},...c})]})};export default ImageLazy;export{ImageLazy};
@@ -8,6 +8,7 @@ export type ImagesLazy = {
8
8
  id?: number | string;
9
9
  className?: string;
10
10
  title?: string;
11
+ useTitleFromAlt?: boolean;
11
12
  extraData?: React.ImgHTMLAttributes<HTMLImageElement>;
12
13
  viewTransitionName?: string;
13
14
  style?: React.CSSProperties;
@@ -21,6 +22,6 @@ export type ImagesLazy = {
21
22
  visibleByDefault?: boolean;
22
23
  loadingComponent?: React.ReactNode;
23
24
  };
24
- declare const ImageLazy: ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, viewTransitionName, style, backgroundColor, animationDuration, blurAmount, fallbackSrc, threshold, transitionType, onLoadComplete, visibleByDefault, loadingComponent, }: ImagesLazy) => import("react/jsx-runtime").JSX.Element;
25
+ declare const ImageLazy: ({ src, alt, srcSet, sizes, width, height, className, id, extraData, title, useTitleFromAlt, viewTransitionName, style, backgroundColor, animationDuration, blurAmount, fallbackSrc, threshold, transitionType, onLoadComplete, visibleByDefault, loadingComponent, }: ImagesLazy) => import("react/jsx-runtime").JSX.Element;
25
26
  export default ImageLazy;
26
27
  export { ImageLazy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lazy-img-observer",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A React component for lazy loading images with Intersection Observer",
5
5
  "main": "dist/cjs/ImageLazy.min.js",
6
6
  "module": "dist/esm/ImageLazy.min.js",