@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 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
+ }