In VUE, in addition to the core functionality of the default built-in directives (V-model and V-show), VUE also allows the registration of custom directives. It is useful when developers need to manipulate ordinary DOM elements in certain scenarios.

Vue custom commands can be registered globally or locally. Directive (id, [definition]) {var. Directive (id, [definition]); The vue.use () call is then made in the entry file.

Batch register directives, new directives/index.js file

Import copy from './copy' import longpress from './longpress' // Self-defined const directives = {copy, longpress, } export default { install(Vue) { Object.keys(directives).forEach((key) => { Vue.directive(key, directives[key]) }) }, }Copy the code

Import and call in main.js

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

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

Bind: called once, when the directive is first bound to an element. You can define an initialization action that is performed once at binding time.

Inserted: Called when the bound element is inserted into its parent (the parent is called if it exists, not in the document).

Update: Called when the template to which the element is bound is updated, regardless of whether the binding value changes. By comparing the binding values before and after the update.

ComponentUpdated: Called when the template to which the bound element belongs completes an update cycle.

Unbind: Called only once, when an instruction is unbound from an element.

A few useful Vue custom directives are shared below

Copy and paste instruction v-longpress instruction v-longpress input box anti-shock instruction v-debounce disable emoticons and special characters v-emoji image LazyLoad v-lazyload permission verification instruction v-premission implement page watermarking V-watermarker Drag instruction V-Draggable

1, v – copy

Requirements: achieve one key copy text content, used for right mouse paste.

Ideas:

Dynamically create the TextaREA tag and set the readOnly property and move out of the viewable area

The value to be copied is assigned to the value property of the Textarea tag and inserted into the body

Select the value Textarea and copy it

Remove the textarea inserted in the body

The event is bound on the first invocation and removed on unbinding

const copy = { bind(el, { value }) { el.$value = value el.handler = () => { if (! El.$value) {// Give a hint when the value is empty. Console. log(' no copy ') return} // create textarea tag dynamically const textarea = document.createElement('textarea') // add the Textarea is set to readonly to prevent iOS from automatically evoking the keyboard, Textarea.readonly = 'readOnly' textarea.style.position = 'absolute' Textarea.style. left = '-9999px' Textarea. Value = el.$value // Insert the textarea into the body Document. The body. The appendChild (textarea) / / selected values and Copy the textarea. Select () const result = document. ExecCommand (' Copy ') if (result) {the console. The log (' copy success ') / / according to project the UI design carefully} document. Body. RemoveChild (textarea)} / / bind click event, El.addeventlistener ('click', el.handler)} // componentUpdated(el, {value}) {el.$value = value}, Unbind (el) {el.removeEventListener('click', el.handler)},} export default CopyCopy the code

Use:

Add v-copy and copied text to the Dom

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

2, v – longpress

Requirement: To implement long press, the user needs to press and hold the button for several seconds to trigger the corresponding event

Ideas:

Create a timer and execute the function after 2 seconds

The mouseDown event is triggered when the user presses the button, starting 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 does not clear within 2 seconds, it is considered a long press and the associated function can be executed.

On the mobile side you have to consider the TouchStart, TouchEnd events

const longpress = { bind: function (el, binding, vNode) { if (typeof binding.value ! == 'function') {throw 'callback must be a function'} // create a timer (after 2 seconds) 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}} // Run const Handler = (e) => {binding.value(e)} // Add event listener AddEventListener ('mousedown', start) el.adDeventListener (' touchStart ', start) cancel) el.addEventListener('mouseout', cancel) el.addEventListener('touchend', AddEventListener (' touchCancel ', cancel)}, // componentUpdated(el, {value}) {el.$value = value}, Unbind (el) {el.removeEventListener('click', el.handler)},} export default longpress <template> <button v-longpress="longpress"> longpress </button> </template> <script> export default {methods: {longpress () {alert(' longpress ')}}} </script>Copy the code

3, v – debounce

Background:

In the development, some submit and save buttons are sometimes clicked for many times in a short time, which will repeatedly request the back-end interface and cause data confusion. For example, when the submit button of a new form is clicked for many times, multiple repeated data will be added.

Requirements: to prevent the button from being clicked multiple times in a short period of time, use the anti-shake function to limit clicking to one time.

Ideas:

Define a method that delays execution, and recalculate the execution time if the method is called within the delay time.

Bind the time to the click method.

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

export default debounce
Copy the code

Use:

Add v-debounce and a callback to the Dom

<template> <button v-debounce="debounceClick"> </button> </template> <script> export default {methods: {debounceClick () {console.log(' only triggered once ')}}} </script>Copy the code

4, v – emoji

Background:

For example, expressions and special characters cannot be entered, but only numbers or letters can be entered.

Our normal approach is to do this on 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 RNS,..?!,.?! ... - & $= () - + / * {} []] | s/g this. Note = this. Note, the replace (reg, ' ')},,}} < / script >Copy the code

This is a lot of code and difficult to maintain, so we need to define a custom instruction to solve this problem.

Requirements:

According to the regular expression, design custom processing form input rules of the instruction, the following is to prohibit the input of facial 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 according to demand custom var regRule = / [^ u4E00 - u9FA5 | | d | a zA - Z RNS,..?!,.?! ... - & $= () - + / * {} []] let $inp | s/g = 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 emojiCopy the code

Use:

Add v-emoji to the input box that needs verification

5, v – the LazyLoad

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

Requirement: implement an image lazy loading command, only load the browser visible area of the image.

Ideas:

The principle of picture lazy loading is mainly to judge whether the current picture has reached the visual area of the core logic

Take all the image Dom and walk through each image to determine if the current image is in the viewable range

If it does, set the SRC attribute for the image, otherwise display the default image

Lazy image loading can be implemented in two ways: binding srCOLl event for monitoring, and using IntersectionObserver to judge whether an image reaches the visible area, but there are compatibility problems with browsers.

The following package contains a lazy loading command compatible with both methods to determine whether the browser supports IntersectionObserver API. If so, IntersectionObserver can be used to implement lazy loading; otherwise, SRCOLl event monitoring + throtting method is used to implement lazy loading.

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) } }, }) }, {el.setAttribute('data-src', val) el.setAttribute(' SRC ', def)}, // Use IntersectionObserver to monitor 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)}, // listenerScroll(el) {const handler = lazyload.load (lazyload.load, 300) LazyLoad.load(el) window.addEventListener('scroll', () => { handler(el) }) }, / / load the real images load (el) {const windowHeight = document. The 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 LazyLoadCopy the code

