@duckafire/lazy-loading-js 2.0.2 → 3.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 CHANGED
@@ -1,157 +1,107 @@
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>
1
+ [wiki-lazy-load]: https://en.wikipedia.org/wiki/Lazy_loading "Wikipedia: Lazy Loading technique"
2
+ [int-ob]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API "Intersection Observer API"
3
+ [caiuse-int-ob]: https://caniuse.com/intersectionobserver "Can I use IntersectionObserver API?"
4
+ [origin-repo]: https://github.com/gyanprabhat7/LazyLoad.JS "From GitHub"
5
+ [int-ob-opt]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#creating_an_intersection_observer "IntersectionObserver constructor options"
2
6
 
3
- ## Lazy loading
7
+ ## LazyLoading.js
4
8
 
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.
9
+ This project is a no-dependence library that aims to available simple and light
10
+ implementations of the [Lazy Loading][wiki-lazy-load] technique to improve the
11
+ optimization of specific elements, objects, and/or components from FrontEnd WEB pages.
12
+ Currently, it supports:
7
13
 
8
- ### Index
14
+ * Images (`<img/>`).
9
15
 
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/src/LazyLoadingImage.js"></script>
16
+ ```html
17
+ <!-- CDN (jsDelivr) URL -->
18
+ <script src="https://cdn.jsdelivr.net/npm/@duckafire/lazy-loading-js@3/dist/lazy-loading.min.js"></script>
29
19
  ```
30
20
 
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");
21
+ > [!IMPORTANT]
22
+ > If [`IntersectionObserver`][int-ob] (**core** of this project) is not available in the
23
+ > client's browser, nothing fallback observer algorithm will be implemented and the
24
+ > high quality image will be attributed to `src`.
25
+ > See more about the availability of this class [here][caniuse-int-ob].
68
26
 
69
- // OR:
70
- // const api_unit = new LazyLoadingImage(".foo");
71
- </script>
72
- ```
27
+ > [!NOTE]
28
+ > This project is a fork from [gyanprabhat7/LazyLoad.JS][origin-repo].
73
29
 
74
- ### API methods
75
30
 
76
- #### Constructor
77
31
 
78
- Instance the object and it validate the HTML elements that were obtained by
79
- the query specificed.
32
+ #### Index
80
33
 
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. |
34
+ * [LazyLoadingImages](#lazyloadingimages)
35
+ * [Element attributes](#element-attributes)
36
+ * [Methods](#methods)
37
+ * [isApiAvailable](#isapiavailable)
38
+ * [isStarted](#isstarted)
39
+ * [startToObserve](#starttoobserve)
86
40
 
87
41
  > [!IMPORTANT]
88
- > Parameters that have `undefined` as default value they are mandatory.
89
-
90
- ##### Exceptions
42
+ > To improve the understanding of this documentation, types was added to
91
43
 
92
- * None element was found with the specified query.
93
- * Element type (`img`, `div`, ...) invalid - only `img` is allow.
94
44
 
95
- ##### Console
96
45
 
97
- ###### Warnings
46
+ ### LazyLoadingImages
98
47
 
99
- * All [`start`](#start) warnings.
48
+ ---
100
49
 
101
- ###### Errors
50
+ * `constructor(query: string, [options: ILLImagesOptions])`
51
+ * `query`: query selector used to catch target elements.
52
+ * `options`: set of options to customize API behavior.
102
53
 
103
- * All [`start`](#start) errors.
54
+ | Options | Default | Description |
55
+ | :-- | :-: | :-- |
56
+ | waitToStart: boolean | false | it defines API do not have to be started after to create the object. |
57
+ | styleClasses: IStyleClasses | {} | set of lists of CSS classes they have to be added/removed to/from the catched elements when the show/hide event occur. |
58
+ | observerOptions: IIOOptions | {} | options to `IntersectionObserver` constructor. See [this][int-ob-opt] to get more information about. |
59
+ | useSrcAsFallbackToLazySrc: boolean | true | if `true`, it defines `src` content is the lazy quality image if `data-lazy` is undefined, else it defines `src` content is the high quality image if `data-high` is undefined. |
104
60
 
105
- #### `start`
61
+ ---
106
62
 
107
- Used to start the API manually.
63
+ #### Element attributes
108
64
 
109
- | Parameters | Type | Default | Description |
110
- | :-- | :-- | :-- | :-- |
111
- | warning | boolean | false | it allows console warning and error messages. |
65
+ * `data-high`: path of the high quality image. It is optional if
66
+ `options.useSrcAsFallbackToLazySrc === false`.
112
67
 
113
- ##### Exceptions
68
+ * `data-lazy`: path of the lazy quality image. It is optional if
69
+ `options.useSrcAsFallbackToLazySrc === true`.
114
70
 
115
- * *None.*
71
+ * `data-style-high` (optional): comman-separated list formed of CSS styles they will
72
+ be given to the catched elements when the show/hide event occur.
116
73
 
117
- ##### Console
74
+ * `data-style-lazy` (optional): comman-separated list formed of CSS styles they will
75
+ be given to the catched elements when the show/hide event occur.
118
76
 
119
- ###### Warnings
77
+ #### Methods
120
78
 
121
- * The API already was started.
79
+ ##### isApiAvailable
122
80
 
123
- ###### Errors
81
+ * `isApiAvailable(): boolean`
124
82
 
125
- * The browser do not have support to *Intersection Observer API* (API from JS
126
- standard, use as base to this project).
83
+ * Behavior: verify if the API is available.
84
+ * Return: result, in boolean.
127
85
 
128
- #### `disconnectFromObserver`
86
+ ---
129
87
 
130
- Used to end API work.
131
-
132
- > [!NOTE]
133
- > This method is declared inside `#connectToObserver` (*private* method).
88
+ ##### isStarted
134
89
 
