@wezom/zz-load
v0.2.0-beta
Published
Lazy loader based on IntersectionObserver API
Downloads
45
Maintainers
Readme
zz-load
Lazy loader based on IntersectionObserver API
Live preview: https://wezomcompany.github.io/zz-load/
Coverage
| Statements | Branches | Functions | Lines | | --------------------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------- | | | | | |
Code-base features
🌟 Fully treeshackable
🌟 Types included
🌟 ESNext distribute
🌟 CommonJS version available
Table of Content:
Usage
Install npm package
npm i @wezom/zz-load
Import to your codebase
ESNext
We use TypeScript as main development language and distribute our lib in the maximum compliance with modern JavaScript specifications.
You project bundler (webpack or something else) must not exclude this installed package from node_modules
folder.
The package babel-loader-exclude-node-modules-except
can help you with this
CommonJS Version
If you cannot change your bundler config or if you don not want to include esnext code version into your project - for this we have compiled CommonJS version of each library file and you can import *.cjs.js
files. They ready to use without excluding node_modules
and else. These files may have redundant code that is necessary for them to work "out of the box". And they will also be deprived of all the features of the ESNext specifications.
// no ES6 features but ready for use as is, without transpiling
import zzLoad from '@wezom/zz-load/dist/index.cjs';
Usage example
<img
alt
width="640"
height="320"
class="js-lazy-load"
data-zzload-source-img="https://placeimg.com/640/320/nature"
src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="640" height="320"></svg>'
/>
import zzLoad from '@wezom/zz-load';
// create and run observer for elements
const observer = zzLoad('.js-lazy-load');
observer.observe();
See in action https://wezomcompany.github.io/zz-load/
Progressive Enhancement
If you do not know what "Progressive Enhancement" is, watch this video from Heydon Pickering, we assure you you will not regret.
We have browsers that support Intersection Observer API, and browsers that do not support Intersection Observer API.
We can detect the feature and apply polyfill for API if needed.
An example:
// zz-load-init.js
import zzLoad from '@wezom/zz-load';
const observer = zzLoad('.js-lazy-load');
observer.observe();
// lazy-loading-progressive-enhancement.js
(function () {
if ('IntersectionObserver' in window) {
import('./zz-load-init');
} else {
import('intersection-observer').then(() => {
import('./zz-load-init');
});
}
})();
Why I need to use zz-load
when we have native loading
attribute?
Not all browser support native loading!
If you want to make real "progressive enhancement" with browser native lazy loading as primary implementation - please consider next two circumstances:
- Not all cases can be covered with native loading
- You should be able to influence the template of your markup on the server.
Since the browser environment (for reliable verification) will not be available to you on the server - you can determine support of native loading baserd on the browser version by comparing with the table caniuse.com. Based on the result of the check, you can render the markup for native loading or markup for JavaScript implementation. After that, we need to extend our previous script
// lazy-loading-progressive-enhancement.js
(function () {
const lazyLoadingElements = document.querySelectorAll('.js-lazy-load');
// if render was for native loading implementation it should be empty
// otherwise - initialize JS implemetation
if (lazyLoadingElements.length > 0) {
if ('IntersectionObserver' in window) {
import('./zz-load-init');
} else {
import('intersection-observer').then(() => {
import('./zz-load-init');
});
}
}
})();
API
zzLoad()
zzLoad(elements: RootElement, options: Options = {}): Observer
type RootElement = string | Element | Element[] | NodeList | JQuery;
interface Options {
rootMargin?: string;
threshold?: number;
clearSourceAttrs?: boolean;
setSourcesOnlyOnLoad?: boolean;
onProcess?(element: Element, resource?: string): void;
onLoad?(element: Element, resource?: string): void;
onError?(element: Element, resource?: string): void;
}
interface Observer {See `https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options`
observe(): void;
triggerLoad(triggerElements: RootElement, triggerOptions?: Options): void;
}
Parameters:
Name | Data type | Argument | Default value | Description
--- | --- | --- | --- | ---
elements
| string
, Element
, Element[]
, NodeList
, JQuery
| required | | String selector or already programmatically received Elements. You can give it even jQuery collection, if use it.
options
| Object
| optional | undefined
| Observing options
options.rootMargin
| string
| | '0px'
| Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections. Defaults to all zeros. See Intersection observer options -> rootMargin
options.threshold
| number
| | 0
| Either a single number or an array of numbers which indicate at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback to run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1]. The default is 0 (meaning as soon as even one pixel is visible, the callback will be run). A value of 1.0 means that the threshold isn't considered passed until every pixel is visible. See Intersection observer options -> rootMargin
options.setSourcesOnlyOnLoad
| boolean
| | true
| Setting resource attributes to the element only after successful loading
options.clearSourceAttrs
| boolean
| | false
| Removing all zz-load source attributes from element after successful loading
options.onProcess
| Function
| | undefined
| Method called before starting to load resources for element.
options.onLoad
| Function
| | undefined
| Callback after successful after successful loading resources for element.
options.onFail
| Function
| | undefined
| Callback on error loading resources for element.
Return type: Observer
import zzLoad from '@wezom/zz-load';
const observer = zzLoad('.my-js-selector', {
rootMargin: '0px 10%'
});
Observer.observe()
observe(): void;
import zzLoad from '@wezom/zz-load';
const observer = zzLoad('.my-js-selector', {
rootMargin: '0px 10%'
});
// run observer
observer.observe();
Observer.triggerLoad()
triggerLoad(triggerElements: RootElement, triggerOptions?: Options): void;
import zzLoad from '@wezom/zz-load';
const observer = zzLoad('.my-js-selector', {
rootMargin: '0px 10%'
});
// run observer
observer.observe();
// ..code conditions
observer.triggerLoad('#load-me-immediately-now');
Attrs NS
Library attributes namespace
See source
import zzLoad, { attrs } from '@wezom/zz-load';
const observer = zzLoad(`[${attrs.sourceImg}]`); // zzLoad('[data-zzload-source-img]')
Dataset NS
Library dataset namespace
See source
import zzLoad, { dataset } from '@wezom/zz-load';
const picture = document.querySelector('.my-picture-element');
if (picture !== null) {
const sources = [
{
media: '(min-width: 1280px)',
srcset: 'https://via.placeholder.com/1280x720/0000FF/000000'
},
{
media: '(min-width: 750px)',
srcset: 'https://via.placeholder.com/800x450/7878ff/000000'
}
]
picture.dataset[dataset.sourceSources] = JSON.stringify(sources);
const observer = zzLoad(picture);
observer.observe();
}
Events NS
Library events namespace.
See source
import zzLoad, { events } from '@wezom/zz-load';
const picture = document.querySelector('.my-picture-element');
if (picture !== null) {
picture.addEventListener(events.observed, () => {
console.log('Start observing');
});
picture.addEventListener(events.processed, () => {
console.log('Start loading');
});
picture.addEventListener(events.loaded, () => {
console.log('Successfully loaded');
});
picture.addEventListener(events.failed, () => {
console.log('Loading failed');
});
picture.addEventListener(events.inView, (event) => {
console.log('Element is visible', event.detail.visible);
});
}
Contributing
Please fill free to create issues or send PR