Vue 自定义指令可以实现哪些有用的功能

简介: Vue 有一些很实用的指令 v-show v-if v-text v-html v-bind v-on 可以帮助我们实现很复杂的功能,同时它还开辟了钩子供我们自己实现自定义指令。

Vue 有一些很实用的指令 v-showv-ifv-textv-htmlv-bindv-on 可以帮助我们实现很复杂的功能,同时它还开辟了钩子供我们自己实现自定义指令。根据自己平时开发总结了一些可以通过指令实现的功能场景:

  • 控制页面元素显示与隐藏,可用作控制权限功能
  • 页面元素点击事件防抖与节流
  • 通过自定义指令控制图片懒加载
  • 针对页面特定元素添加自定义行为
  • 对输入内容进行过滤

下面列举一些常见的自定义指令的实现代码:


权限指令


/**
 * 权限指令 
 * @param {string} value 权限标识
 * 例:<div v-permission="'editInfo'"></div>
 */
Vue.directive('permission', {
  inserted: function (el, binding) {
    const { value } = binding
    // 在前置路由拦截获取权限按钮列表后存储在 store 中
    const actionList = store.state.user.permission
    if (value) {
      const hasPermission = actionList .some(btnKey => btnKey === value)
      // 没有权限直接移除 dom元素
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`需要指定权限标识! 如:v-permission="'editInfo'"`)
    }
  }
})


防抖指令


/**
 * 防抖指令 单位时间只触发最后一次
 * @param {Function} fn - 执行事件
 * @param {?String|"click"} event - 事件类型 例:"click"
 * @param {?Number|500} time - 间隔时间
 * @param {Array} binding.value - [fn,event,time]
 * 直接使用: <XXX v-debounce="reset]">刷新</XXX>
 * 配置事件,间隔时间: <button v-debounce="[reset,'click',500]">刷新</button>
 * 事件传递参数则: <button v-debounce="[()=>reset(param),`click`,500]">刷新</button>
 */
Vue.directive('debounce', {
    bind: function (el, binding) {
        try {
            let fn, event = "click", time = 500;
            if (typeof binding.value == 'function') {
                fn = binding.value
            } else {
                [fn, event = "click", time = 500] = binding.value
            }
            let timer;
            el.addEventListener(event, () => {
                timer && clearTimeout(timer)
                timer = setTimeout(() => fn(), time)
            })
        } catch (e) {
            console.log(e)
        }
    }
})


节流指令


/**
 * 节流指令 一段时间内首次触发时立即执行,此时间段内再次触发,不会执行!
 * @param {Function} fn - 执行事件
 * @param {?String|"click"} event - 事件类型 例:"click"
 * @param {?Number|500} time - 间隔时间
 * @param {Array} binding.value - [fn,event,time]
 * 直接使用: <XXX v-throttle="reset]">刷新</XXX>
 * 配置事件,间隔时间: <button v-throttle="[reset,'click',500]">刷新</button>
 * 事件传递参数则: <button v-throttle="[()=>reset(param),`click`,500]">刷新</button>
 */
Vue.directive('throttle', {
    bind: function (el, binding) {
        let fn, event = "click", time = 1500;
        if (typeof binding.value == 'function') {
            fn = binding.value
        } else {
            [fn, event = "click", time = 1500] = binding.value
        }
        /**
         * el.preTime 记录上次触发事件,
         * 每次触发比较nowTime(当前时间) 和 el.preTime 的差是否大于指定的时间段!
         */
        el.addEventListener(event, () => {
            const nowTime = new Date().getTime()
            if (!el.preTime || nowTime - el.preTime > time) {
                el.preTime = nowTime
                fn();
            }
        })
    }
})


图片懒加载


Vue.directive('lazy', {
  bind(el, binding) {
    LazyLoad.init(el, binding.value, defaultSrc)
  },
  inserted(el) {
    if (IntersectionObserver) {
      LazyLoad.observe(el)
    } else {
      LazyLoad.listenerScroll(el)
    }
  },
})
// 初始化
init(el, val, def) {
  el.setAttribute('data-src', val)
  el.setAttribute('src', def)
},
// 利用IntersectionObserver监听el
observe(el) {
  var io = new IntersectionObserver((entries) => {
    const realSrc = el.dataset.src
    if (entries[0].isIntersecting) {
      if (realSrc) {
        el.src = realSrc
        el.removeAttribute('data-src')
      }
    }
  })
  io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
  const handler = LazyLoad.throttle(LazyLoad.load, 300)
  LazyLoad.load(el)
  window.addEventListener('scroll', () => {
    handler(el)
  })
}


