Very practical global Vue custom instructions in the vue project

In Vue, in addition to the default built-in directives of the core functions ( v-model and v-show ), Vue also allows registration of custom directives. Its value lies in the fact that developers need to operate on ordinary DOM elements in certain scenarios.
Vue custom directives have two ways of global registration and local registration. Let’s take a look at the way to register global directives first. Register global directives through Vue.directive( id, [definition] ). Then make a Vue.use() call in the entry file.

Register custom directives globally

Create a new directives/index.js file

import copy from './copy'
import longpress from './longpress'
// 自定义指令
const directives = {
    
    
  copy,
  longpress,
}

export default {
    
    
  install(Vue) {
    
    
    Object.keys(directives).forEach((key) => {
    
    
      Vue.directive(key, directives[key])
    })
  },
}

Introduce and call in main.js

import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)

Hook function for directive definition function (optional):

  • bind: Called only once, when the instruction is bound to the element for the first time, you can define an initialization action that is executed once when binding.
  • inserted: Called when the bound element is inserted into the parent node (it can be called only if the parent node exists, and does not have to exist in the document).
  • update: Called when the template where the bound element is located is updated, regardless of whether the bound value changes. By comparing the binding value before and after the update.
  • componentUpdated: Called when the template where the bound element is located completes an update cycle.
  • unbind: Called only once, when the directive is unbound from the element.

Share 8 commonly used vue instructions

  • Copy and paste command v-copy
  • Long press command v-longpress
  • Input box anti-shake command v-debounce
  • Prohibition of emoticons and special characters v-emoji
  • Image lazy loading v-LazyLoad
  • Permission verification command v-premission
  • Implement page watermark v-waterMarker
  • Drag command v-draggable

Copy and paste command v-copy
requirements: realize one-click copying of text content, which is used for pasting with the right mouse button.
1. Dynamically create a textarea tag, set the readOnly attribute and move it out of the visible area
2. Assign the value to be copied to the value attribute of the textarea tag and insert it into the body
3. Select the value textarea and copy it
4. Move the inserted textarea in the body Except
5. Bind the event when calling for the first time, and remove the event when unbinding

const copy = {
    
    
  bind(el, {
     
      value }) {
    
    
    el.$value = value
    el.handler = () => {
    
    
      if (!el.$value) {
    
    
        // 值为空的时候,给出提示。可根据项目UI仔细设计
        console.log('无复制内容')
        return
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea')
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea)
      // 选中值并复制
      textarea.select()
      const result = document.execCommand('Copy')
      if (result) {
    
    
        console.log('复制成功') // 可根据项目UI仔细设计
      }
      document.body.removeChild(textarea)
    }
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler)
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, {
     
      value }) {
    
    
    el.$value = value
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    
    
    el.removeEventListener('click', el.handler)
  },
}

export default copy

Use: Add v-copy and copied text to Dom

<template>
  <button v-copy="copyText">复制</button>
</template>
<script>
  export default {
    
    
    data() {
    
    
      return {
    
    
        copyText: 'a copy directives',
      }
    },
  }
</script>

Long press command v-longpress
requirements: To achieve long press, the user needs to press and hold the button for a few seconds to trigger the corresponding event
Idea:

1. Create a timer and execute the function after 2 seconds.
2. Trigger the mousedown event when the user presses the button to start the timer; call the mouseout event when the user releases the button.
3. If the mouseup event is triggered within 2 seconds, clear the timer and treat it as a normal click event.
4. If the timer is not cleared within 2 seconds, it will be judged as a long press and the associated function can be executed.
5. On the mobile side, touchstart and touchend events should be considered.
Code implementation:

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

export default longpress

Use: add v-longpress and callback function to Dom

<template>
  <button v-longpress="longpress">长按</button>
</template>

<script>
export default {
    
    
  methods: {
    
    
    longpress () {
    
    
      alert('长按指令生效')
    }
  }
}

Anti-shake instruction v-debounce
requirements: prevent the button from being clicked multiple times in a short period of time, and use the anti-shake function to limit the click to only one time within a specified time.
Ideas:
1. Define a method to delay execution. If the method is called again within the delay time, the execution time will be recalculated.
2. Bind the time to the click method.

const debounce = {
    
    
  inserted: function (el, binding) {
    
    
    let timer
    el.addEventListener('click', () => {
    
    
      if (timer) {
    
    
        clearTimeout(timer)
      }
      timer = setTimeout(() => {
    
    
        binding.value()
      }, 1000)
    })
  },
}

export default debounce

Use: add v-debounce and callback function to Dom

<template>
  <button v-debounce="debounceClick">防抖</button>
</template>

<script>
export default {
    
    
  methods: {
    
    
    debounceClick () {
    
    
      console.log('只触发一次')
    }
  }
}

Prohibition of emoticons and special character commands v-emoji
Background: Form input encountered in development often has restrictions on input content, such as emoticons and special characters cannot be input, only numbers or letters can be input. Our normal approach is to handle each form's on-change event.

Requirements: According to regular expressions, design custom instructions for processing form input rules. The following is an example of prohibiting the input of emoticons and special characters.

<template>
  <input type="text" v-model="note" @change="vaidateEmoji" />
</template>
<script>
  export default {
    
    
    methods: {
    
    
      vaidateEmoji() {
    
    
        var reg = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/g
        this.note = this.note.replace(reg, '')
      },
    },
  }
