Very practical 8 Vue custom instructions

In Vue, in addition to the default built-in instructions of core functions (v-model and v-show), Vue also allows the registration of custom instructions. Its value lies in that when developers need to operate on ordinary DOM elements in some scenarios.

Vue user-defined instructions can be registered globally or locally. Let's first look at the way to register global instructions through Vue Register the global instruction in the direct (ID, [definition]) mode. Then Vue. In the portal file Use() call.

Batch registration instructions, new directives / index JS file

import copy from './copy'
import longpress from './longpress'
// Custom instruction
const directives = {
  copy,
  longpress,
}

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

In main JS introduced and called

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

The instruction definition function provides several hook functions (optional):

bind: only called once. It is called when the instruction is bound to the element for the first time. You can define an initialization action that is executed once during binding.
Inserted: called when the bound element is inserted into the parent node (it can be called if the parent node exists, and it does not have to exist in the document).
update: called when the template of the bound element is updated, regardless of whether the binding value changes. Front end training By comparing the binding values before and after the update.
componentUpdated: called when the template of the bound element completes an update cycle.
unbind: called only once when the instruction is unbound from the element.
Here are some practical Vue customization instructions

Copy and paste instruction v-copy
Long press the command v-longpress
Input box anti shake command v-debounce
No emoticons and special characters v-emoji
Image lazy loading v-LazyLoad
Permission verification instruction v-permission
Realize page watermark v-waterMarker
Drag command v-draggable
usage method

1. Installation dependency

npm install cm-directives --save

2. Registration instruction

import cmDirectives from 'cm-directives'
Vue.use(cmDirectives)

v-copy
Requirements: copy text content with one click for right-click pasting.

Idea:

Dynamically create the textarea tag, set the readOnly attribute and move out of the visual area
Assign the value to be copied to the value attribute of the textarea tag and insert it into the body
Check the value textarea and copy it
Remove the textarea inserted in the body
Bind the event on the first call and remove the event on unbinding

const copy = {
  bind(el, { value }) {
    el.$value = value
    el.handler = () => {
      if (!el.$value) {
        // When the value is empty, a prompt is given. It can be carefully designed according to the project UI
        console.log('No copy content')
        return
      }
      // Dynamically create textarea Tags
      const textarea = document.createElement('textarea')
      // Set the textarea to readonly to prevent the keyboard from being automatically recalled under iOS, and move the textarea out of the visual area at the same time
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // Assign the value to copy to the value attribute of the textarea tag
      textarea.value = el.$value
      // Insert textarea into the body
      document.body.appendChild(textarea)
      // Select the value and copy it
      textarea.select()
      const result = document.execCommand('Copy')
      if (result) {
        console.log('Copy succeeded') // It can be carefully designed according to the project UI
      }
      document.body.removeChild(textarea)
    }
    // Binding click events is the so-called one click copy
    el.addEventListener('click', el.handler)
  },
  // Triggered when the value passed in is updated
  componentUpdated(el, { value }) {
    el.$value = value
  },
  // When an instruction is unbound from an element, the event binding is removed
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}

export default copy

Use: add v-copy and copied text to Dom

<template>
  <button v-copy="copyText">copy</button>
</template>

<script>
  export default {
    data() {
      return {
        copyText: 'a copy directives',
      }
    },
  }
</script>

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:

Create a timer and execute the function in 2 seconds
When the user presses the button, the mousedown event is triggered to start the timer; The mouseout event is called when the user releases the button.
If the mouseup event is triggered within 2 seconds, the timer is cleared as a normal click event
If the timer is not cleared within 2 seconds, it is determined as a long press, and the associated function can be executed.
In the mobile terminal, the touchstart and touchend events should be considered

const longpress = {
  bind: function (el, binding, vNode) {
    if (typeof binding.value !== 'function') {
      throw 'callback must be a function'
    }
    // Define variables
    let pressTimer = null
    // Create timer (execute function after 2 seconds)
    let start = (e) => {
      if (e.type === 'click' && e.button !== 0) {
        return
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler()
        }, 2000)
      }
    }
    // Cancel timer
    let cancel = (e) => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer)
        pressTimer = null
      }
    }
    // Run function
    const handler = (e) => {
      binding.value(e)
    }
    // Add event listener
    el.addEventListener('mousedown', start)
    el.addEventListener('touchstart', start)
    // Cancel timer
    el.addEventListener('click', cancel)
    el.addEventListener('mouseout', cancel)
    el.addEventListener('touchend', cancel)
    el.addEventListener('touchcancel', cancel)
  },
  // Triggered when the value passed in is updated
  componentUpdated(el, { value }) {
    el.$value = value
  },
  // When an instruction is unbound from an element, the event binding is removed
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}
export default longpress

Use: add v-longpress and callback function to Dom

<template>
  <button v-longpress="longpress">Long press</button>
</template>

<script>
export default {
  methods: {
    longpress () {
      alert('Long press the instruction to take effect')
    }
  }
}
</script>

