react-lazy-img-observer 1.1.1 → 1.4.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/LICENCE.md CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 Percy Saul Rondan Chuzon
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the “Software”), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Percy Saul Rondan Chuzon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
- # 📷 ImageLazy
1
+ # 📷 ImageLazy (v1.4.0)
2
2
 
3
- **ImageLazy** is a lightweight React component for lazy loading images using the Intersection Observer API. It delays the loading of off-screen images until they appear in the viewport, helping optimize performance and reduce initial load times.
3
+ **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
4
 
5
- ![npm](https://img.shields.io/npm/v/react-lazy-img-observer.svg)
6
- ![license](https://img.shields.io/npm/l/react-lazy-img-observer.svg)
5
+ **ImageLazy** es un componente liviano y personalizable de React para cargar imágenes perezosamente (lazy load) usando la API de Intersection Observer. Incluye manejo de errores, efectos visuales, y soporte para spinners personalizados y transiciones — ideal para aplicaciones optimizadas.
7
6
 
8
7
  ---
9
8
 
10
- ## 📦 Installation
9
+ ## 📦 Installation / Instalación
11
10
 
12
11
  ```bash
13
12
  npm install react-lazy-img-observer
@@ -15,98 +14,199 @@ npm install react-lazy-img-observer
15
14
 
16
15
  ---
17
16
 
18
- ## 🚀 Usage
17
+ ## 🚀 Usage / Uso Básico
19
18
 
20
- ### Basic example (JSX or TSX)
19
+ ```tsx
20
+ import ImageLazy from "react-lazy-img-observer";
21
+
22
+ function App() {
23
+ return (
24
+ <ImageLazy
25
+ src="https://example.com/photo.jpg"
26
+ alt="Example image"
27
+ width={400}
28
+ height={300}
29
+ />
30
+ );
31
+ }
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 🎨 Advanced Example with Spinner and Fallback / Ejemplo Avanzado con Spinner y Fallback
21
37
 
22
38
  ```tsx
23
39
  import ImageLazy from "react-lazy-img-observer";
24
40
 
25
- const Example = () => (
26
- <ImageLazy
27
- src="https://example.com/image.jpg"
28
- alt="Description of the image"
29
- width={600}
30
- height={400}
31
- className="custom-class"
32
- id="image-1"
33
- title="Image title"
34
- extraData={{ "data-custom": "value" }}
35
- />
36
- );
41
+ const Spinner = () => <div className="spinner">Loading...</div>;
42
+
43
+ function GalleryImage() {
44
+ return (
45
+ <ImageLazy
46
+ src="/img/cat.webp"
47
+ alt="Cute Cat"
48
+ width={300}
49
+ height={200}
50
+ fallbackSrc="/img/fallback.webp"
51
+ backgroundColor="#eee"
52
+ animationDuration="1s"
53
+ blurAmount="8px"
54
+ transitionType="fade"
55
+ loadingComponent={<Spinner />}
56
+ />
57
+ );
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 💫 Custom Transition with Your Own CSS / Transiciones Personalizadas con CSS Propio
64
+
65
+ ```tsx
66
+ <ImageLazy
67
+ src="/img/circle.jpg"
68
+ alt="Spinning Image"
69
+ transitionType="custom"
70
+ className="my-custom-transition"
71
+ />
72
+ ```
73
+
74
+ **CSS**
75
+
76
+ ```css
77
+ .my-custom-transition {
78
+ opacity: 0;
79
+ transform: rotate(10deg) scale(0.95);
80
+ transition: all 0.8s ease-in-out;
81
+ }
82
+
83
+ .my-custom-transition.loaded {
84
+ opacity: 1;
85
+ transform: rotate(0deg) scale(1);
86
+ }
37
87
  ```
38
88
 
39
89
  ---
40
90
 
41
91
  ## 🧩 Props
42
92
 
43
- | Prop | Type | Required | Description |
44
- |-------------|---------------------------------------------|----------|-------------------------------------------------------|
45
- | `src` | `string` | | The source URL of the image. |
46
- | `alt` | `string` | | The alt text for accessibility. |
47
- | `width` | `number` | | The width of the image. |
48
- | `height` | `number` | | The height of the image. |
49
- | `id` | `string \| number` | | Optional ID for the image element. |
50
- | `className` | `string` | | CSS class for custom styling. |
51
- | `title` | `string` | | Tooltip text shown on hover. |
52
- | `extraData` | `React.ImgHTMLAttributes<HTMLImageElement>` | | Any extra HTML attributes (e.g., `data-*`). |
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 |
53
115
 
54
116
  ---
55
117
 
56
- ## 📚 How It Works
118
+ ## 🔧 How it works / ¿Cómo funciona?
57
119
 
58
- The component uses the [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) API to detect when the image enters the viewport. Once at least **50%** of the image is visible, the real `src` is assigned to the image, triggering the browser to load it.
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.
59
123
 
60
124
  ---
61
125
 
62
- ## ⚙️ Intersection Observer Configuration
126
+ ## 🔁 Effects / Efectos Visuales
63
127
 
64
- - `root`: `null` (default viewport)
65
- - `rootMargin`: `"0px"`
66
- - `threshold`: `0.5` (50% visibility required)
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) |
67
134
 
68
135
  ---
69
136
 
70
- ## 🎨 Styling
137
+ ## 🌐 SSR Compatible / Compatible con SSR
138
+
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.
140
+
141
+ ImageLazy es seguro para entornos con renderizado del lado del servidor como Next.js. Detecta si `window` está disponible antes de usar IntersectionObserver.
142
+
143
+ ---
144
+
145
+ ## 🖼 Spinner / Carga Personalizada
146
+
147
+ You can use `loadingComponent` to display a spinner while the image loads:
71
148
 
72
- The image starts blurred and transitions smoothly once it’s loaded. You can override or enhance this with your own styles:
149
+ ```tsx
150
+ <ImageLazy
151
+ src="/img/pic.webp"
152
+ alt="Custom Spinner"
153
+ loadingComponent={<div className="my-spinner" />}
154
+ />
155
+ ```
73
156
 
74
157
  ```css
75
- .custom-class {
76
- filter: blur(20px);
77
- transition: filter 0.9s ease;
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;
78
165
  }
79
-
80
- .custom-class.loaded {
81
- filter: none;
166
+ @keyframes spin {
167
+ to { transform: rotate(360deg); }
82
168
  }
83
169
  ```
84
170
 
85
- You may also override the `style` prop directly if preferred.
171
+ ---
172
+
173
+ ## 📊 Comparison with react-lazy-load-image-component / Comparación con react-lazy-load-image-component
174
+
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 |
86
186
 
87
187
  ---
88
188
 
89
- ## 📄 License
189
+ ## 📄 License / Licencia
90
190
 
91
191
  [ISC License](./LICENSE)
92
192
 
93
193
  ---
94
194
 
95
- ## 🤝 Contributing
195
+ ## 👤 Author / Autor
96
196
 
97
- Contributions are welcome!
98
- Feel free to [open an issue](https://github.com/perch33/react-lazy-img-observer/issues) or submit a pull request.
197
+ **Percy Chuzon**\
198
+ 📧 [contacto@percychuzon.com](mailto\:contacto@percychuzon.com)\
199
+ 🌐 [https://percychuzon.com](https://percychuzon.com)
99
200
 
100
201
  ---
101
202
 
102
- ## 👤 Author
203
+ ## 💡 Tip
103
204
 
104
- **Percy Chuzon**
105
- 📧 contacto@percychuzon.com
106
- 🌐 [https://percychuzon.com](https://percychuzon.com)
205
+ If you want more control, just set `transitionType="custom"` and style the image with CSS. It’s that simple.
206
+
207
+ Si deseas más control, usa `transitionType="custom"` y aplica tus estilos con CSS. Así de simple.
107
208
 
108
209
  ---
109
210
 
110
- ## 🙏 Acknowledgments
211
+ Happy loading! 🎉 / ¡Carga feliz! 🎉
111
212
 
112
- Thanks to the React community and the MDN docs for inspiration and guidance.
@@ -3,38 +3,65 @@ 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, width, height, className, id, extraData, title, }) => {
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, }) => {
7
7
  const imageRef = (0, react_1.useRef)(null);
8
- const [isIntersecting, setIsIntersecting] = (0, react_1.useState)(false);
8
+ const [realSrc, setRealSrc] = (0, react_1.useState)(visibleByDefault ? src : null);
9
+ const [loaded, setLoaded] = (0, react_1.useState)(false);
10
+ const [hasError, setHasError] = (0, react_1.useState)(false);
9
11
  (0, react_1.useEffect)(() => {
10
- if (typeof IntersectionObserver === "undefined" ||
11
- !imageRef.current) {
12
+ if (typeof window === "undefined" || visibleByDefault)
13
+ return;
14
+ if (typeof IntersectionObserver === "undefined" || !imageRef.current)
12
15
  return;
13
- }
14
16
  const observer = new IntersectionObserver((entries) => {
15
17
  entries.forEach((entry) => {
16
18
  if (entry.isIntersecting) {
17
- setIsIntersecting(true);
18
- if (imageRef.current) {
19
- imageRef.current.src = imageRef.current.dataset.src || "";
20
- }
19
+ setRealSrc(src);
21
20
  observer.unobserve(entry.target);
22
21
  }
23
22
  });
24
- }, {
25
- root: null,
26
- rootMargin: "0px",
27
- threshold: 0.5,
28
- });
23
+ }, { root: null, rootMargin: "0px", threshold });
29
24
  observer.observe(imageRef.current);
30
- return () => {
31
- observer.disconnect();
32
- };
33
- }, [src]);
34
- return ((0, jsx_runtime_1.jsx)("img", { ref: imageRef, "data-src": src, alt: alt, title: title, className: className, width: width, height: height, loading: "lazy", id: id?.toString(), style: {
35
- filter: isIntersecting ? "none" : "blur(20px)",
36
- transition: "filter 0.9s",
37
- }, ...extraData }));
25
+ return () => observer.disconnect();
26
+ }, [src, threshold, visibleByDefault]);
27
+ const handleLoad = () => {
28
+ setLoaded(true);
29
+ onLoadComplete?.();
30
+ };
31
+ const handleError = () => {
32
+ if (fallbackSrc && realSrc !== fallbackSrc) {
33
+ setRealSrc(fallbackSrc);
34
+ }
35
+ else {
36
+ setHasError(true);
37
+ }
38
+ };
39
+ const isCustom = transitionType === "custom";
40
+ const transitionStyles = isCustom
41
+ ? {}
42
+ : transitionType === "fade"
43
+ ? {
44
+ opacity: loaded ? 1 : 0,
45
+ transition: `opacity ${animationDuration}`,
46
+ }
47
+ : transitionType === "scale"
48
+ ? {
49
+ transform: loaded ? "scale(1)" : "scale(1.05)",
50
+ opacity: loaded ? 1 : 0,
51
+ transition: `transform ${animationDuration}, opacity ${animationDuration}`,
52
+ }
53
+ : {
54
+ filter: loaded ? "none" : `blur(${blurAmount})`,
55
+ transition: `filter ${animationDuration}`,
56
+ };
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: {
58
+ backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined,
59
+ backgroundSize: "cover",
60
+ backgroundPosition: "center",
61
+ ...(viewTransitionName ? { viewTransitionName } : {}),
62
+ ...transitionStyles,
63
+ ...style,
64
+ }, ...extraData }))] }));
38
65
  };
39
66
  exports.ImageLazy = ImageLazy;
40
67
  exports.default = ImageLazy;
@@ -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,width:r,height:s,className:a,id:n,extraData:i,title:c})=>{const o=(0,react_1.useRef)(null),[u,l]=(0,react_1.useState)(!1);return(0,react_1.useEffect)((()=>{if("undefined"==typeof IntersectionObserver||!o.current)return;const e=new IntersectionObserver((t=>{t.forEach((t=>{t.isIntersecting&&(l(!0),o.current&&(o.current.src=o.current.dataset.src||""),e.unobserve(t.target))}))}),{root:null,rootMargin:"0px",threshold:.5});return e.observe(o.current),()=>{e.disconnect()}}),[e]),(0,jsx_runtime_1.jsx)("img",{ref:o,"data-src":e,alt:t,title:c,className:a,width:r,height:s,loading:"lazy",id:n?.toString(),style:{filter:u?"none":"blur(20px)",transition:"filter 0.9s"},...i})};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: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,37 +1,64 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
- const ImageLazy = ({ src, alt, width, height, className, id, extraData, title, }) => {
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, }) => {
4
4
  const imageRef = useRef(null);
5
- const [isIntersecting, setIsIntersecting] = useState(false);
5
+ const [realSrc, setRealSrc] = useState(visibleByDefault ? src : null);
6
+ const [loaded, setLoaded] = useState(false);
7
+ const [hasError, setHasError] = useState(false);
6
8
  useEffect(() => {
7
- if (typeof IntersectionObserver === "undefined" ||
8
- !imageRef.current) {
9
+ if (typeof window === "undefined" || visibleByDefault)
10
+ return;
11
+ if (typeof IntersectionObserver === "undefined" || !imageRef.current)
9
12
  return;
10
- }
11
13
  const observer = new IntersectionObserver((entries) => {
12
14
  entries.forEach((entry) => {
13
15
  if (entry.isIntersecting) {
14
- setIsIntersecting(true);
15
- if (imageRef.current) {
16
- imageRef.current.src = imageRef.current.dataset.src || "";
17
- }
16
+ setRealSrc(src);
18
17
  observer.unobserve(entry.target);
19
18
  }
20
19
  });
21
- }, {
22
- root: null,
23
- rootMargin: "0px",
24
- threshold: 0.5,
25
- });
20
+ }, { root: null, rootMargin: "0px", threshold });
26
21
  observer.observe(imageRef.current);
27
- return () => {
28
- observer.disconnect();
29
- };
30
- }, [src]);
31
- return (_jsx("img", { ref: imageRef, "data-src": src, alt: alt, title: title, className: className, width: width, height: height, loading: "lazy", id: id?.toString(), style: {
32
- filter: isIntersecting ? "none" : "blur(20px)",
33
- transition: "filter 0.9s",
34
- }, ...extraData }));
22
+ return () => observer.disconnect();
23
+ }, [src, threshold, visibleByDefault]);
24
+ const handleLoad = () => {
25
+ setLoaded(true);
26
+ onLoadComplete?.();
27
+ };
28
+ const handleError = () => {
29
+ if (fallbackSrc && realSrc !== fallbackSrc) {
30
+ setRealSrc(fallbackSrc);
31
+ }
32
+ else {
33
+ setHasError(true);
34
+ }
35
+ };
36
+ const isCustom = transitionType === "custom";
37
+ const transitionStyles = isCustom
38
+ ? {}
39
+ : transitionType === "fade"
40
+ ? {
41
+ opacity: loaded ? 1 : 0,
42
+ transition: `opacity ${animationDuration}`,
43
+ }
44
+ : transitionType === "scale"
45
+ ? {
46
+ transform: loaded ? "scale(1)" : "scale(1.05)",
47
+ opacity: loaded ? 1 : 0,
48
+ transition: `transform ${animationDuration}, opacity ${animationDuration}`,
49
+ }
50
+ : {
51
+ filter: loaded ? "none" : `blur(${blurAmount})`,
52
+ transition: `filter ${animationDuration}`,
53
+ };
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: {
55
+ backgroundColor: !loaded && backgroundColor ? backgroundColor : undefined,
56
+ backgroundSize: "cover",
57
+ backgroundPosition: "center",
58
+ ...(viewTransitionName ? { viewTransitionName } : {}),
59
+ ...transitionStyles,
60
+ ...style,
61
+ }, ...extraData }))] }));
35
62
  };
36
63
  export default ImageLazy;
37
64
  export { ImageLazy };
@@ -1 +1 @@
1
- import{jsx as _jsx}from"react/jsx-runtime";import{useEffect,useRef,useState}from"react";const ImageLazy=({src:e,alt:t,width:r,height:s,className:n,id:a,extraData:i,title:o})=>{const c=useRef(null),[u,l]=useState(!1);return useEffect((()=>{if("undefined"==typeof IntersectionObserver||!c.current)return;const e=new IntersectionObserver((t=>{t.forEach((t=>{t.isIntersecting&&(l(!0),c.current&&(c.current.src=c.current.dataset.src||""),e.unobserve(t.target))}))}),{root:null,rootMargin:"0px",threshold:.5});return e.observe(c.current),()=>{e.disconnect()}}),[e]),_jsx("img",{ref:c,"data-src":e,alt:t,title:o,className:n,width:r,height:s,loading:"lazy",id:a?.toString(),style:{filter:u?"none":"blur(20px)",transition:"filter 0.9s"},...i})};export default ImageLazy;export{ImageLazy};
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,13 +1,26 @@
1
- type ImagesLazy = {
1
+ export type ImagesLazy = {
2
2
  src: string;
3
3
  alt: string;
4
+ srcSet?: string;
5
+ sizes?: string;
4
6
  width?: number;
5
7
  height?: number;
6
8
  id?: number | string;
7
9
  className?: string;
8
10
  title?: string;
9
11
  extraData?: React.ImgHTMLAttributes<HTMLImageElement>;
12
+ viewTransitionName?: string;
13
+ style?: React.CSSProperties;
14
+ backgroundColor?: string;
15
+ animationDuration?: string;
16
+ blurAmount?: string;
17
+ fallbackSrc?: string;
18
+ threshold?: number;
19
+ transitionType?: "blur" | "fade" | "scale" | "custom";
20
+ onLoadComplete?: () => void;
21
+ visibleByDefault?: boolean;
22
+ loadingComponent?: React.ReactNode;
10
23
  };
11
- declare const ImageLazy: ({ src, alt, width, height, className, id, extraData, title, }: ImagesLazy) => import("react/jsx-runtime").JSX.Element;
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;
12
25
  export default ImageLazy;
13
26
  export { ImageLazy };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lazy-img-observer",
3
- "version": "1.1.1",
3
+ "version": "1.4.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",
@@ -37,6 +37,7 @@
37
37
  "responsive-images",
38
38
  "web-optimization",
39
39
  "accessibility",
40
+ "React Lazy Load Image Component",
40
41
  "a11y",
41
42
  "react-hooks",
42
43
  "viewport"