Github (SRC /demo/example)

Meaning of image lazy loading:

In the project, if the page is loaded at the beginning, all the real images are also loaded, no matter from the network consumption, or from the page rendering will be very performance consumption, resulting in too slow loading.

In real development, we usually render the first time, don’t render the real image, place the image portion with a default box (or put a default loading background image) and then load the image when the IMG tag is fully in the viewport.

To realize the demo

Method one:getBoundingClientRect

Traditional scheme

Using API getBoundingClientRect, the bottom of the box is less than or equal to the height of the viewport (the height of the screen).

<style>
    html.body {
        height:300%; }. lazyImageBox {position: absolute;left:50%;top:1500px;transform: translateX (-50%);width:400px;height:300px;background: url (". / images/default. GIF ") no-repeat center center#EEE; }. lazyImageBoximg {
        width:100%;height:100%;opacity:0;transition:opacity3s; }</style>


<div class="lazyImageBox">
    <img src="" alt="" lazy-image="images/12.jpg">
</div>
Copy the code
function throttle(func, wait = 500) {
            let timer = null,
                previous = 0;
            return function anonymous(. params) {
                let now = new Date(),
                    remaining = wait - (now - previous);
                if (remaining <= 0) {
                    clearTimeout(timer);
                    timer = null;
                    previous = now;
                    func.call(this. params); }else if(! timer) { timer =setTimeout(() = > {
                        clearTimeout(timer);
                        timer = null;
                        previous = new Date(a); func.call(this. params); }, remaining); }}; }let lazyImageBox = document.querySelector('.lazyImageBox'),
            lazyImage = lazyImageBox.querySelector('img');

        const singleLazy = function singleLazy() {
            let trueImg = lazyImage.getAttribute('lazy-image');
            lazyImage.src = trueImg;
            lazyImage.onload = () = > {
                // The real image was loaded successfully
                lazyImage.style.opacity = 1;
            };
            lazyImageBox.isLoad = true;
        };

        const lazyFunc = function lazyFunc() {
            console.log('OK');
            // Prevent repeated processing
            if (lazyImageBox.isLoad) return;
            let A = lazyImageBox.getBoundingClientRect().bottom,
                B = document.documentElement.clientHeight;
            if(A <= B) { singleLazy(); }};setTimeout(lazyFunc, 1000);
        // window.onscroll = lazyFunc; 
        // The default browser will listen for the scroll event to trigger and execute the lazyFunc method in the fastest response time, which causes the trigger frequency too high
        // Throttling processing
        window.onscroll = throttle(lazyFunc);

Copy the code

Method 2:IntersectionObserverDOM listener

Principle: A listener IntersectionObserver monitors the intersection information of one or more DOM elements and visual Windows. Pass in a function that, when the information changes, executes the callback function, passing in the parameter CHANGES, changes an array that contains all the listening DOM elements and the viewport intersection information. For example, the first monitored element, changes[0]. IsIntersecting indicates whether it appears in the viewport. By default, it fires once the first load completes, again when it appears in the viewport, and again when it disappears in the viewport.

The second parameter is configuration, which triggers the callback function when the viewport is present. For example, {threshold: [0,0.5]} will be triggered at the beginning of the occurrence, and triggered in the middle of the occurrence.

Without throttling, the listener is automatically optimized internally.

According to this principle, the code is as follows

