vue3-自定义指令-图片懒加载

vue3-自定义指令-图片懒加载

1. 效果展示

效果展示

2. 类型文件 - types/lazyImage.ts

import type { ObjectDirective } from 'vue'

export type LazyImageDirective = ObjectDirective

export interface MapItem {
  intersectionObserver: IntersectionObserver
  callback: (...args: any) => void
}

export type IntersectionObserverOptions = {
  root: Element | null
  rootMargin: string
  threshold: number
}

export type ExtraOptions = {
  loadingSrc?: string
  loadingTime?: number
  show: (...args: any) => void
  hide: (...args: any) => void
}

export type BindingValue = ExtraOptions & {
  src: string
  options?: IntersectionObserverOptions
}

export type LazyImageOptions = ExtraOptions & {
  options: IntersectionObserverOptions
}

3. 全局配置 config/index.ts

import { LazyImageOptions} from '@/types/lazyImage'
export interface VDConfig {
  lazyImage?: LazyImageOptions
}

const config: VDConfig = {
  lazyImage: {
    loadingTime: 500,
    show: () => {},
    hide: () => {},
    options: {
      root: null,
      rootMargin: '0px 0px 0px 0px',
      threshold: 0.0
    }
  }
}

export default config

4. 指令文件 - lazyImage.ts

import { LazyImageDirective, BindingValue, MapItem } from '@/types/lazyImage'
import config from '@/config'
import message from '@/message'

// 懒加载
const lazyImageEls = new Map<HTMLElement, MapItem>()
const copyDirective: LazyImageDirective = {
  mounted(el: HTMLElement, binding: any) {
    if (lazyImageEls.has(el)) return
    const arg = binding.arg || 'image'
    let bindingValue: BindingValue = binding.value
    const bindingValueType = Object.prototype.toString.call(bindingValue)
    const { once } = binding.modifiers
    let tagName = el.tagName.toLowerCase() || ''
    let src = ''
    if (bindingValueType === '[object String]') {
      src = bindingValue as unknown as string
    } else if (bindingValueType === '[object Object]') {
      src = bindingValue.src
      bindingValue = { ...config.lazyImage, ...bindingValue }
    } else {
      return message.warning('binding 只能绑定字符串或者对象')
    }
    // setSrc
    const setSrc = (rSrc: string) => {
      if (arg === 'image') {
        ;(el as HTMLImageElement).src = rSrc
      } else if (arg === 'background') {
        el.style.background = `url(${rSrc}) 100%/cover`
      }
    }

    // 替换src
    const replaceSrc = () => {
      setSrc(bindingValue.loadingSrc!)
      const startTime = performance.now()
      let time = bindingValue.loadingTime || 0
      time = isNaN(Number(time)) ? 500 : Number(time)
      const img = new Image()
      img.src = src
      img.onload = () => {
        const endTime = performance.now()
        let useTime = endTime - startTime
        const loadingTime = useTime > time ? 0 : time - useTime
        setTimeout(() => {
          setSrc(src)
        }, loadingTime)
      }
    }

    // 回调
    const callback = (entries: Array<IntersectionObserverEntry>) => {
      const isIntersecting = entries[0].isIntersecting
      if (isIntersecting) {
        if (once) {
          lazyImageEls.get(el)!.intersectionObserver.unobserve(el)
        }
        if (bindingValue.loadingSrc) {
          replaceSrc()
        } else {
          setSrc(src)
        }
        bindingValue.show && bindingValue.show()
      } else {
        bindingValue.hide && bindingValue.hide()
      }
    }
    // value
    if (arg !== 'image' && arg !== 'background') {
      return message.warning('仅支持 image 和 background')
    } else if (arg === 'image' && tagName !== 'img') {
      return message.warning('image 仅配合 img 标签使用')
    } else {
      const intersectionObserver = new IntersectionObserver(callback, bindingValue.options)
      intersectionObserver.observe(el)
      lazyImageEls.set(el, {
        intersectionObserver,
        callback
      })
    }
  },
  beforeUnmount(el: HTMLElement) {
    lazyImageEls.get(el)!.intersectionObserver.unobserve(el)
  }
}

export default copyDirective

5. 对外暴露 - v-directives/index.ts

import type { App } from 'vue'
import lazyImage from '@/directives/lazyImage'

type Directive = 'lazyImage'

type Directives = {
  [key in Directive]: Object
}

// 指令
const directives: Directives = { lazyImage }

// 注入函数
const useD = (app: App, directives: UseDirectivesObj) => {
  Object.keys(directives).forEach(key => {
    app.directive(key, directives[key as keyof UseDirectivesObj] as any)
  })
}

// 注入
const vDirectives = {
  install(app: App) {
    useD(app, directives)
  }
}

export default vDirectives

6. 指令引入 - main.ts

import { vDirectives } from '@/v-directives/index.ts'

// ...
app.use( vDirectives )
// ...

7. 指令使用

基础语法:v-lazyImage:支持的图片用途.修饰符='选项'


修饰符:

  • once:元素与可视窗口交叉后移除监听该元素

选项:

  • 字符串:src地址
  • 对象:
    • src:src地址
    • loadingSrc:图片加载完成前的替代图片
    • loadingTime:图片加载完成到替换的的最大缓冲时间
    • show:可视范围外到可视范围内时的回调 () => {}
    • hide:可视范围内到可视范围外时的回调 () => {}
    • options:具体查看 - IntersectionObserver
      • root:视口元素
      • rootMargin:一个在计算交叉值时添加至根的边界盒,可以有效的缩小或扩大根的判定范围从而满足计算需要,语法和 CSS 中的margin 属性等同
      • threshold:规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组

支持的图片用途:


  • image
  • background
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值