Source | github.com/Michael-lzg… The day before yesterday, I saw someone on Github sharing that the optimized version of Vue custom instruction was adjusted according to the actual business needs. Don’t spray if you don’t like it.

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.

Import and call in main.js

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

Batch register directives, new directives/index.js file

import copy from './copy'
import longpress from './longpress'
import debounce from './debounce'
import spare from './spare'
// Custom instruction
const directives = {
  copy, // Copy and paste instructions
  longpress, // Hold the command
  debounce, // Enter box anti-shake command
  spare // Image loading failed to display the standby image command
}
export default {
  install(Vue) {
    Object.keys(directives).forEach(key= > {
      Vue.directive(key, directives[key])
    })
  }
} 
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 instructions V-copy
  • Longpress command v-longpress
  • Enter the box anti-shock command V-debounce
  • Picture load failure display spare picture command V-spare

1, v – copy

Requirements:

One key copy text content, used for right mouse paste, some scenes may need to have a callback.

Ideas:

  1. Dynamically create the TextaREA tag and set the readOnly property and move out of the viewable area
  2. The value to be copied is assigned to the value property of the Textarea tag and inserted into the body
  3. Select the value Textarea and copy it
  4. Remove the textarea inserted in the body
  5. The event is bound on the first invocation and removed on unbinding
const copy = {
  bind(el, binding) {
    el.$value = binding.value
    el.handler = () = > {
      // If the value is null, a prompt will be given
      if (typeofel.$value ! = ='object') {
        if(! el.$value) {// Can be based on the project UI design default prompt
          console.log('No copy content')
          return}}else {
        if(! el.$value.text) {// Execute the callback function if there is a callback
          el.$value.callback(false)
          return}}// Create textarea tag dynamically
      const textarea = document.createElement('textarea')
      // Set this textarea to readonly to prevent iOS from automatically evoking the keyboard and remove the Textarea out of view
      textarea.readOnly = 'readonly'
      textarea.style.position = 'absolute'
      textarea.style.left = '-9999px'
      // Assign the copy value to the textarea tag's value property
      if (typeofel.$value ! = ='object') {
        textarea.value = el.$value
      } else {
        textarea.value = el.$value.text
      }
      // Insert the textarea into the body
      document.body.appendChild(textarea)
      // Select the value and copy it
      textarea.select()
      const result = document.execCommand('Copy')
      if (typeofel.$value ! = ='object') {
        if (result) {
          // Copy success according to the project UI design default prompt
          console.log('Copy successful')}else {
          // Failure to copy can be prompted by the project UI design default
          console.log('Replication failed')}}else {
        if (result) {
          // Execute the callback function if there is a callback after replication
          el.$value.callback(true)}else {
          // If there is a callback for replication failure, execute the callback function
          el.$value.callback(false)}}document.body.removeChild(textarea)
    }
    // Add event listeners
    el.addEventListener('click', el.handler)
  },
  // Triggered when the value passed in is updated
  componentUpdated(el, binding) {
    el.$value = binding.value
  },
  // Remove event bindings when directives are unbound from elements
  unbind(el) {
    el.removeEventListener('click', el.handler)
  }
}
export default copy
// Pass String without a callback function
// <div v-copy="'复制内容'">复制按钮</div>
// Pass the Object callback callback parameter Boolean true successful false failed
// 
      
Copy the code

Use:

Add v-copy and copied text to the Dom

<template> <div> <div v-copy="copyText"> </div> <div v-copy="{text: copyText, callback: CopyCallback}"> copy </div> </div> </template> <script> export default {data() {return {copyText: 'copy'}}, methods: { copyCallback(CallbackVal) { console.log(CallbackVal) } } } </script>Copy the code

2, v – longpress

Requirements:

To achieve 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. 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.
  3. If the mouseup 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. On the mobile side you have to consider the TouchStart, TouchEnd events
const longpress = {
  bind: function(el, binding) {
    el.$value = binding.value
    // Define the timer container
    let timer = null
    // Create timer (execute function after 2 seconds)
    el.start = e= > {
      if (typeofel.$value ! = ='function') {
        throw 'callback must be a function'
      }
      if (e.type === 'click'&& e.button ! = =0) {
        return
      }
      if (timer === null) {
        timer = setTimeout(() = > {
          // Run the function
          el.$value()
        }, 2000)}}// Cancel the timer
    el.cancel = () = > {
      if(timer ! = =null) {
        clearTimeout(timer)
        timer = null}}// Add event listeners
    el.addEventListener('mousedown', el.start)
    el.addEventListener('touchstart', el.start)
    // Cancel the timer
    el.addEventListener('click', el.cancel)
    el.addEventListener('mouseout', el.cancel)
    el.addEventListener('touchend', el.cancel)
    el.addEventListener('touchcancel', el.cancel)
  },
  // Triggered when the value passed in is updated
  componentUpdated(el, binding) {
    el.$value = binding.value
  },
  // Remove event bindings when directives are unbound from elements
  unbind(el) {
    el.removeEventListener('mousedown', el.start)
    el.removeEventListener('touchstart', el.start)
    el.removeEventListener('click', el.cancel)
    el.removeEventListener('mouseout', el.cancel)
    el.removeEventListener('touchend', el.cancel)
    el.removeEventListener('touchcancel', el.cancel)
  }
}

export default longpress


      
longpress
Copy 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() {console.log(' longpress to effect ')}}} </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 the button to one click in a specified period of 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 = {
  bind: function(el, binding) {
    el.$value = binding.value
    // Define the timer container
    let timer = null
    el.handler = () = > {
      if (typeofel.$value ! = ='function') {
        throw 'callback must be a function'
      }
      // Cancel the timer
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      // Create timer (execute function after 1 second)
      if (timer === null) {
        timer = setTimeout(() = > {
          // Run the function
          el.$value()
        }, 1000)}}// Add event listeners
    el.addEventListener('keyup', el.handler)
  },
  // Triggered when the value passed in is updated
  componentUpdated: function(el, binding) {
    el.$value = binding.value
  },
  // Remove event bindings when directives are unbound from elements
  unbind(el) {
    el.removeEventListener('keyup', el.handler)
  }
}

export default debounce

// <input type="text" v-debounce="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 – spare

Requirements:

When the img request fails to load a network image, a new image address is given to img

Ideas:

  1. Listen for img error events
  2. An error event is raised after the img load error to verify that the alternate address is valid
  3. If the alternate address is available, assign the alternate address to the SRC of img
const spare = {
  bind(el, binding) {
    el.$value = binding.value
    el.handler = () = > {
      // Dynamically create an IMG tag
      const img = document.createElement('img')
      // Verify that the alternate address is valid
      img.onload = () = > {
        // Assign a new image address
        el.src = el.$value
      }
      img.onerror = () = > {
        console.log('Backup image cannot load')
      }
      img.src = el.$value
    }
    // Bind events
    el.addEventListener('error', el.handler)
  },
  // Triggered when the value passed in is updated
  componentUpdated(el, binding) {
    el.$value = binding.value
  },
  // Remove event bindings when directives are unbound from elements
  unbind(el) {
    el.removeEventListener('error', el.handler)
  }
}

export default spare

// <img :src="imgScr" v-spare="spareImg" />
Copy the code

Use:

Add v-spare and spare image addresses to Dom

<template>
  <div>
    <img src="http://www.xxx.com/" v-spare="spareImg" />
  </div>
</template>
<script>
export default {
  props: {},
  data() {
    return { spareImg: require('@/assets/logo.png') }
  }
}
</script>
Copy the code

Copy, Longpress, and Debounce are all based on other people’s code

Attached source code address: github.com/Michael-lzg…