let lazyImageBox = document.querySelector('.lazyImageBox'),
            lazyImage = lazyImageBox.querySelector('img');
        const singleLazy = function singleLazy() {
            let trueImg = lazyImage.getAttribute('lazy-image');
            lazyImage.src = trueImg;
            lazyImage.onload = () = > {
                lazyImage.style.opacity = 1;
            };
        };
        IntersectionObserver: monitors the intersection information of one or more DOM elements and visual Windows
        let ob = new IntersectionObserver(changes= > {
            // Changes is an array containing the cross-information of all listening DOM elements and viewports
            let item = changes[0], {isIntersecting, target/ / the target DOM
                } = item;
            if (isIntersecting) {
                // Completely in the viewport
                singleLazy();
                ob.unobserve(lazyImageBox); // After loading the real image, remove the listening on the box}}, {threshold: [1]}); ob.observe(lazyImageBox);// ob.observe(lazyImageBox); // By default, when listening, it is de-heavy, do not need to repeat the firm
Copy the code

This API is not compatible with IE and is the main application mode on mobile. This is how we implement plug-ins when we package them

Package as plug-in

Some principles of encapsulation:

  • Ease of use
    • Call simple
    • Don’t need too many dependencies (preferably zero)
    • Various fault tolerant handling and perfect error prompt
    • Detailed documentation and reference demos for various cases
  • strong
    • Powerful function, often present in the project effect, basic can support
    • Adapt to more needs
    • More user – defined extensions (styles/features)
  • Upgrade and backward compatibility (low learning cost)
    • High performance (performance optimization, lightweight (less code, small size))
    • Maintainability (application of various design patterns)

First, the factory pattern is used so that the exported function can be executed either as a class or as a normal function. When executed as a normal function, it can also create an instance of its own class

const lz = LazyImage()
Copy the code
const lz = new LazyImage()
Copy the code

They work the same way

Complete package code, specific logic is in the comments:

(function () {
    function LazyImage(options) {
        // You can use the function to generate the instance directly or as a constructor
        return new LazyImage.prototype.init(options);
    }

    LazyImage.prototype = {
        constructor: LazyImage,
        init: function init(options) {// With jQuery's factory mode, the logic in init is actually the same as the logic in the original constructor, because new init is required at the end

            // init params merge config
            options = options || {};
            let defaults = {
                context: document.attr: 'lazy-image'.threshold: 1.speed: 300.callback: Function.prototype
            };
            let config = Object.assign(defaults, options)
            // Hang the information on the instance: in other methods, the information is obtained based on the instance
            this.config = config;

            this.imageBoxList = [];

            // Create a listener
            const oboptions = {
                threshold: [config.threshold]
            };
            this.ob = new IntersectionObserver(changes= > {
                changes.forEach(item= > {
                    let {
                        isIntersecting,
                        target
                    } = item;
                    if (isIntersecting) {
                        this.singleHandle(target);
                        this.ob.unobserve(target);// If the listener is already loaded, cancel the listener}}); }, oboptions);this.observeAll();// Listen to all
        },
        // Lazy loading of single images
        singleHandle: function singleHandle(imgBox) {
            let config = this.config,
                imgObj = imgBox.querySelector('img'),
                trueImage = imgObj.getAttribute(config.attr);
            imgObj.src = trueImage;
            imgObj.removeAttribute(config.attr);
            imgObj.onload = () = > {
                imgObj.style.transition =  ` opacity ${config.speed}ms ` ;
                imgObj.style.opacity = 1;
                // Callback function -> Plug-in lifecycle function "callback function & publish subscribe"
                config.callback.call(this, imgObj);
            };
        },
        // Listen for the required DOM element
        observeAll(refresh) {
            let config = this.config,
                allImages = config.context.querySelectorAll( ` img[${config.attr}] ` );
            [].forEach.call(allImages, item= > {
                let imageBox = item.parentNode;
                // The list is already listening to the box
                if (refresh && this.imageBoxList.includes(imageBox)) return;
                // If not already monitored, put it in the list and then monitor it for later comparison with refresh
                this.imageBoxList.push(imageBox);
                this.ob.observe(imageBox);// Listen on the box
            });
        },
        // Refresh: get the new image that needs lazy loading, and do lazy loading
        refresh: function refresh() {
            this.observeAll(true); }};// Since our last new is init, we need to specify lazyImage.prototype to init
    LazyImage.prototype.init.prototype = LazyImage.prototype;

    if (typeof window! = ="undefined") {
        window.LazyImage = LazyImage;
    }
    if (typeof module= = ="object" && typeof module.exports === "object") {
        // Support commonJS and ES6Module specifications
        module.exports = LazyImage;
    }
})();
Copy the code

After writing, use WebPack to package and export a compressed version of min.js for use

use

Introduction:

<script src=".. /dist/LazyImage.min.js"></script>
Copy the code

Commonjs and ES6Module specifications are also supported

Add a ‘lazy-image’ (default, modifiable) attribute to the img tag that needs to be loaded. Setting opacity to 0 for the IMG tag allows you to add a default placeholder if desired. This method LazyImage() is used to lazy-load images on a page.

Supports custom configuration:

  • contextdocumentSpecifying context
  • attr'lazy-image'Which property does it haveimgNeed to do lazy loading (property value is real image address)
  • threshold1When it appears in the viewport before loading1Stands for complete,0The representative just showed up
  • speed300The transition time when the real picture animation appears
  • callbackFunction.prototypeThe callback function that is triggered after the image is successfully loaded

Supported methods:

  • refresh(): Reloading lazy Settings for all new images

Such as:

const lz = LazyImage({
    threshold:0.5.context:box
});
// Add the image dom
lz.refresh()
Copy the code

example

You can go to the repository and look for examples under the example folder

See example -gitPage

Lazy loading Settings for all images, and scroll to the bottom to rejoin images, and lazy loading Settings for new images

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Examples of lazy loading using pictures</title>
    <style>
        .wrapper {
            width: 236px;
            height: 420px;
            margin: 0 auto;
            background: url(./images/default.gif) center center no-repeat;
            padding-bottom: 300px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .wrapper img {
            opacity: 0;
        }
    </style>
</head>
<body>
<div class="img-area">
    <div class="wrapper">
        <img src="" alt="" lazy-image="./images/1.jpg">
    </div>
    <div class="wrapper">
        <img src="" alt="" lazy-image="./images/1.jpg">
    </div>
    <div class="wrapper">
        <img src="" alt="" lazy-image="./images/1.jpg">
    </div>
</div>
<div id="bottom">
    bottom
</div>
<script src=".. /dist/LazyImage.min.js"></script>
<script>
    const imgArea = document.querySelector('.img-area')
    // Lazy loading uses:
    const lz = LazyImage({
        threshold: 0.5.speed: 1000.callback: function (target) {
            console.log(this, target)
        }
    })

    // Scroll to the bottom to load more, and still lazily load the new div
    const bottomOb = new IntersectionObserver((changes) = > {
        const {isIntersecting, target} = changes[0]
        if(isIntersecting){
            console.log('Scroll to the bottom, load more')
            const div = document.createElement('div')
            div.classList.add('wrapper')
            div.innerHTML = `<img src="" alt="" lazy-image="./images/1.jpg">`
            imgArea.appendChild(div)
            lz.refresh()// Perform lazy loading on the newly added DOM}}, {threshold: [0]
    })
    bottomOb.observe(document.querySelector('#bottom'))


</script>
</body>
</html>
Copy the code

Github (SRC /demo/example)