@duckafire/lazy-loading-js 2.0.2-1
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/LICENSE +19 -0
- package/README.md +157 -0
- package/package.json +30 -0
- package/src/LazyLoadingImage.js +177 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Zlib License
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2025 DuckAfire <duckafire.github.io/nest>
|
|
4
|
+
|
|
5
|
+
This software is provided 'as-is', without any express or implied
|
|
6
|
+
warranty. In no event will the authors be held liable for any damages
|
|
7
|
+
arising from the use of this software.
|
|
8
|
+
|
|
9
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
10
|
+
including commercial applications, and to alter it and redistribute it
|
|
11
|
+
freely, subject to the following restrictions:
|
|
12
|
+
|
|
13
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
14
|
+
claim that you wrote the original software. If you use this software
|
|
15
|
+
in a product, an acknowledgment in the product documentation would be
|
|
16
|
+
appreciated but is not required.
|
|
17
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
18
|
+
misrepresented as being the original software.
|
|
19
|
+
3. This notice may not be removed or altered from any source distribution.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<a href="https://github.com/sponsors/duckafire" title="GitHub Sponsors"><img align="right" src="https://img.shields.io/badge/Buy%20me%20a%20coffee-E5DB2F?&logo=buy-me-a-coffee&style=flat-square&logoColor=000"></a>
|
|
2
|
+
|
|
3
|
+
## Lazy loading
|
|
4
|
+
|
|
5
|
+
Small and simple JS API, developed to easily the application of the Lazy Loading
|
|
6
|
+
tecnique to images, in order to optimize website pages.
|
|
7
|
+
|
|
8
|
+
### Index
|
|
9
|
+
|
|
10
|
+
* [Usage](#usage)
|
|
11
|
+
* [Import the API](#import-the-api)
|
|
12
|
+
* [Set the properties](#set-the-properties)
|
|
13
|
+
* [Choose a class](#choose-a-class)
|
|
14
|
+
* [Start the API](#start-the-api)
|
|
15
|
+
* [API Methods](#api-methods)
|
|
16
|
+
* [Constructor](#constructor)
|
|
17
|
+
* [`start`](#start)
|
|
18
|
+
* [`disconnectFromObserver`](#disconnectfromobserver)
|
|
19
|
+
* [Incompatibility browsers](#incompatibility-browsers)
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
|
|
23
|
+
#### Import the API
|
|
24
|
+
|
|
25
|
+
Import the API in your HTML code:
|
|
26
|
+
|
|
27
|
+
``` html
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/@duckafire/lazy-loading-js@2.0.2-1/lib/LazyLoadingImage.min.js"></script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
[source]: https://github.com/duckafire/lazy-loading-js/tree/main/src/ "Lazy loading API source code"
|
|
32
|
+
|
|
33
|
+
> Or download the file(s), from [here][source].
|
|
34
|
+
|
|
35
|
+
#### Set the properties
|
|
36
|
+
|
|
37
|
+
Choose which `img` will use the API, then set the properties below:
|
|
38
|
+
|
|
39
|
+
* `data-src`: contains the original image - high quality. It will be used when
|
|
40
|
+
the `img` is visible.
|
|
41
|
+
|
|
42
|
+
* `data-placeholder`: contains an low quality image that will be used when image
|
|
43
|
+
is not visible. Used for avoid the breaking of the page layout.
|
|
44
|
+
|
|
45
|
+
``` html
|
|
46
|
+
<img data-src="./foo.png" data-placeholder="./foo-placeholder.png"/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Choose a class
|
|
50
|
+
|
|
51
|
+
Attribute a specific class to all elements whose visibility will be controlled
|
|
52
|
+
by the API.
|
|
53
|
+
|
|
54
|
+
``` html
|
|
55
|
+
<img class="foo" data-src="./foo.png" data-placeholder="./foo-placeholder.png"/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Start the API
|
|
59
|
+
|
|
60
|
+
Instance a object using the class (choosed in the last step) as first argument and enjoy!
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
``` html
|
|
64
|
+
<img class="foo" data-src="./foo.png" data-placeholder="./foo-placeholder.png"/>
|
|
65
|
+
|
|
66
|
+
<script>
|
|
67
|
+
new LazyLoadingImage(".foo");
|
|
68
|
+
|
|
69
|
+
// OR:
|
|
70
|
+
// const api_unit = new LazyLoadingImage(".foo");
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### API methods
|
|
75
|
+
|
|
76
|
+
#### Constructor
|
|
77
|
+
|
|
78
|
+
Instance the object and it validate the HTML elements that were obtained by
|
|
79
|
+
the query specificed.
|
|
80
|
+
|
|
81
|
+
| Parameters | Type | Default | Description |
|
|
82
|
+
| :-- | :-- | :-- | :-- |
|
|
83
|
+
| query | string | undefined | CSS query to get specific HTML elements. |
|
|
84
|
+
| startWarning | boolean | false | it allows console warning and error messages. |
|
|
85
|
+
| awaitToStart | boolean | false | set that the API will be started manually. |
|
|
86
|
+
|
|
87
|
+
> [!IMPORTANT]
|
|
88
|
+
> Parameters that have `undefined` as default value they are mandatory.
|
|
89
|
+
|
|
90
|
+
##### Exceptions
|
|
91
|
+
|
|
92
|
+
* None element was found with the specified query.
|
|
93
|
+
* Element type (`img`, `div`, ...) invalid - only `img` is allow.
|
|
94
|
+
|
|
95
|
+
##### Console
|
|
96
|
+
|
|
97
|
+
###### Warnings
|
|
98
|
+
|
|
99
|
+
* All [`start`](#start) warnings.
|
|
100
|
+
|
|
101
|
+
###### Errors
|
|
102
|
+
|
|
103
|
+
* All [`start`](#start) errors.
|
|
104
|
+
|
|
105
|
+
#### `start`
|
|
106
|
+
|
|
107
|
+
Used to start the API manually.
|
|
108
|
+
|
|
109
|
+
| Parameters | Type | Default | Description |
|
|
110
|
+
| :-- | :-- | :-- | :-- |
|
|
111
|
+
| warning | boolean | false | it allows console warning and error messages. |
|
|
112
|
+
|
|
113
|
+
##### Exceptions
|
|
114
|
+
|
|
115
|
+
* *None.*
|
|
116
|
+
|
|
117
|
+
##### Console
|
|
118
|
+
|
|
119
|
+
###### Warnings
|
|
120
|
+
|
|
121
|
+
* The API already was started.
|
|
122
|
+
|
|
123
|
+
###### Errors
|
|
124
|
+
|
|
125
|
+
* The browser do not have support to *Intersection Observer API* (API from JS
|
|
126
|
+
standard, use as base to this project).
|
|
127
|
+
|
|
128
|
+
#### `disconnectFromObserver`
|
|
129
|
+
|
|
130
|
+
Used to end API work.
|
|
131
|
+
|
|
132
|
+
> [!NOTE]
|
|
133
|
+
> This method is declared inside `#connectToObserver` (*private* method).
|
|
134
|
+
|
|
135
|
+
| Parameters | Type | Default | Description |
|
|
136
|
+
| :-- | :-- | :-- | :-- |
|
|
137
|
+
| *None* | | | |
|
|
138
|
+
|
|
139
|
+
##### Exceptions
|
|
140
|
+
|
|
141
|
+
* *None.*
|
|
142
|
+
|
|
143
|
+
##### Console
|
|
144
|
+
|
|
145
|
+
###### Warnings
|
|
146
|
+
|
|
147
|
+
* *None.*
|
|
148
|
+
|
|
149
|
+
###### Errors
|
|
150
|
+
|
|
151
|
+
* *None.*
|
|
152
|
+
|
|
153
|
+
### Incompatibility browsers
|
|
154
|
+
|
|
155
|
+
This API is based in other API, from JS Standard, named *Intersection Observer
|
|
156
|
+
API*. If the user's browser do not have support to this Standard API, the
|
|
157
|
+
`data-src` image will load without lazy loading.
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duckafire/lazy-loading-js",
|
|
3
|
+
"author": "duckafire",
|
|
4
|
+
"version": "2.0.2-1",
|
|
5
|
+
"license": "Zlib",
|
|
6
|
+
"description": "Simple lazy loading algorithm for websites front-end.",
|
|
7
|
+
"keywords": ["client", "html", "images", "web", "front-end", "optimization", "lazy", "loading", "lazy-loading"],
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"terser": "^5.43.1"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/duckafire/lazy-loading-js.git"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"compress": "for file in ./src/*; do npx terser \"$file\" -o \"./lib/$(basename -- \"${file%.js}\").min.js\" --compress --mangle; done"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"README.md",
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"custom-fields": {
|
|
24
|
+
"cloud-repostories": [
|
|
25
|
+
"https://github.com/duckafire/lazy-loading-js.git",
|
|
26
|
+
"https://gitlab.com/duckafire/lazy-loading-js.git",
|
|
27
|
+
"https://npmjs.com/package/@duckafire/lazy-loading-js.git"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Zlib License
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2025 DuckAfire <duckafire.github.io/nest>
|
|
5
|
+
|
|
6
|
+
This software is provided 'as-is', without any express or implied
|
|
7
|
+
warranty. In no event will the authors be held liable for any damages
|
|
8
|
+
arising from the use of this software.
|
|
9
|
+
|
|
10
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
11
|
+
including commercial applications, and to alter it and redistribute it
|
|
12
|
+
freely, subject to the following restrictions:
|
|
13
|
+
|
|
14
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
15
|
+
claim that you wrote the original software. If you use this software
|
|
16
|
+
in a product, an acknowledgment in the product documentation would be
|
|
17
|
+
appreciated but is not required.
|
|
18
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
19
|
+
misrepresented as being the original software.
|
|
20
|
+
3. This notice may not be removed or altered from any source distribution.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
class LazyLoadingImage {
|
|
24
|
+
#items; #hasSupport;
|
|
25
|
+
|
|
26
|
+
#started = false;
|
|
27
|
+
|
|
28
|
+
#ELEMENT_INSTANCE = HTMLImageElement;
|
|
29
|
+
|
|
30
|
+
#HIDED = "-1";
|
|
31
|
+
#AWAITING = "0";
|
|
32
|
+
#SHOWED = "1";
|
|
33
|
+
|
|
34
|
+
constructor(query, startWarning, awaitToStart) {
|
|
35
|
+
this.#items = document.querySelectorAll(query);
|
|
36
|
+
|
|
37
|
+
if(this.#items.length == 0)
|
|
38
|
+
throw new Error(`None was found. Query: "${query}"`);
|
|
39
|
+
|
|
40
|
+
this.#items.forEach(item => {
|
|
41
|
+
if(!(item instanceof this.#ELEMENT_INSTANCE))
|
|
42
|
+
throw new Error(`Invalid instance. Expected "${this.constructor.name}" instead "${item.constructor.name}".` );
|
|
43
|
+
|
|
44
|
+
if(item instanceof HTMLImageElement)
|
|
45
|
+
item.loading = "lazy";
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if(!awaitToStart)
|
|
49
|
+
this.start(startWarning);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
start(warning) {
|
|
53
|
+
if(this.#started){
|
|
54
|
+
if(warning){
|
|
55
|
+
console.warn(
|
|
56
|
+
(this.hasSupport)
|
|
57
|
+
? "This instance already was started."
|
|
58
|
+
: "This browser do not support the Intersection Observer API."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.#started = true;
|
|
66
|
+
this.#hasSupport = ("IntersectionObserver" in window)
|
|
67
|
+
|
|
68
|
+
if(this.#hasSupport){
|
|
69
|
+
this.#items.forEach(item => {
|
|
70
|
+
this.#awaitToWorking(item);
|
|
71
|
+
this.#connectToObserver(item);
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.#showAllWithoutObserver();
|
|
77
|
+
|
|
78
|
+
if(warning){
|
|
79
|
+
console.error(
|
|
80
|
+
"This browser do not suppor the Intersection Observer API - from JavaScript. " +
|
|
81
|
+
"So the resources that depend of it it will be loaded without optimizations of loading."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#showAllWithoutObserver(){
|
|
87
|
+
this.#items.forEach(item => {
|
|
88
|
+
this.#showSource(item);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#checkItemPosition(pos0, pos1, isWidth){
|
|
93
|
+
const max = window["inner" + (isWidth ? "Width" : "Height")];
|
|
94
|
+
|
|
95
|
+
return (pos0 >= 0 && pos0 <= max) || (pos1 >= 0 && pos1 <= max);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#awaitToWorking(item){
|
|
99
|
+
this.#setItemProperties(item);
|
|
100
|
+
|
|
101
|
+
item.onload = () => {
|
|
102
|
+
let loader = new Image();
|
|
103
|
+
loader.src = item.dataset.src;
|
|
104
|
+
|
|
105
|
+
loader.onload = () => {
|
|
106
|
+
loader = null; // "throw" in the collect garbage
|
|
107
|
+
item.dataset.awaiting = "no";
|
|
108
|
+
|
|
109
|
+
// hitbox
|
|
110
|
+
const hb = item.getBoundingClientRect();
|
|
111
|
+
|
|
112
|
+
// if the screen was maintened stopped, after page (re)load,
|
|
113
|
+
// the Intersection... will not update
|
|
114
|
+
if(this.#checkItemPosition(hb.top, hb.bottom) || this.#checkItemPosition(hb.left, hb.right, true))
|
|
115
|
+
this.#showSource(item, this.#SHOWED);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#setItemProperties(item){
|
|
121
|
+
item.src = item.dataset.placeholder;
|
|
122
|
+
item.dataset.currentSrc = item.dataset.placeholder;
|
|
123
|
+
item.dataset.state = this.#AWAITING;
|
|
124
|
+
item.dataset.awaiting = "yes";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#connectToObserver(item){
|
|
128
|
+
const api = new IntersectionObserver(entities => {
|
|
129
|
+
entities.forEach(entity => {
|
|
130
|
+
if(entity.target.dataset.awaiting == "yes")
|
|
131
|
+
return;
|
|
132
|
+
|
|
133
|
+
if(entity.isIntersecting)
|
|
134
|
+
this.#showSource(entity.target, this.#SHOWED);
|
|
135
|
+
|
|
136
|
+
else
|
|
137
|
+
this.#hideSource(entity.target, this.#HIDED);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
api.observe(item);
|
|
142
|
+
|
|
143
|
+
if(this.disconnectFromObserver == undefined){
|
|
144
|
+
this.disconnectFromObserver = () => {
|
|
145
|
+
this.#started = false;
|
|
146
|
+
|
|
147
|
+
this.#items.forEach(item => {
|
|
148
|
+
api.unobserve(item);
|
|
149
|
+
entity.target.dataset.state = this.#AWAITING;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#updateSource(item, state, dataProperty){
|
|
156
|
+
if(item.dataset[dataProperty] != undefined){
|
|
157
|
+
// avoid unnecessary reload
|
|
158
|
+
if((item.dataset.state != state || item.dataset.state == this.#AWAITING) && item.dataset.currentSrc != item.dataset[dataProperty]){
|
|
159
|
+
item.dataset.state = state;
|
|
160
|
+
item.src = item.dataset[dataProperty];
|
|
161
|
+
item.dataset.currentSrc = item.dataset[dataProperty];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
throw new Error(`Data property "data-${dataProperty}" not found.`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#showSource(item, state){
|
|
171
|
+
this.#updateSource(item, state, "src");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#hideSource(item, state){
|
|
175
|
+
this.#updateSource(item, state, "placeholder");
|
|
176
|
+
}
|
|
177
|
+
}
|