</script>

This kind of code is relatively large and difficult to maintain, so we need to customize a command to solve this problem.
Implementation code:

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

const emoji = {
    
    
  bind: function (el, binding, vnode) {
    
    
    // 正则规则可根据需求自定义
    var regRule = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\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)
  },
}

export default emoji

Use: Add v-emoji to the input box that needs to be verified

<template>
  <input type="text" v-model="note" v-emoji />
</template>

Image lazy loading instruction v-LazyLoad
Background: In e-commerce projects, there are often a large number of images, such as banner advertisement images, menu navigation images, and merchant list header images such as Meituan. Too many pictures and too large pictures often affect the loading speed of the page, resulting in a bad user experience, so it is imperative to optimize the lazy loading of pictures.

Requirements: Implement a lazy image loading command, which only loads images in the visible area of ​​the browser.
Lazy loading ideas:
1. The principle of lazy loading of pictures is mainly to determine whether the current picture has reached the visible area, which is the core logic. 2.
Get all the picture Dom, traverse each picture to determine whether the current picture is within the visible area
3. If it arrives, set the src attribute of the picture, otherwise display the default picture

There are two ways to implement lazy loading of pictures. One is to bind the srcoll event for monitoring, and the other is to use IntersectionObserver to judge whether the picture has reached the visible area, but there are browser compatibility issues.

The following encapsulates a lazy loading instruction compatible with the two methods to determine whether the browser supports the IntersectionObserver API. If it supports it, use IntersectionObserver to implement lazy loading, otherwise use srcoll event monitoring + throttling method.
Implementation code:

const LazyLoad = {
    
    
  // install方法
  install(Vue, options) {
    
    
    const defaultSrc = options.default
    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)
    })
  },
  // 加载真实图片
  load(el) {
    
    
    const windowHeight = document.documentElement.clientHeight
    const elTop = el.getBoundingClientRect().top
    const elBtm = el.getBoundingClientRect().bottom
    const realSrc = el.dataset.src
    if (elTop - windowHeight < 0 && elBtm > 0) {
    
    
      if (realSrc) {
    
    
        el.src = realSrc
        el.removeAttribute('data-src')
      }
    }
  },
  // 节流
  throttle(fn, delay) {
    
    
    let timer
    let prevTime
    return function (...args) {
    
    
      const currTime = Date.now()
      const context = this
      if (!prevTime) prevTime = currTime
      clearTimeout(timer)

      if (currTime - prevTime > delay) {
    
    
        prevTime = currTime
        fn.apply(context, args)
        clearTimeout(timer)
        return
      }

      timer = setTimeout(function () {
    
    
        prevTime = Date.now()
        timer = null
        fn.apply(context, args)
      }, delay)
    }
  },
}

export default LazyLoad

Use: replace the src of the label in the component with v-LazyLoad

<img v-LazyLoad="xxx.jpg" />

Background of permission verification command v-premission
: In some background management systems, we may need to judge some operation permissions according to user roles. Many times we roughly add v-if / v-show to an element to display and hide , but if the judgment conditions are cumbersome and multiple places need to be judged, the code in this way is not only inelegant but also redundant. For this situation, we can handle it through global custom directives.

Requirement: Customize a permission command to display and hide Dom that needs permission judgment.
Ideas:
1. Customize a permission array
2. Determine whether the user's permission is in this array, if it is, display it, otherwise remove the Dom

Code:

function checkArray(key) {
    
    
  let arr = ['1', '2', '3', '4']
  let index = arr.indexOf(key)
  if (index > -1) {
    
    
    return true // 有权限
  } else {
    
    
    return false // 无权限
  }
}

const permission = {
    
    
  inserted: function (el, binding) {
    
    
    let permission = binding.value // 获取到 v-permission的值
    if (permission) {
    
    
      let hasPermission = checkArray(permission)
      if (!hasPermission) {
    
    
        // 没有权限 移除Dom元素
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  },
}
export default permission

Use: assign a value to v-permission to judge

<div class="btns">
  //1显示
  <button v-permission="'1'">权限按钮1</button>
  //0不显示
  <button v-permission="'0'">权限按钮2</button>
</div>

Realize the page watermark instruction v-waterMarker
requirements: add background watermark to the entire page
Ideas:

1. Use the canvas feature to generate an image file in base64 format, and set its font size, color, etc.
2. Set it as a background image to achieve page or component watermark effect

function 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') + ')'
}

const waterMarker = {
    
    
  bind: function (el, binding) {
    
    
    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
  },
}

export default waterMarker

Use: set the watermark text, color, font size

<template>
  <div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
</template>

Drag and drop command v-draggable
requirements: implement a drag and drop command, which can drag and drop elements in the visible area of ​​the page arbitrarily.
Ideas:
1. Set the element to be dragged as relative positioning, and its parent element as absolute positioning.
2. Record the current left and top values ​​of the target element when the mouse is pressed (onmousedown).
3. When the mouse moves (onmousemove), calculate the change value of the horizontal distance and vertical distance for each movement, and change the left and top values ​​of the element
4. Complete a drag when the mouse is released (onmouseup)

const draggable = {
    
    
  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
      }
    }
  },
}
export default draggable

Use: Add v-draggable to Dom

<template>
  <div class="el-dialog" v-draggable></div>
</template>

Guess you like

Origin blog.csdn.net/qq_37635012/article/details/128958135