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)
  },
})




目录
相关文章
|
4天前
|
JavaScript
vue页面加载时同时请求两个接口
vue页面加载时同时请求两个接口
|
1天前
|
JavaScript
vue知识点
vue知识点
9 1
|
3天前
|
JavaScript
vue打印v-model 的值
vue打印v-model 的值
|
4天前
|
JavaScript
Vue实战-组件通信
Vue实战-组件通信
5 0
|
4天前
|
JavaScript
Vue实战-将通用组件注册为全局组件
Vue实战-将通用组件注册为全局组件
5 0
|
4天前
|
JavaScript 前端开发
vue的论坛管理模块-文章评论02
vue的论坛管理模块-文章评论02
|
4天前
|
JavaScript Java
vue的论坛管理模块-文章查看-01
vue的论坛管理模块-文章查看-01
|
4天前
|
JavaScript
VUE里的find与filter使用与区别
VUE里的find与filter使用与区别
14 0
|
4天前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式
13 0
|
4天前
|
移动开发 JavaScript 应用服务中间件
vue打包部署问题
Vue项目`vue.config.js`中,`publicPath`设定为&quot;/h5/party/pc/&quot;,在线环境基于打包后的`dist`目录,而非Linux的`/root`。Nginx代理配置位于`/usr/local/nginx/nginx-1.13.7/conf`,包含两个相关配置图。
vue打包部署问题