Github project address: github.com/murongg/vue…

O star and issues

I am not a good writer and may not write good articles. If you have any questions, please comment in the comment section and I will try my best to answer them

This project has been published to NPM

Installation:

$ npm i vue3-lazyload
# or
$ yarn add vue3-lazyload
Copy the code

Demand analysis

  • You can customize loading images to be used when loading images

  • Support to customize the error picture, the picture load failed to use this picture

  • Support for Lifecycle hooks, similar to vue’s lifecycle, and bind lazy attributes to the IMG tag, similar to

    <img src="..." lazy="loading">
    <img src="..." lazy="loaded">
    <img src="..." lazy="error">
    Copy the code

    And support:

      img[lazy=loading] {
        /*your style here*/
      }
      img[lazy=error] {
        /*your style here*/
      }
      img[lazy=loaded] {
        /*your style here*/
      }
    Copy the code
  • Supports the v-lazy custom directive, which specifies that string/object can be passed in. If it is string, it is the URL to be loaded by default. If it is Object, it can be passed in

    • src:The url of the image that needs to be loaded
    • loading:The image used to load the state
    • error:The image used when loading failed
    • lifecycle:The lazy life cycle replaces the global life cycle

The directory structure

- SRC ---- index.ts entry file, which is used to register plug-ins ---- lazy.ts lazy loading main function ---- types. Ts type files, including ---- utilCopy the code

Write lazy loading classes

Lazy loading is mainly implemented through IntersectionObserver object, which may not be supported by some browsers and thus not compatible with IntersectionObserver object.

Determines the parameters passed in when registering the plug-in

Types. Ts:

export interfaceLazyOptions { error? :string; // Failed to load the imageloading? :string; // Load the imageobserverOptions? : IntersectionObserverInit;// IntersectionObserver object passes in the second parameterlog? :boolean; // Whether to print logslifecycle? : Lifecycle;// Lifecycle hooks
}

export interface ValueFormatterObject {
  src: string, error? :string, loading? :string, lifecycle? : Lifecycle; }export enum LifecycleEnum {
  LOADING = 'loading',
  LOADED = 'loaded',
  ERROR = 'error'
}

export type Lifecycle = {
  [x inLifecycleEnum]? :() = > void;
};
Copy the code

Determine the framework of the class

Vue3 Custom Directives support the following Hook Functions: BeforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted. You can view the definitions in vue3. Currently, only mounted, updated, and unmounted are required. These three hooks.

Lazy class infrastructure code, lazy.ts:

export default class Lazy {
  public options: LazyOptions = {
    loading: DEFAULT_LOADING,
    error: DEFAULT_ERROR,
    observerOptions: DEFAULT_OBSERVER_OPTIONS,
    log: true.lifecycle: {}};constructor(options? : LazyOptions) {
    this.config(options)      
  }
  