135
- | Parameters | Type | Default | Description |
136
- | :-- | :-- | :-- | :-- |
137
- | *None* | | | |
90
+ * `isStarted(): boolean`
138
91
 
139
- ##### Exceptions
92
+ * Behavior: verify if the API algorithm was started.
93
+ * Return: result, in boolean.
140
94
 
141
- * *None.*
95
+ ---
142
96
 
143
- ##### Console
97
+ ##### startToObserve
144
98
 
145
- ###### Warnings
99
+ * `startToObserve(): void`
146
100
 
147
- * *None.*
101
+ * Behavior: start the API algorithm.
102
+ * Return: none.
148
103
 
149
- ###### Errors
150
-
151
- * *None.*
152
-
153
- ### Incompatibility browsers
104
+ > [!NOTE]
105
+ > It prints a *warning* in the console, if API is already started.
154
106
 
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.
107
+ ---
@@ -0,0 +1 @@
1
+ "use strict";const __LL_ElemStatus__=Object.freeze({NULL:0,HIGH:1,LAZY:2}),__LL_SrcGroup__=Object.freeze({HIGH:"high",LAZY:"lazy"});class __LL_UseSrc__{}class __LL_TV__{constructor(e){this.exceptionMessage="A type error occur.",this.fallbackWarning=null,this.value=e}expect(e){var t,s;this.isExpAlreadyDefined(),this.isUndef(e);const r=typeof e;if("string"===r){const t=typeof this.value;return this.isValidValue="object"===e?null!==this.value&&t===e:"array"===e?Array.isArray(this.value):t===e,this.exceptionMessage=`Expecting type "${e}", instead "${t}".`,this}if("function"!==r)throw new TypeError(`Expecting constructor, instead "${r}".`);return this.isValidValue=this.value instanceof HTMLImageElement,this.exceptionMessage=`Expecting instance or heir of \`${e.constructor.name}\`, instead \`${null===(s=null===(t=this.value)||void 0===t?void 0:t.constructor)||void 0===s?void 0:s.name}\`.`,this}fallback(e,t=!1){if(void 0!==this.fallbackValue)throw new SyntaxError("Fallback already defined.");return this.isUndef(e),t&&(this.fallbackWarning=new Error("Invalid value type. Using fallback.")),this.fallbackValue=e,this}val(){if(void 0===this.isValidValue)throw new SyntaxError("Expected value not defined.");if(this.isValidValue)return this.value;if(void 0!==this.fallbackValue)return null!==this.fallbackWarning&&console.warn(this.fallbackWarning),this.fallbackValue;throw new TypeError(this.exceptionMessage)}isUndef(e){if(void 0===e)throw new TypeError("`undefined` is invalid.")}isExpAlreadyDefined(){if(void 0!==this.isValidValue)throw new SyntaxError("Expected value already defined.")}}class APIUnavailableError extends Error{constructor(){super("Intersection Observer API is unavailable. High quality images are in use.")}}class __LL_Element__ extends __LL_UseSrc__{constructor(e,t=!1){super(),this.status=__LL_ElemStatus__.NULL,this.elem=new __LL_TV__(e).expect(HTMLImageElement).val(),this.setAttribute(),this.imgSrc={high:this.catchSrc(__LL_SrcGroup__.HIGH,!t),lazy:this.catchSrc(__LL_SrcGroup__.LAZY,t)},this.styleClasses={high:this.catchStyles(__LL_SrcGroup__.HIGH),lazy:this.catchStyles(__LL_SrcGroup__.LAZY)},this.clearBootAttr()}useSrc(e,t){if(t!==__LL_SrcGroup__.HIGH){if(t!==__LL_SrcGroup__.LAZY)throw new SyntaxError(`Invalid source group: "${t}".`);this.toggleSrc(__LL_ElemStatus__.LAZY,this.imgSrc.lazy)&&this.toggleStyles(__LL_ElemStatus__.LAZY,e)}else this.toggleSrc(__LL_ElemStatus__.HIGH,this.imgSrc.high)&&this.toggleStyles(__LL_ElemStatus__.HIGH,e)}setAttribute(){void 0===this.elem.getAttribute("loading")&&this.elem.setAttribute("loading","lazy")}setGroupIndex(e){this.elem.dataset.lliId=e.toString()}getAsHTMLImg(){return this.elem}apiUnavailable(){this.useSrc({high:null,lazy:null},__LL_SrcGroup__.HIGH)}catchSrc(e,t){if(void 0!==this.elem.dataset[e])return this.elem.dataset[e];if(t)return this.elem.src;throw new Error(`Element attribute not found: \`data-${e}\`.`)}catchStyles(e){var t;return(null===(t=this.elem.getAttribute("data-style-"+e))||void 0===t?void 0:t.split(","))||null}clearBootAttr(){delete this.elem.dataset.high,this.elem.dataset.lazy,this.elem.dataset.styleHigh,this.elem.dataset.styleLazy}toggleSrc(e,t){return this.status!==e&&(this.status=e,this.elem.src=t,!0)}toggleStyles(e,t){this.toggleIndieStyles(e,t),this.toggleIndieStyles(e,this.styleClasses)}toggleIndieStyles(e,t){let s=t.lazy,r=t.high;e===__LL_ElemStatus__.LAZY&&(s=t.high,r=t.lazy),null!==s&&this.elem.classList.remove(...s),null!==r&&this.elem.classList.add(...r)}}class __LL_ElementsGroup__ extends __LL_UseSrc__{constructor(e,t,s){super(),this.elements=[],this.styleClasses={high:this.valStyle(s.high),lazy:this.valStyle(s.lazy)},this.catchElements(new __LL_TV__(e).expect("string").val(),t)}useSrc(e,t){this.elements[e].useSrc(this.styleClasses,t)}startToObserve(e){this.elements.forEach((t,s)=>{t.setGroupIndex(s),e.observe(t.getAsHTMLImg())})}apiUnavailable(){for(let e=0;e<this.elements.length;e++)this.apiUnavailable()}valStyle(e){if(!e)return null;for(const t of new __LL_TV__(e).expect("array").val())new __LL_TV__(t).expect("string").val();return e}catchElements(e,t){document.querySelectorAll(e).forEach(e=>{this.elements.push(new __LL_Element__(e,t))})}}class __LL_Observer__ extends __LL_UseSrc__{constructor(e,t){super();const s={root:new __LL_TV__(e.root).expect(HTMLElement).fallback(null).val(),rootMargin:new __LL_TV__(e.rootMargin).expect("string").fallback("0px 0px 0px 0px").val(),scrollMargin:new __LL_TV__(e.scrollMargin).expect("string").fallback("0px 0px 0px 0px").val(),threshold:this.valThreshold(e.threshold)};this.isAvailable(),this.api=this.startAPI(s),this.elementsGroup=t}useSrc(e,t){this.elementsGroup.useSrc(parseInt(e.target.dataset.lliId),t)}startToObserve(){this.elementsGroup.startToObserve(this.api)}valThreshold(e){if(!Array.isArray(e))return new __LL_TV__(e).expect("number").fallback(0).val();for(const t of e)new __LL_TV__(t).expect("number").val();return e}isAvailable(){if(void 0===window.IntersectionObserver)throw this.elementsGroup.apiUnavailable(),new APIUnavailableError}startAPI(e){return new IntersectionObserver(e=>{e.forEach(e=>{this.useSrc(e,e.isIntersecting?__LL_SrcGroup__.HIGH:__LL_SrcGroup__.LAZY)})},e)}}class LazyLoadingImage{constructor(e,t){this.isApiAvail=!0,this.started=!1;const s=new __LL_TV__(t).expect("object").fallback({},!0).val();try{this.observer=new __LL_Observer__(s.observerOptions||{},new __LL_ElementsGroup__(e,s.useSrcAsFallbackToLazySrc||!0,s.styleClasses||{}))}catch(e){throw e instanceof APIUnavailableError&&(this.isApiAvail=!1),e}s.waitToStart||this.startToObserve()}isApiAvailable(){return this.isApiAvail}isStarted(){return this.started}startToObserve(){this.started?console.warn(new Error("API already started.")):(this.started=!0,this.observer.startToObserve())}}
package/package.json CHANGED
@@ -1,24 +1,45 @@
1
1
  {
2
2
  "name": "@duckafire/lazy-loading-js",
3
3
  "author": "duckafire",
4
- "version": "2.0.2",
4
+ "version": "3.0.0",
5
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"],
6
+ "description": "Simple, no-dependeces, lazy loading implementation to FrontEnd WEB",
7
+ "keywords": [
8
+ "client",
9
+ "html",
10
+ "images",
11
+ "web",
12
+ "front-end",
13
+ "optimization",
14
+ "lazy",
15
+ "loading",
16
+ "lazy-loading"
17
+ ],
18
+ "devDependencies": {
19
+ "terser": "^5.43.1",
20
+ "typescript": "^4.9.5"
21
+ },
8
22
  "repository": {
9
23
  "type": "git",
10
24
  "url": "git+https://github.com/duckafire/lazy-loading-js.git"
11
25
  },
26
+ "scripts": {
27
+ "build": "./scripts.js build",
28
+ "clear": "./scripts.js clear",
29
+ "compile": "./scripts.js compile",
30
+ "help": "./scripts.js help",
31
+ "minify": "./scripts.js minify"
32
+ },
12
33
  "files": [
13
34
  "LICENSE",
14
35
  "README.md",
15
- "src"
36
+ "dist/*.min.js"
16
37
  ],
17
38
  "custom-fields": {
18
39
  "cloud-repostories": [
19
- "https://github.com/duckafire/lazy-loading-js.git",
20
- "https://gitlab.com/duckafire/lazy-loading-js.git",
21
- "https://npmjs.com/package/@duckafire/lazy-loading-js.git"
40
+ "https://github.com/duckafire/lazy-loading-js",
41
+ "https://gitlab.com/duckafire/lazy-loading-js",
42
+ "https://npmjs.com/package/@duckafire/lazy-loading-js"
22
43
  ]
23
44
  }
24
45
  }
@@ -1,177 +0,0 @@
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
- }