Vue custom directive
In the past, I seldom looked at some relatively seldom-used documents in vue at work. I noticed a vue custom command in the past few days and found it very interesting. I hereby record some common custom command packaging and usage. Refer to the document vue customization instruction .
Registration and parameter details:
Register a global custom directive:
Vue.directive('throttle', {
// 钩子函数,bind只调用一次,指令第一次绑定到元素时调用
bind: function (el, binding) {
// 这里处理你想做的事
}
})
Register a local custom directive in the component:
directives:{
throttle:{
// 钩子函数,bind只调用一次,指令第一次绑定到元素时调用
bind: (el, binding) => {
// 这里处理你想做的事
}
}
},
Custom command hook function:
bind: Called only once, when the directive is bound to the element for the first time. One-time initialization settings can be performed here.
inserted: Called when the bound element is inserted into the parent node (only the parent node is guaranteed to exist, but not necessarily inserted into the document).
update: Called when the VNode of the component is updated, but it may happen before the update of its child VNode.
componentUpdated: Called after the VNode of the component where the command is located and its child VNodes are all updated.
unbind: Called only once, when the instruction is unbound from the element.
Hook function parameters:
- el: The element bound to the instruction, which can be used to directly manipulate the DOM.
- binding: An object, including the following properties:
name: The command name, excluding the v- prefix.
value: the binding value of the directive, for example: in v-my-directive="1 + 1", the binding value is 2.
oldValue: The previous value bound by the directive, only available in update and componentUpdated hooks. Available whether or not the value has changed.
expression: Instruction expression in string form. For example, in v-my-directive="1 + 1", the expression is "1 + 1".
arg: Arguments passed to the command, optional. For example, in v-my-directive:foo, the parameter is "foo".
modifiers: An object containing modifiers. For example: in v-my-directive.foo.bar, the modifier object is { foo: true, bar: true }. - vnode: The virtual node generated by Vue compilation. Move to VNode API for more details.
- oldVnode: the previous virtual node, only available in update and componentUpdated hooks.
Custom directive:
v-throttle ======= throttling
Here is an explanation of the similarities and differences between anti-shake and throttling, although both are to prevent multiple executions of events within a unified system, consuming browser performance.
But throttling only triggers one event per unit time, and anti-shake only triggers the last event per unit time.
Application scenario:
throttling, the button is triggered multiple times, so that it is only triggered once per unit time.
Anti-shake, input type trigger or window trigger resize, so that it only triggers the last time per unit time.
Instruction package:
const throttle = {
bind: (el, binding) => {
// console.log('指令与元素绑定')
let {
throttleFunc, onThrottle, timeout } = binding.value||{
}
timeout = timeout || 2 * 1000
let throttleTimer;
el.addEventListener('click', event => {
if (!throttleTimer) {
if(throttleFunc)throttleFunc() // 节流取消或者第一次触发时的事件
throttleTimer = setTimeout(() => {
throttleTimer = null;
}, timeout);
} else {
console.warn('处于节流,无法触发点击事件,防误触时间间隔:'+timeout+' ms')
if(onThrottle)onThrottle() // 触发节流时触发的事件
event && event.stopImmediatePropagation();
}
}, true);
},
unbind(el) {
// console.warn('指令与元素解绑')
el.removeEventListener('click', el.handler)
}
}
export default throttle
Directives.js imports:
import throttle from './custom-order/throttle'
// 自定义指令
const directives = {
throttle
}
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
},
}
main.js global import:
import Directives from '@/utils/directives'
Vue.use(Directives)
In-component directives use:
<template>
<div class="hello">
<button v-throttle="options">Vue全局注册自定义指令</button><br/>
</div>
</template>
<script>
export default {
data(){
return{
options: {
onThrottle: null, // 节流时触发的事件
throttleFunc:()=>{
this.throttleTest()
},
timeout: 200
}
}
},
methods:{
throttleTest(){
console.log('触发了一个事件')
}
}
}
</script>
<style scoped>
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
</style>
ps: v-throttle="options" is bound here as a parameter, so you can pass data according to your own needs, for example, v-throttle="throttleTest", bind a func, and then change the processing in throttle.js function.
bind: (el, binding) => {
let throttleFunc = binding.value||null // 此时值为传入的 throttleTest
let timeout = timeout || 2 * 1000
let throttleTimer;
el.addEventListener('click', event => {
if (!throttleTimer) {
if(throttleFunc)throttleFunc() // 节流取消或者第一次触发时的事件
throttleTimer = setTimeout(() => {
clearTimeout(throttleTimer)
throttleTimer = null;
}, timeout);
} else {
console.warn('处于节流,无法触发点击事件,防误触时间间隔:'+timeout+' ms')
event && event.stopImmediatePropagation();
}
}, true);
}
v-debounce ======= debounce
Application scenario:
input is triggered, so that it only triggers the last time per unit time.
Instruction package:
let debounceFunc,timeout,debounceTimer
let executeFunc = async function(){
debounceTimer = await setTimeout(() => {
if(debounceFunc)debounceFunc()
}, timeout);
}
let clearTimer = function(){
clearTimeout(debounceTimer)
debounceTimer = null;
}
const debounce = {
bind: (el, binding) => {
let val = binding.value||{
}
debounceFunc = val.debounceFunc
timeout = val.timeout || 3 * 1000
el.addEventListener('input', () => {
if (debounceTimer) {
console.warn('先清除上次还未执行的延时事件')
clearTimer()
executeFunc()
}else{
executeFunc()
}
}, true);
},
unbind(el) {
el.removeEventListener('input', el.handler)
}
}
export default debounce
In-component directives use:
<template>
<div class="hello">
<input v-debounce="debounceOptions" v-model="val">
</div>
</template>
<script>
export default {
data(){
return{
debounceOptions: {
debounceFunc:()=>{
this.debounceTest()
},
timeout: 2 * 1000
},
val: ''
}
},
methods:{
debounceTest(){
console.log('触发了一个事件')
}
}
}
</script>
<style scoped>
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
</style>
v-draggable ======= drag
Application scenario:
Some custom pop-up windows cover the content behind, and dragging and dropping can make users easily read the covered content.
Instruction package:
const draggable = {
inserted: function (el, binding) {
let bodyClientWidth = parseInt(document.body.clientWidth)
let bodyClientHeight = parseInt(document.body.clientHeight)
let dragMoveReal = el
let dialogTop = binding.value || '15vh'
let dragMoveRealWidth = parseInt(dragMoveReal.style.width)
if (dragMoveReal.style.width && dragMoveReal.style.width.indexOf) {
// 这里是避免dragMoveReal获取的宽度为rem或者%定义的宽度
if (dragMoveReal.style.width.indexOf('rem') != -1) {
dragMoveRealWidth = parseInt(dragMoveReal.style.width) * 100
} else if (dragMoveReal.style.width.indexOf('%') != -1) {
dragMoveRealWidth = parseInt(dragMoveReal.style.width) / 100 * bodyClientWidth
}
}
let dialogLeft = (bodyClientWidth - dragMoveRealWidth) / 2
dragMoveReal.style.cursor = 'move'
dragMoveReal.style.position = 'fixed'
dragMoveReal.style.left = dialogLeft + 'px'
dragMoveReal.style.top = dialogTop
dragMoveReal.style.margin = '0'
dragMoveReal.onmousedown = function (e) {
let disx = e.pageX - dragMoveReal.offsetLeft
let disy = e.pageY - dragMoveReal.offsetTop
document.onmousemove = function (e) {
let x = e.pageX - disx
let y = e.pageY - disy
let maxX = bodyClientWidth - parseInt(window.getComputedStyle(dragMoveReal).width)
let maxY = bodyClientHeight - parseInt(window.getComputedStyle(dragMoveReal).height)
if (x < 0) {
x = 0
} else if (x > maxX) {
x = maxX
}
if (y < 0) {
y = 0
} else if (y > maxY) {
y = maxY
}
dragMoveReal.style.left = x + 'px'
dragMoveReal.style.top = y + 'px'
}
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null
}
}
}
}
export default draggable
In-component directives use:
<template>
<div class="hello">
<div v-draggable class="block"></div>
</div>
</template>
<script></script>
<style>
html,body{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.hello{
text-align: left;
margin: 15% 20%;
line-height: 40px;
}
.block{
width: 300px;
height: 300px;
background: red;
}
</style>