TOP 8 very useful Vue custom instructions
In Vue, in addition to the default built-in instructions (v-model and v-show) of the core functions, Vue also allows the registration of custom instructions. Its value is when developers need to operate on ordinary DOM elements in certain scenarios.
Share 8 very useful Vue custom instructions
This article has been included in github.com/Michael-lzg...
Demo source code address github.com/Michael-lzg...
In Vue, in addition to the default built-in instructions (v-model and v-show) of the core functions, Vue also allows the registration of custom instructions. Its value is when developers need to operate on ordinary DOM elements in certain scenarios.
Vue custom instructions have two methods: global registration and local registration. Let's look at the instruction register globals way, by Vue.directive( id, [definition] )
way of registered global directive. Then at the entrance file Vue.use()
called.
Bulk registration instruction, the 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 the main.js
introduction and call
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)
The instruction definition function provides several hook functions (optional):
- bind: It is called only 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 when binding.
- inserted: Called when the bound element is inserted into the parent node (the parent node can be called, it does not need to exist in the document).
- update: Called when the template where the bound element is located is updated, regardless of whether the bound value changes. By comparing the binding value before and after the update.
- componentUpdated: Called when the template of the bound element completes an update cycle.
- unbind: Only called once, when the instruction is unbound from the element.
Here are a few useful Vue custom instructions to share
- Copy and paste instructions
v-copy
- Long press command
v-longpress
- Input box anti-shake command
v-debounce
- Emoticons and special characters are prohibited
v-emoji
- Image lazy loading
v-LazyLoad
- Authorization check instruction
v-premission
- Implement page watermark
v-waterMarker
- Drag instruction
v-draggable
v-copy
Requirement: Realize one-click copying of text content for right mouse button pasting.
Ideas:
- Dynamically created
textarea
label, and setsreadOnly
the properties in and out of the visible area - The value assigned to copy
textarea
the tagvalue
attributes, and inserted into thebody
- Select the values
textarea
and copy - The
body
insert istextarea
removed - Bind the event on the first call and remove the event when unbinding
const copy = {
bind(el, {value }) {
el.$value = value
el.handler = () => {
if (!el.$value) {
// When the value is empty, a prompt is given. 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 awakened under iOS and move the textarea out of the visible area
textarea.readOnly ='readonly'
textarea.style.position ='absolute'
textarea.style.left ='-9999px'
// Assign the value to be copied to the value attribute of the textarea tag
textarea.value = el.$value
// insert textarea into body
document.body.appendChild(textarea)
// select value and copy
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)
}
// Binding the click event 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 the instruction is unbound from the element, remove the event binding
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
export default copy
Use: Dom add v-copy
text and can be copied
export default {
data() {
return {
copyText:'a copy directives',
}
},
}
v-longpress
Requirements: To achieve a 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
- When the user presses a button to trigger
mousedown
the event, starting a timer; invoked when the user releases the buttonmouseout
event. - If
mouseup
the trigger event within two seconds, the timer is cleared, as an ordinary click event - If the timer is not cleared within 2 seconds, it is judged as a long press and the associated function can be executed.
- On the mobile side to consider
touchstart
,touchend
events
const longpress = {
bind: function (el, binding, vNode) {
if (typeof binding.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 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 the instruction is unbound from the element, remove the event binding
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
export default longpress
Use: Dom plus v-longpress
and a callback function to
export default {
methods: {
longpress () {
alert('Long press the command to take effect')
}
}
}
v-debounce
Background: During development, some submit and save buttons are sometimes clicked multiple times in a short period of time, which will repeatedly request the back-end interface multiple times, causing data confusion. For example, the submit button of a new form will be clicked multiple times. Multiple duplicate 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 be clicked once within the specified time.
Ideas:
- Define a method to delay execution. If the method is called 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: Dom plus v-debounce
and a callback function to
export default {
methods: {
debounceClick () {
console.log('Only trigger once')
}
}
}
v-emoji
Background: The form input encountered during development often has restrictions on the input content, such as not being able to enter emoticons and special characters, but only numbers or letters.
Our method is conventional in every form of on-change
doing handle on events.
export default {
methods: {
vaidateEmoji() {
var reg = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/g
this.note = this.note.replace(reg, '')
},
},
}
This kind of code is relatively large and difficult to maintain, so we need to customize a command to solve this problem.
Requirements: According to regular expressions, design instructions for custom processing form input rules. The following takes the prohibition of emoticons 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: The plus input box needs to be checked v-emoji
to
v-LazyLoad
Background: In e-commerce projects, there are often a lot of pictures, such as banner advertising pictures, menu navigation pictures, and business listing header pictures such as Meituan. A large number of pictures and a large picture often affect the page loading speed and cause a bad user experience. Therefore, it is imperative to optimize the lazy loading of pictures.
Requirements: Implement a picture lazy loading instruction, only load pictures in the visible area of the browser.
Ideas:
- The principle of lazy loading of pictures is mainly to determine whether the current picture has reached the visual area, which is the core logic.
- Get all the picture Dom, traverse each picture to determine whether the current picture is within the visible area
- If you set the picture
src
property, otherwise the default image
Pictures lazy loading can be achieved in two ways, one Binding srcoll
Events to monitor, the second is to use IntersectionObserver
to determine whether the image to the viewing area, but there are browser compatibility issues.
The following package is compatible with a lazy load instruction are two ways to determine whether the browser supports the IntersectionObserver
API, if supported on the use of IntersectionObserver
realization lazy loading, or use the srcoll
event listener method throttling + implementation.
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)
}
},
})
},
// Initialize
init(el, val, def) {
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)
},
// listen for scroll events
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
// Load real picture
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
In use, the inner assembly tag src
intov-LazyLoad
v-permission
Background: In some backstage management system, we may need some action based on user roles determine permissions, many times we are rudely to add an element v-if / v-show
to display hidden, but if the judgment conditions are cumbersome and require more than one place, that this The code in this way is not only inelegant but also redundant. In view of this situation, we can handle it through global custom instructions.
Requirement: Customize a permission instruction to display and hide the Dom that needs permission judgment.
Ideas:
- Customize an array of permissions
- Determine whether the user's permission is in this array, if it is, display it, 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 // 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) {
// No permission to remove Dom element
el.parentNode && el.parentNode.removeChild(el)
}
}
},
}
export default permission
Use: to v-permission
assign judges to
vue-waterMarker
Requirements: Add a background watermark to the entire page
Ideas:
- Use
canvas
properties to create abase64
picture file format, set the font size, color, and so on. - Set it as a background image to achieve the watermark effect of the page or component
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
To use, set the watermark copy, color and font size
v-draggable
Requirements: To implement a drag-and-drop instruction, you can drag and drop elements in the visible area of the page.
Ideas:
- Set the element to be dragged as relative positioning, and its parent element as absolute positioning.
- Mouse Down
(onmousedown)
recording current target elementleft
andtop
values. - Mouse movement
(onmousemove)
calculation transverse distance variation value and the vertical distance moved each time, and change the elementsleft
andtop
values (onmouseup)
Complete a drag when the mouse is released
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: Just add v-draggable to Dom