分享常用的 vue 自定义指令

88 阅读1分钟

分享项目中用到的 vue 自定义指令

  • 表单防止重复提交
  • 图片懒加载
  • 一键 copy 的功能

自定义指令-点击事件节流

Vue.directive("throttle", {
  bind: (el, binding) => {
    let throttleTime = binding.value
    !throttleTime && (throttleTime = 2000)
    let id
    el.addEventListener("click", (evt) => {
      if (!id) {
        id = setTimeout(() => {
          id = null
        }, throttleTime)
      } else {
        evt.stopImmediatePropagation()
      }
      // 注意这里要用捕获模式
    }, true)
  }
})

用法:

<button v-throttle="2000" @click="onClick">submit</button>

通过在浏览器控制台getEventListeners查看按钮的事件回调列表,发现在bind里面绑定的回调,顺序在@click绑定的回调之后,可以推断得出,@click的回调会先于bind的回调绑定到节点。无论钩子是bind或者inserted都一样。

当捕获模式时,节点的回调列表是从后向前,倒序执行的,所以bind里绑定的回调会先执行,stopImmediatePropagation可以阻断@click的回调执行。 反之,当冒泡模式时,回调列表是从前往后执行的,所以会先执行@click的回调,再到bind的回调。

自定义指令-图片懒加载

通过IntersectionObserver、或者scroll事件来判断图片是否需要加载。

const LazyLoad = {
    install(Vue, options) {
        const defaultSrc = options.defaultSrc
        Vue.directive("lazyload", {
            bind(el, binding) {
                LazyLoad.init(el, binding.value, defaultSrc)
            },
            inserted(el) {
                if ('IntersectionObserver' in window) {
                    LazyLoad.observe(el)
                } else {
                    LazyLoad.listenScroll(el)
                }
            }
        })
    },
    init(el, src, defaultSrc) {
        el.setAttribute("data-src", src)
        el.setAttribute('src', defaultSrc)
    },
    observe(el) {
        const observe = new IntersectionObserver((entries) => {
            if (entries[0].isIntersecting) {
                const src = el.dataset.src
                src && el.setAttribute("src", src) && el.removeAttribute('data-src')
            }
        })
        observe.observe(el)
    },
    load(el) {
        const height = document.documentElement.clientHeight
        const {
            top, bottom
        } = el.getBoundingClientRect()
        if (top < height && bottom > 0) {
            const src = el.dataset.src
            src && el.setAttribute("src", src) && el.removeAttribute("data-src")
        }

    },
    listenScroll(el) {
        LazyLoad.load(el)
        const scrollHandler = LazyLoad.throttle(LazyLoad.load)
        document.addEventListener('scroll', () => scrollHandler(el))
    },
    throttle(fn, delay = 300) {
        let id
        return function (...args) {
            if (!id) {
                id = setTimeout(() => {
                    id = null
                    fn.apply(this, args)
                }, delay)
            }
        }
    }
}
export default LazyLoad

用法

import Vue from 'vue'
import Lazyload from './Lazyload.js'
Vue.use(Lazyload, { defaultSrc: 'loadingimgurl' }),
<img v-lazyload="imgurl" />

自定义指令-点击复制

const Copy = {
    bind(el, { value }) {
        el.$value = value
        el.handler = () => {
            if (!el.$value) return
            const textarea = document.createElement("input")
            textarea.setAttribute("readOnly", 'readOnly')
            textarea.style.position = 'absolute'
            textarea.style.left = '-9999px'
            textarea.value = el.$value
            document.documentElement.appendChild(textarea)
            textarea.select()
            const result = document.execCommand("Copy")
            if (result) {
                // 复制成功
            }
            document.documentElement.removeChild(textarea)
        }
        el.addEventListener("click", el.handler)
    },
    componentUpdated(el, { value }) {
        el.$value = value
    }
}
export default Copy

用法

import Vue from 'vue'
import Copy from './copy.js'
Vue.directive('copy', Copy)
<button v-copy="msg">copy</button>