This article is featured on Github github.com/Michael-lzg…
Demo source address github.com/Michael-lzg…
In Vue, in addition to the core functions 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 normal DOM elements in certain scenarios.
Vue custom instruction has two ways: global registration and local registration. Directive (id, [definition]) The vue.use () call is then made in the entry file.
Register directives in batches and create a 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])
})
},
}
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 directive definition function provides several hook functions (optional) :
- Bind: called only once, when the directive is first bound to an element, and can define an initialization action to be performed once at binding time.
- Inserted: Called when the bound element is inserted into the parent node (called when the parent node is present, not in the document).
- Update: called when the template to which the bound element belongs is updated, regardless of whether the bound value has changed. By comparing 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 a directive is unbound from an element.
Here are a few useful Vue custom directives to share
- Copy and paste instruction
v-copy
- Long according to the instruction
v-longpress
- Input box anti – shaking instruction
v-debounce
- Emoji and special characters are prohibited
v-emoji
- Lazy image loading
v-LazyLoad
- Permission check instruction
v-premission
- Implement page watermarking
v-waterMarker
- Drag and drop the instructions
v-draggable
v-copy
Requirements: to achieve a key copy text content, for the right mouse button paste.
Ideas:
- Dynamically create
textarea
Label and setreadOnly
Property and move out of the visible area - The value to be copied is assigned to
textarea
Of the labelvalue
Property and insert intobody
- The selected value
textarea
And copy - will
body
Insert thetextarea
remove - Events are bound on first invocation and removed on unbinding
const copy = {
bind(el, { value }) {
el.$value = value
el.handler = () = > {
if(! el.$value) {// If the value is null, a prompt is given. Can be carefully designed according to the project UI
console.log('No copy')
return
}
// Create a textarea tag dynamically
const textarea = document.createElement('textarea')
// Set the Textarea to readOnly to prevent automatic keyboard evoking on iOS and remove the Textarea from the visual area
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
// Assign the value of copy to the value attribute of the Textarea tag
textarea.value = el.$value
// Insert the 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 successful') // Can be carefully designed according to the project UI
}
document.body.removeChild(textarea)
}
// Bind the click event, which is called a one-click copy
el.addEventListener('click', el.handler)
},
// Triggered when the value passed in is updated
componentUpdated(el, { value }) {
el.$value = value
},
// Remove the event binding when the directive unbinds the element
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
export default copy
Copy 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
Requirements: To implement long press, the user needs to press and hold the button for a few seconds to trigger the corresponding event
Ideas:
- Create a timer and execute the function after 2 seconds
- Triggered when the user presses the button
mousedown
Event, start a timer; Called when the user releases the buttonmouseout
Events. - if
mouseup
If the 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.
- Consider it on mobile
touchstart
.touchend
The event
const longpress = {
bind: function (el, binding, vNode) {
if (typeofbinding.value ! = ='function') {
throw 'callback must be a function'
}
// Define variables
let pressTimer = null
// Create a 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 the timer
let cancel = (e) = > {
if(pressTimer ! = =null) {
clearTimeout(pressTimer)
pressTimer = null}}// Run the function
const handler = (e) = > {
binding.value(e)
}
// Add an event listener
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
// Cancel the 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
},
// Remove the event binding when the directive unbinds the element
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
export default longpress
Copy the code
Use: add v-longpress and a callback function to the Dom
<template>
<button v-longpress="longpress">Long press</button>
</template>
<script>
export default {
methods: {
longpress () {
alert('Long press instruction to effect')}}}</script>
Copy the code
v-debounce
Background: In development, some submit and save buttons are sometimes clicked several times in a short period of time, so the back-end interface will be repeatedly requested, resulting in data confusion, such as the submission button of new form, multiple clicks will add multiple repeated data.
Requirements: To prevent the button from being clicked multiple times in a short time, use the anti-shake function to limit the button to one click within a specified time.
Ideas:
- Defines a method that delays execution and recalculates the execution time if the method is called within the delay time.
- 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
Copy the code
Use: add v-debounce and a callback function to the Dom
<template>
<button v-debounce="debounceClick">Image stabilization</button>
</template>
<script>
export default {
methods: {
debounceClick () {
console.log('Trigger only once')}}}</script>
Copy the code
v-emoji
Background: When developing form input, there are often restrictions on the input content, such as no emoticons and special characters, only numbers or letters.
Our general 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 | \ r \ n \ s..?!,.?!... - & $= () - + / * {} [\]] | \ s/g
this.note = this.note.replace(reg, ' ')}}},</script>
Copy the code
This is a lot of code and not easy to maintain, so we need a custom instruction to solve this problem.
Requirements: According to regular expressions, design custom instructions for processing form input rules. The following takes prohibiting the input of emojis 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
Copy the code
Use: add V-emoji to the input box that needs to be verified
<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 images, such as banner advertisement chart, menu navigation chart, meituan and other business list header chart. Too many images and too large images often affect the page loading speed, resulting in bad user experience, so it is imperative to optimize the lazy loading of images.
Requirement: implement an image lazy loading directive, loading only images in the browser visible area.
Ideas:
- Image lazy loading principle is to determine whether the current image to the visual area of the core logic to achieve
- Get all the image Dom, iterate over each image to determine whether the current image is within the viewable range
- Set the picture if it arrives
src
Property, otherwise the default image is displayed
Lazy image loading can be implemented in two ways: one is to bind srCOLL event for listening; the other is to use IntersectionObserver to judge whether the image is in the viewable area. However, there are browser compatibility problems.
The following is a lazy loading instruction that is compatible with two methods to determine whether the browser supports IntersectionObserver API. If so, it uses IntersectionObserver to implement lazy loading; otherwise, it uses srCOLL event listener + stream.
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)
},
// Using IntersectionObserver to listen to 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)
},
// Listen for Scroll events
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll'.() = > {
handler(el)
})
},
// Load the real image
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')}}},/ / throttling
throttle(fn, delay) {
let timer
let prevTime
return function (. args) {
const currTime = Date.now()
const context = this
if(! prevTime) prevTime = currTimeclearTimeout(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
Copy the code
Use, will component insideOf the labelsrc
Switch tov-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 this with global custom instructions.
Requirement: Customize a permission instruction to display and hide the Dom that requires permission judgment.
Ideas:
- Customize a permission array
- Determine if the user’s permissions are in this array and display them if they are, otherwise remove the Dom
function checkArray(key) {
let arr = ['1'.'2'.'3'.'4']
let index = arr.indexOf(key)
if (index > -1) {
return true / / have permission
} else {
return false / / without 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) {// Do not have permission to remove Dom elements
el.parentNode && el.parentNode.removeChild(el)
}
}
},
}
export default permission
Copy the code
Use: Assign a value judgment to v-Permission
<div class="btns">
<! - show - >
<button v-permission="' 1 '">Permission Button 1</button>
<! -- Do not display -->
<button v-permission="' 10 '">Permission Button 2</button>
</div>
Copy the code
vue-waterMarker
Requirement: Add background watermark to entire page
Ideas:
- use
canvas
Features generatedbase64
Format the image file, set its font size, color, etc. - Set it as the background image to achieve the page or component watermarking 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
Copy the code
Use, set watermark copy, color, font size
<template>
<div v-waterMarker="{text: 'LZG all rights reserved, textColor:' rgba (180, 180, 180, 0.4) '}"></div>
</template>
Copy the code
The effect is shown in the figure
v-draggable
Requirements: implement a drag instruction, can be in the visible area of the page any drag elements.
Ideas:
- Sets the element to be dragged to relative position and its parent to absolute position.
- The mouse click
(onmousedown)
Records the current value of the target elementleft
和top
Value. - The mouse moves
(onmousemove)
Calculate the change value of the transverse distance and longitudinal distance of each movement, and change the elementleft
和top
值 - 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 the Dom
<template>
<div class="el-dialog" v-draggable></div>
</template>
Copy the code
All instructions source address github.com/Michael-lzg…