  /** * Merge config * assgin (); /** * merge config * assgin ()@param {*} [options={}]
   * @memberof Lazy* /
  public config(options = {}): void {
    assign(this.options, options)
  }
  
	public mount(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {} // Corresponding to directive mount hook
  public update() {} // Corresponding to directive update hook
  public unmount() {} // Corresponding to directive unmount hook
}
Copy the code

Write lazy loading features

  /**
   * mount
   *
   * @param {HTMLElement} el
   * @param {DirectiveBinding<string>} binding
   * @memberof Lazy* /
  public mount(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {
    this._image = el
    const { src, loading, error, lifecycle } = this._valueFormatter(binding.value)
    this._lifecycle(LifecycleEnum.LOADING, lifecycle)
    this._image.setAttribute('src', loading || DEFAULT_LOADING)
    if(! hasIntersectionObserver) {this.loadImages(el, src, error, lifecycle)
      this._log(() = > {
        throw new Error('Not support IntersectionObserver! ')})}this._initIntersectionObserver(el, src, error, lifecycle)
  }
  
  /**
   * force loading
   *
   * @param {HTMLElement} el
   * @param {string} src
   * @memberof Lazy* /
  public loadImages(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {
    this._setImageSrc(el, src, error, lifecycle)
  }

  /**
   * set img tag src
   *
   * @private
   * @param {HTMLElement} el
   * @param {string} src
   * @memberof Lazy* /
  private _setImageSrc(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {        
    const srcset = el.getAttribute('srcset')
    if ('img' === el.tagName.toLowerCase()) {
      if (src) el.setAttribute('src', src)
      if (srcset) el.setAttribute('srcset', srcset)
      this._listenImageStatus(el as HTMLImageElement, () = > {
        this._log(() = > {
          console.log('Image loaded successfully! ')})this._lifecycle(LifecycleEnum.LOADED, lifecycle)
      }, () = > {
        // Fix onload trigger twice, clear onload event
        // Reload on update
        el.onload = null
        this._lifecycle(LifecycleEnum.ERROR, lifecycle)
        this._observer.disconnect()
        if (error) el.setAttribute('src', error)
        this._log(() = > { throw new Error('Image failed to load! ')})})}else {
      el.style.backgroundImage = 'url(\'' + src + '\') '}}/**
   * init IntersectionObserver
   *
   * @private
   * @param {HTMLElement} el
   * @param {string} src
   * @memberof Lazy* /
  private _initIntersectionObserver(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {    
    const observerOptions = this.options.observerOptions
    this._observer = new IntersectionObserver((entries) = > {      
      Array.prototype.forEach.call(entries, (entry) = > {
        if (entry.isIntersecting) {
          this._observer.unobserve(entry.target)
          this._setImageSrc(el, src, error, lifecycle)
        }
      })
    }, observerOptions)
    this._observer.observe(this._image)
  }

  /**
   * only listen to image status
   *
   * @private
   * @param {string} src
   * @param {(string | null)} cors
   * @param {() => void} success
   * @param {() => void} error
   * @memberof Lazy* /
  private _listenImageStatus(image: HTMLImageElement, success: ((this: GlobalEventHandlers, ev: Event) => any) | null, error: OnErrorEventHandler) {
    image.onload = success 
    image.onerror = error
  }

  /**
   * to do it differently for object and string
   *
   * @public
   * @param {(ValueFormatterObject | string)} value
   * @returns {*}
   * @memberof Lazy* /
  public _valueFormatter(value: ValueFormatterObject | string): ValueFormatterObject {
    let src = value as string
    let loading = this.options.loading
    let error = this.options.error
    let lifecycle = this.options.lifecycle
    if (isObject(value)) {
      src = (value as ValueFormatterObject).src
      loading = (value as ValueFormatterObject).loading || this.options.loading
      error = (value as ValueFormatterObject).error || this.options.error
      lifecycle = ((value as ValueFormatterObject).lifecycle || this.options.lifecycle)
    }
    return {
      src,
      loading,
      error,
      lifecycle
    }
  }

  /**
   * log
   *
   * @param {() => void} callback
   * @memberof Lazy* /
  public _log(callback: () = > void) :void {
    if (!this.options.log) {
      callback()
    }
  }

  /**
   * lifecycle easy
   *
   * @private
   * @param {LifecycleEnum} life
   * @param {Lifecycle} [lifecycle]
   * @memberof Lazy* /
  private_lifecycle(life: LifecycleEnum, lifecycle? : Lifecycle):void {            
    switch (life) {
    case LifecycleEnum.LOADING:
      this._image.setAttribute('lazy', LifecycleEnum.LOADING)
      if(lifecycle? .loading) { lifecycle.loading() }break
    case LifecycleEnum.LOADED:
      this._image.setAttribute('lazy', LifecycleEnum.LOADED)
      if(lifecycle? .loaded) { lifecycle.loaded() }break
    case LifecycleEnum.ERROR:
      this._image.setAttribute('lazy', LifecycleEnum.ERROR)      
      if(lifecycle? .error) { lifecycle.error() }break
    default:
      break}}Copy the code

Write the update hooks

  /**
   * update
   *
   * @param {HTMLElement} el
   * @memberof Lazy* /
  public update(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {    
    this._observer.unobserve(el)
    const { src, error, lifecycle } = this._valueFormatter(binding.value)
    this._initIntersectionObserver(el, src, error, lifecycle)
  }
Copy the code

Write unmount to hook

  /**
   * unmount
   *
   * @param {HTMLElement} el
   * @memberof Lazy* /
  public unmount(el: HTMLElement): void {
    this._observer.unobserve(el)
  }
Copy the code

Write the install method in index.ts to register the plug-in

import Lazy from './lazy'
import { App } from 'vue'
import { LazyOptions } from './types'

export default {
  /**
   * install plugin
   *
   * @param {App} Vue
   * @param {LazyOptions} options* /
  install (Vue: App, options: LazyOptions): void {
    const lazy = new Lazy(options)

    Vue.config.globalProperties.$Lazyload = lazy
    $Lazyload = $Lazyload
    $this.$Lazyload = this.$Lazyload = this.$Lazyload = this
    // So implement this requirement by providing
    Const useLazylaod = inject('Lazyload')
    Vue.provide('Lazyload', lazy)
    Vue.directive('lazy', {
      mounted: lazy.mount.bind(lazy),
      updated: lazy.update.bind(lazy),
      unmounted: lazy.unmount.bind(lazy)
    })
  }
}

Copy the code

The use of plug-in

Main.js:

import { createApp } from 'vue'
import App from './App.vue'
import VueLazyLoad from '.. /src/index'

const app = createApp(App)
app.use(VueLazyLoad, {
  log: true.lifecycle: {
    loading: () = > {
      console.log('loading')},error: () = > {
      console.log('error')},loaded: () = > {
      console.log('loaded')
    }
  }
})
app.mount('#app')

Copy the code

App.vue:

<template> <div class="margin" /> <img v-lazy="'/example/assets/logo.png'" alt="Vue logo" width="100"> <img v-lazy="{src: errorlazy.src, lifecycle: errorlazy.lifecycle}" alt="Vue logo" class="image" width="100"> <button @click="change"> change </button> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { const errorlazy = reactive({ src: '/example/assets/log1o.png', lifecycle: { loading: () => { console.log('image loading') }, error: () => { console.log('image error') }, loaded: () => { console.log('image loaded') } } }) const change = () => { errorlazy.src = 'FM = http://t8.baidu.com/it/u=3571592872, 3353494284 & 79 & app = 86 & size = h300 & & g = 4 n & n = 0 f = jpeg? = 1603764281 & t = bedd2d52d62e141c the SEC bb08c462183601c7' } return { errorlazy, change } } } </script> <style> .margin { margin-top: 1000px; } .image[lazy=loading] { background: goldenrod; } .image[lazy=error] { background: red; } .image[lazy=loaded] { background: green; } </style>Copy the code

The main functions of this plug-in refer to: vue-Lazyload