v-debounce
Background: in development, some submit save buttons are sometimes clicked many times in a short time, which will repeatedly request the back-end interface, resulting in data confusion. For example, for the submit button of a new form, multiple clicks will add multiple duplicate data.

Requirement: prevent the button from being clicked many times in a short time, Front end training Use the anti shake function to limit one click within a specified time.

Idea:

Define a method to delay execution. If the method is called again within the delay time, the execution time will be recalculated.
Bind the event 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">Anti shake</button>
</template>

<script>
export default {
  methods: {
    debounceClick () {
      console.log('Trigger only once')
    }
  }
}
</script>

v-emoji
Background: in the form input encountered in development, there are often restrictions on the input content, such as unable to input expressions and special characters, and only numbers or letters.

Our normal method is to handle the on change event of each form.

<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 code is large and difficult to maintain, so we need to customize an instruction to solve this problem.

Requirements: according to regular expressions, design user-defined instructions for processing form input rules. The following takes the prohibition of inputting expressions and special characters as an example.

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) {
    // Regular rules can be customized according to requirements
    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 to be verified

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

v-LazyLoad
Background: in e-commerce projects, there are often a large number of pictures, such as banner advertising map, menu navigation map, meituan and other business list header map, etc. The large number of pictures and the large size of pictures often affect the page loading speed and cause bad user experience, so it is imperative to optimize the lazy loading of pictures.

Requirements: implement a lazy image loading instruction to load only the images in the visible area of the browser.

Idea:

The principle of image lazy loading is mainly to judge whether the current image has reached the visual area
Get all the pictures Dom, traverse each picture, and judge whether the current picture is within the visual area
Set the src attribute of the picture if it arrives, otherwise the default picture will be displayed
There are two ways to load images lazily: one is to bind the sroll event to listen, and the other is to use the IntersectionObserver to judge whether the image has reached the visual area, but there is a browser compatibility problem.

The following encapsulates a lazy loading instruction, which is compatible with two methods to judge whether the browser supports IntersectionObserver API. If so, use IntersectionObserver to realize lazy loading. Otherwise, use sroll event listening + throttling.

const LazyLoad = {
  // install method
  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)
        }
      },
    })
  },
  // initialization
  init(el, val, def) {
    el.setAttribute('data-src', val)
    el.setAttribute('src', def)
  },
  // Monitoring el with IntersectionObserver
  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)
  },
  // Listen for scroll events
  listenerScroll(el) {
    const handler = LazyLoad.throttle(LazyLoad.load, 300)
    LazyLoad.load(el)
    window.addEventListener('scroll', () => {
      handler(el)
    })
  },
  // Load real pictures
  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
  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 to replace the src of the tag in the component with v-LazyLoad

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

v-permission
Background: in some background management systems, we may need to judge some operation permissions according to the user role. Most of the time, we roughly add v-if / v-show to an element for display and hiding. However, if the judgment conditions are cumbersome and judgment is required in multiple places, the code in this way is not elegant and redundant. web front end training In this case, we can handle it through global user-defined instructions.

Requirement: customize a permission instruction to display and hide DOMS requiring permission judgment.

Idea:

Customize a permission array
Judge whether the user's permission is in this array. If yes, it will be displayed. Otherwise, Dom will be removed

function checkArray(key) {
  let arr = ['1', '2', '3', '4']
  let index = arr.indexOf(key)
  if (index > -1) {
    return true // Have authority
  } else {
    return false // No permission
  }
}

const permission = {
  inserted: function (el, binding) {
    let permission = binding.value // Get the value of v-permission
    if (permission) {
      let hasPermission = checkArray(permission)
      if (!hasPermission) {
        // You do not have permission to remove Dom elements
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  },
}

export default permission

Use: assign a value to v-permission to judge

<div class="btns">
  <!-- display -->
  <button v-permission="'1'">Permission button 1</button>
  <!-- Do not display -->
  <button v-permission="'10'">Permission button 2</button>
</div>

vue-waterMarker
Requirement: add background watermark to the whole page

Idea:

Use canvas feature to generate base64 format picture file, and set its font size, color, etc.
Set it as the background picture to realize the page or component watermark effect

function addWaterMarker(str, parentNode, font, textColor) {
  // Watermark text, parent element, font, text color
  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 to set the watermark copy, color and font size

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

The effect is shown in the figure

v-draggable
Requirements: implement a drag and drop instruction, which can drag and drop elements arbitrarily in the visual area of the page.

Idea:

Set the element to be dragged as relative positioning and its parent element as absolute positioning.
Record the current left and top values of the target element when the mouse is pressed (onmousedown).
When the mouse moves (onmousemove), calculate the change values of the horizontal distance and vertical distance of each movement, and change the left and top values of the element
When the mouse is released (onmouseup), a drag is completed

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>

Keywords: Vue.js

Added by abhi_elementx on Mon, 10 Jan 2022 05:13:08 +0200