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 instructionv-copy
  • Long according to the instructionv-longpress
  • Input box anti – shake commandv-debounce
  • Prohibit emoticons and special charactersv-emoji
  • Lazy loading of imagesv-LazyLoad
  • Permission check instructionv-premission
  • Implementing page watermarkingv-waterMarker
  • Drag and drop the instructionsv-draggable

v-copy

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

Ideas:

  1. Dynamically createtextareaTag and setreadOnlyProperties and move out of viewable area
  2. The value to be copied is assigned totextareaOf the labelvalueProperty and insert intobody
  3. The selected valuetextareaAnd copy
  4. willbodyInsert thetextarearemove
  5. 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

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:

  1. Create a timer and execute the function after 2 seconds
  2. Triggered when the user presses the buttonmousedownEvent to start the timer; Called when the user releases the buttonmouseoutEvents.
  3. ifmouseupIf the event is triggered within 2 seconds, the timer is cleared as a normal click event
  4. If the timer does not clear within 2 seconds, it is considered a long press and the associated function can be executed.
  5. Think about it on mobiletouchstart.touchendThe event
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 longpressCopy the code

Use: Add v-longpress and the callback function to the Dom

<template> <button v-longpress="longpress"> </button> </template> <script> export default {methods: {longpress () {alert(' longpress ')}}} </script>Copy the code

v-debounce

Background: In the development, some submit and save buttons are sometimes clicked several times in a short period of 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 several 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:

  1. Define a method that delays execution, and recalculate the execution time if the method is called within the delay time.
  2. 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

v-emoji

Background: In the development of forms, there are often restrictions on input content, for example, expressions and special characters cannot be entered, 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: Design custom instructions for processing form input rules based on regular expressions. The following uses 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

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

v-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:

  1. The principle of picture lazy loading is mainly to judge whether the current picture has reached the visual area of the core logic
  2. Take all the image Dom and walk through each image to determine if the current image is in the viewable range
  3. If it arrives, set the picturesrcProperty, otherwise the default image is displayed

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

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

v-permission

Background: in some background management system, we may need to undertake some actions according to user role permissions, many times we are rudely to – if an element to add v/v – show to show hidden, but you need to determine if judgment conditions cumbersome and a number of places and code this way not only elegant and redundancy. 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:

  1. Customize an array of permissions
  2. 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 permissionCopy the code

Use: assign v-permission

<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

vue-waterMarker

Need: Add a background watermark to the entire page

Ideas:

  1. usecanvasFeatures generatedbase64Format the picture file, set its font size, color and so on.
  2. 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

v-draggable

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

Ideas:

  1. Sets the element to be dragged to a relative position and its parent to an absolute position.
  2. The mouse click(onmousedown)Records the current value of the target elementleft 和 topValue.
  3. The mouse moves(onmousemove)Calculate the change value of transverse distance and longitudinal distance of each movement, and change the element’sleft 和 top 值
  4. Release the mouse(onmouseup)To complete 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 Dom

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