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, and create the directives/directive.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 './directives/directive.js'
Vue.use(Directives)
Copy the code
The instruction definition function provides several hook functions (optionally) :
- Bind: called only once, when the directive is first bound to an element. You can define an initialization action that is performed once at bind time, when the parent node is obtained as null.
- Inserted: Called when the bound element is inserted into a parent, at which point the parent node can be obtained.
- 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-copy
- Long according to the instruction
v-longpress
- Function anti-shake instruction
v-debounce
- Function throttle instruction
v-throttle
- Click the element external directive
v-click-out
- Popovers restrict external scroll instructions
v-scroll-pop
- Loading instructions
v-loading
1, v – copy
Requirements: achieve one key copy text content, used for right mouse paste.
Ideas:
- Dynamically create
textarea
Tag and setreadOnly
Properties and move out of viewable 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 - 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) {// When the value is empty, a hint is given. Can be carefully designed according to the project UI
console.log('No copy content')
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
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 && document.execCommand('Copy') | |false;
if (result) {
console.log('Copy successful') // Can be carefully designed according to the project UI
}else {
console.log('Copy failed, please copy manually')}document.body.removeChild(textarea)
}
// Bind the click event
el.addEventListener('click', el.handler)
},
// Triggered when the value passed in is updated
componentUpdated(el, { value }) {
el.$value = value
},
// Remove event bindings when directives are unbound from elements
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: 'text',}}}</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 n seconds
- Triggered when the user presses the button
mousedown
Event to start the timer; Called when the user releases the buttonmouseout
Events. - if
mouseup
If the event is triggered in n seconds, the timer is cleared as a normal click event - If the timer does not clear within n seconds, it is considered a long press and the associated function can be executed.
- Think about it on mobile
touchstart
.touchend
The event
const longpress = {
bind: function (el, {value:{fn,time}}, vNode) {
// Returns directly without binding function
if (typeoffn ! = ='function') return
// Define timer variables
el.pressTimer = null
// Create timer (execute function after 2 seconds)
el._start = (e) = > {
//e.type Specifies the triggered event type such as mouseDown, touchStart, etc
// PC: e.button Indicates which key to press. 0 indicates the left key, 1 indicates the middle key, and 2 indicates the right key
// Mobile terminal: e.touches indicates the number of keys to be pressed simultaneously
if ( (e.type === 'mousedown'&& e.button && e.button ! = =0) ||
(e.type === 'touchstart' && e.touches && e.touches.length > 1))return;
// Hold the timer for two seconds to execute the event
if (el.pressTimer === null) {
el.pressTimer = setTimeout(() = > {
fn()
}, time)
// Cancel browser default events, such as right-click window
el.addEventListener('contextmenu'.function(e) { e.preventDefault(); }}})// If you let go within two seconds, cancel the timer
el._cancel = (e) = > {
if(el.pressTimer ! = =null) {
clearTimeout(el.pressTimer)
el.pressTimer = 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)
},
// Remove event bindings when directives are unbound from elements
unbind(el) {
// Remove the event listener
el.removeEventListener('mousedown', el._start)
el.removeEventListener('touchstart', el._start)
// Remove the cancel timer
el.removeEventListener('click', el._cancel)
el.removeEventListener('mouseout', el._cancel)
el.removeEventListener('touchend', el._cancel)
el.removeEventListener('touchcancel', el._cancel)
},
}
export default longpress
Copy the code
Use: Add v-longpress and the callback function to the Dom
<template>
<button v-longpress="{fn: longpress,time:2000}">Long press</button>
</template>
<script>
export default {
methods: {
longpress () {
alert('Long press instruction to take effect')}}}</script>
Copy the code
3, v – debounce
Background: In development, it is sometimes necessary to add listening events to the input or scroll bar.
Requirement: Prevent the input or scroll event from being triggered more than once in a short period of time.
Ideas:
- Define a method that delays execution, and recalculate the execution time if the method is called within the delay time.
- Bind the event to the method passed in.
const debounce = {
inserted: function (el, {value:{fn, event, time}}) {
// Returns directly without binding function
if (typeoffn ! = ='function') return
// Listen for click events. If the timer is clicked again, the timer will be cleared and retimed
el.addEventListener(event, () = > {
if (el._timer) {
clearTimeout(el._timer)
}
el._timer = setTimeout(() = > {
fn()
}, time)
})
},
}
export default debounce
Copy the code
Use: Add v-debounce and a callback to the Dom
<template>
<input v-debounce="{fn: debounce, event: 'input', time: 5000}" />
<div v-debounce="{fn: debounce, event: 'scroll', time: 5000}">
<p>Text, text, text, text, text, text, text, text, text, text, text</p>
<p>Text, text, text, text, text, text, text, text, text, text, text</p>
<p>Text, text, text, text, text, text, text, text, text, text, text</p>
<p>Text, text, text, text, text, text, text, text, text, text, text</p>
<p>Text, text, text, text, text, text, text, text, text, text, text</p>
</div>
</template>
<script>
export default {
methods: {
debounce(){
console.log('the debounce if you')}}}</script>
Copy the code
4, v – throttle
Background: In development, some submit save buttons are sometimes clicked multiple times in a short period of time, which leads to repeated requests to the back-end interface for multiple times, resulting in data confusion. For example, click the Buy now button for multiple times, and the create order interface will be called multiple times.
Requirements: prevent buttons from being clicked more than once in a short period of time, using throttling functions to limit clicks to one time.
Ideas:
- Defines a method whose execution is controlled by a switch (on by default), which is turned off the first time the function is executed, and which will not be executed again until a specified time has passed when the switch is turned on.
- Bind the event to the click method.
const throttle = {
bind:function (el,{value:{fn,time}}) {
if (typeoffn ! = ='function') return
el._flag = true;// Switch is on by default
el._timer = null
el.handler = function () {
if(! el._flag)return;
// Switch off after execution
el._flag && fn()
el._flag = false
el._timer && clearTimeout(el.timer)
el._timer = setTimeout(() = > {
el._flag = true;// Switch on after 3 seconds
}, time);
}
el.addEventListener('click',el.handler)
},
unbind:function (el,binding) {
el.removeEventListener('click',el.handler)
}
}
export default throttle
Copy the code
Use: add V-throttle and callback functions to the Dom.
<template>
<button v-throttle="{fn: throttle,time:3000}">Throttle orifice</button>
</template>
<script>
export default {
methods: {
throttle () {
console.log('Trigger only once')}}}</script>
Copy the code
5, v – clickOut
Background: In our project, there is often a popover that needs to be closed by clicking on the popover section.
Requirement: Implement an instruction, click outside the target area, trigger the specified function.
Ideas:
- Check whether the clicked element is the target element, if yes, no action, otherwise the specified function is fired.
const clickOut = {
bind(el,binding,vnode){
function clickHandler(e) {
// Check if the clicked element is itself, if so, return
if (el.contains(e.target)) return;
// Check whether the instruction is bound to a function
if (typeof binding.value === 'function') {
console.log(binding);
// If a function is bound, the function is called, where binding.value is the clickImgOut method
binding.value(e)
}
}
// Bind a private variable to the current element so that you can unbind events
el.handler = clickHandler;
// Add event listener
setTimeout(() = > {
document.addEventListener('click',el.handler);
}, 0);
},
unbind(el,binding){
// Remove event listening
document.removeEventListener('click',el.handler); }}export default clickOut
Copy the code
Add v-ClickOut to the element that needs the directive
<template>
<div v-if="isPopShow" v-click-out="clickPopOut">
<p>I am content</p>
</div>
</template>
<script>
export default {
data(){
return {
isPopShow : false}},mounted(){
this.isPopShow = true;
},
methods: {clickPopOut(){
this.isPopShow = false; }}}</script>
Copy the code
6, v – scrollPop
Background: In our project, pop-ups are often used to display activity rules. When the activity rules are too long to scroll, the time will cause external scrolling. In this case, we can handle the global custom instruction.
Need: define a command to scroll inside the popover, but not outside.
Ideas:
- When the popup is displayed, record the scrolling distance of the scroll bar, and then set the body and HTML to a fixed position with a height of 100% and a value of top as the scrolling distance.
- When the popover is cleared, restore the original style and set the scrolling distance to the original value.
const scrollPop = {
bind(el) {
// Define the vertical scrolling distance to the element's content
el.st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
let cssStr = `overflow: hidden; width: 100%; height: 100%; position: fixed; top:${- el.st}px; `
document.querySelector('html').cssText = cssStr
document.body.style.cssText = cssStr
},
unbind(el,{value}) {
let cssStr = 'overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: auto'
document.querySelector('html').cssText = cssStr
document.body.style.cssText = cssStr
document.querySelector('html').style.scrollBehavior = 'auto'
// Manually set the scrolling distance
document.documentElement.scrollTop = el.st
document.body.scrollTop = el.st
if(value ! = ='smooth')return;
// If the scroll mode is "smooth", the scroll will feel smooth. When the scroll is finished, change auto back to "smooth"
let timer = setTimeout(() = > {
cssStr = `overflow: auto; height: 100%; position: relative; top: 0px; scroll-behavior: ${value||'smooth'}`
document.querySelector('html').cssText = cssStr
document.querySelector('html').style.scrollBehavior = value || 'smooth'
document.body.style.cssText = cssStr
}, 1); }}export default scrollPop
Copy the code
Use: Bind the v-ScrollPOP property to the popover you want to restrict
<div class="scroll-pop" v-if="isScrollPopShow" v-scroll-pop>
<div class="content">
text...
</div>
</div>
Copy the code
7, v – loading
Requirement: Add a loading mask layer to only the elements that you want to prevent the user from operating on during data requests.
Ideas:
- Create a mask layer by using the createdElement method, determine the current state value in the insert and update hook functions, add the mask layer when loading, remove the mask layer when not loading, determine whether the mask layer is mounted before removing.
// Create a mask
function createMask() {
const ele = document.createElement("div");
ele.style.position = "absolute";
ele.style.top = 0;
ele.style.right = 0;
ele.style.bottom = 0;
ele.style.left = 0;
ele.style.zIndex = 9999;
ele.style.display = "flex";
ele.style.justifyContent = "center";
ele.style.alignItems = "center";
ele.style.backgroundColor = "Rgba (0, 0, 0, 0.2)";
ele.innerHTML = "Loading...";
return ele;
}
const mask = createMask();
let mountedFlag = false;// Switch, whether loading has occurred
/ / add | remove loading
function checkLoading(node, isLoading) {
if (node) {
/ / show the loading
if (isLoading) {
node.style.position = "relative";
node.appendChild(mask);
mountedFlag = true;
} else if (mountedFlag) {
/ / close the loading
node.removeChild(mask);
mountedFlag = false; }}}const loading = {
inserted(el, { value }) {
checkLoading(el, value);
},
update(el, { value }){ checkLoading(el, value); }};export default loading;
Copy the code
You can bind the specified label that requires a loading mask.
<template>
<button @click="fetchData">Mock request data</button>
<div class="data-box" v-loading="loading">
{{text}}
</div>
</template>
Copy the code