给块元素增加背景


/**
* 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
* 将其设置为背景图片,从而实现页面或组件水印效果
**/
Vue.directive('waterMarker', {
  bind: function (el, binding) {
    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
  }
}),
addWaterMarker(str, parentNode, font, textColor) {
  // 水印文字,父元素,字体,文字颜色
  var can = document.createElement('canvas')
  parentNode.appendChild(can)
  can.width = 200
  can.height = 150
  can.style.display = 'none'
  var cans = can.getContext('2d')
  cans.rotate((-20 * Math.PI) / 180)
  cans.font = font || '16px Microsoft JhengHei'
  cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)'
  cans.textAlign = 'left'
  cans.textBaseline = 'Middle'
  cans.fillText(str, can.width / 10, can.height / 2)
  parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}


页面元素拖拽指令


/**
* 设置需要拖拽的元素为相对定位,其父元素为绝对定位
* 鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值
* 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
**/
Vue.directive('waterMarker', {
  inserted: function (el) {
    el.style.cursor = 'move'
    el.onmousedown = function (e) {
      let disx = e.pageX - el.offsetLeft
      let disy = e.pageY - el.offsetTop
      document.onmousemove = function (e) {
        let x = e.pageX - disx
        let y = e.pageY - disy
        let maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el).width)
        let maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el).height)
        if (x < 0) {
          x = 0
        } else if (x > maxX) {
          x = maxX
        }
        if (y < 0) {
          y = 0
        } else if (y > maxY) {
          y = maxY
        }
        el.style.left = x + 'px'
        el.style.top = y + 'px'
      }
      document.onmouseup = function () {
        document.onmousemove = document.onmouseup = null
      }
    }
  },
})


输入框过滤特殊字符


let findEle = (parent, type) => {
  return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
}
const trigger = (el, type) => {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(type, true, true)
  el.dispatchEvent(e)
}
Vue.directive('filterInput', {
  bind: function (el, binding, vnode) {
    // 正则规则可根据需求自定义
    var regRule = /[^u4E00-u9FA5|d|a-zA-Z|rns,.?!,。?!…—&$=()-+/*{}[]]|s/g
    let $inp = findEle(el, 'input')
    el.$inp = $inp
    $inp.handle = function () {
      let val = $inp.value
      $inp.value = val.replace(regRule, '')
      trigger($inp, 'input')
    }
    $inp.addEventListener('keyup', $inp.handle)
  },
  unbind: function (el) {
    el.$inp.removeEventListener('keyup', el.$inp.handle)
  }
})


增加长按行为


实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件

/**
* 创建一个计时器, 2 秒后执行函数
* 当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。
* 如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
* 如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
* 在移动端要考虑 touchstart,touchend 事件
**/
Vue.directive('longpress', {
  bind: function (el, binding, vNode) {
    if (typeof binding.value !== 'function') {
      throw 'callback must be a function'
    }
    // 定义变量
    let pressTimer = null
    // 创建计时器( 2秒后执行函数 )
    let start = (e) => {
      if (e.type === 'click' && e.button !== 0) {
        return
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler()
        }, 2000)
      }
    }
    // 取消计时器
    let cancel = (e) => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer)
        pressTimer = null
      }
    }
    // 运行函数
    const handler = (e) => {
      binding.value(e)
    }
    // 添加事件监听器
    el.addEventListener('mousedown', start)
    el.addEventListener('touchstart', start)
    // 取消计时器
    el.addEventListener('click', cancel)
    el.addEventListener('mouseout', cancel)
    el.addEventListener('touchend', cancel)
    el.addEventListener('touchcancel', cancel)
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value }) {
    el.$value = value
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
})




目录
相关文章
|
7天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
vue学习第四章
|
7天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
vue学习第九章(v-model)
|
7天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
vue学习第十章(组件开发)
|
12天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
12天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
13天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
13天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
13天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
13天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
14天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
下一篇
无影云桌面