Change the SRC tag inside the component to V-lazyLoad

6, v – permission

Background:

In some background management systems, we may need to judge the operation permissions according to the user role. Most of the time, we simply add V-if/V-show to an element to show and hide, but if the judgment conditions are too complicated and multiple places need to judge, this method of code is not only elegant but also redundant. In this case, we can handle it through global custom instructions.

Requirement: customize a permission directive to show and hide Dom that requires permission judgment.

Ideas:

Customize an array of permissions

Determine if the user’s permissions are in this array, if so display, otherwise remove the Dom

function checkArray(key) { let arr = ['1', '2', '3', '4'] let index = arr.indexof (key) if (index > -1) {return true // permission} else {return false // no permission}} const Permission  = { inserted: function (el, Binding) {let permission = binding. Value // Obtain the value of v-permission if (permission) {let hasPermission = checkArray(permission) if (! HasPermission) {/ / no permissions remove Dom elements el parentNode && el. ParentNode. RemoveChild (el)}}},} export default permission use: <div class=" BTNS "> <! - show - > < button v - permission = "' 1 '" > button 1 < / button > <! - don't show - > < button v - permission = "' 10" > permissions button 2 < / button > < / div >Copy the code

7, the vue – waterMarker

Need: Add a background watermark to the entire page

Ideas:

Use Canvas feature to generate base64 format picture file, set its font size, color and so on.

Set it as a background image to achieve a 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, TextBaseline = 'Middle' (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 waterMarkerCopy the code

Use, set watermark copy, color, font size

<template> <div v-watermarker ="{text:' RZG (180, 180, 180, 0.4)'}"></div> </template>Copy the code

8, v – draggable

Requirement: Implement a drag-and-drop instruction that can drag and drop elements anywhere in the viewable area of the page.

Ideas:

Sets the element to be dragged to a relative position and its parent to an absolute position.

Record the current left and top values of the target element when the mouse is down (onmouseDown).

The mouse movement (onmousemove) calculates the change value of the horizontal and vertical distance of each movement, and changes the left and top values of the element

Mouse release (onmouseup) completes a drag

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
Copy the code

Use:

Add v-draggable to the Dom

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