In Vue, in addition to the default built-in instructions of core functions (v-model and v-show), Vue also allows the registration of custom instructions. Its value lies in that when developers need to operate on ordinary DOM elements in some scenarios.
Vue user-defined instructions can be registered globally or locally. Let's first look at the way to register global instructions through Vue Register the global instruction in the direct (ID, [definition]) mode. Then Vue. In the portal file Use() call.
Batch registration instructions, 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 main JS introduced and called
import Vue from 'vue' import Directives from './JS/directives' Vue.use(Directives)
The instruction definition function provides several hook functions (optional):
bind: only called 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 during binding.
Inserted: called when the bound element is inserted into the parent node (it can be called if the parent node exists, and it does not have to exist in the document).
update: called when the template of the bound element is updated, regardless of whether the binding value changes. Front end training By comparing the 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 the instruction is unbound from the element.
Here are some practical Vue customization instructions
Copy and paste instruction v-copy
Long press the command v-longpress
Input box anti shake command v-debounce
No emoticons and special characters v-emoji
Image lazy loading v-LazyLoad
Permission verification instruction v-permission
Realize page watermark v-waterMarker
Drag command v-draggable
usage method
1. Installation dependency
npm install cm-directives --save
2. Registration instruction
import cmDirectives from 'cm-directives' Vue.use(cmDirectives)
v-copy
Requirements: copy text content with one click for right-click pasting.
Idea:
Dynamically create the textarea tag, set the readOnly attribute and move out of the visual area
Assign the value to be copied to the value attribute of the textarea tag and insert it into the body
Check the value textarea and copy it
Remove the textarea inserted in the body
Bind the event on the first call and remove the event on unbinding
const copy = { bind(el, { value }) { el.$value = value el.handler = () => { if (!el.$value) { // When the value is empty, a prompt is given. It 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 recalled under iOS, and move the textarea out of the visual area at the same time textarea.readOnly = 'readonly' textarea.style.position = 'absolute' textarea.style.left = '-9999px' // Assign the value to copy to the value attribute of the textarea tag textarea.value = el.$value // Insert 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 succeeded') // It can be carefully designed according to the project UI } document.body.removeChild(textarea) } // Binding click events 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 an instruction is unbound from an element, the event binding is removed unbind(el) { el.removeEventListener('click', el.handler) }, } export default copy
Use: add v-copy and copied text to Dom
<template> <button v-copy="copyText">copy</button> </template> <script> export default { data() { return { copyText: 'a copy directives', } }, } </script>
v-longpress
Requirements: to achieve long press, the user needs to press and hold the button for a few seconds to trigger the corresponding event
Idea:
Create a timer and execute the function in 2 seconds
When the user presses the button, the mousedown event is triggered to start the timer; The mouseout event is called when the user releases the button.
If the mouseup event is triggered within 2 seconds, the timer is cleared as a normal click event
If the timer is not cleared within 2 seconds, it is determined as a long press, and the associated function can be executed.
In the mobile terminal, the touchstart and touchend events should be considered
const longpress = { bind: function (el, binding, vNode) { if (typeof binding.value !== 'function') { throw 'callback must be a function' } // Define variables let pressTimer = null // Create 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 an instruction is unbound from an element, the event binding is removed unbind(el) { el.removeEventListener('click', el.handler) }, } export default longpress
Use: add v-longpress and callback function to Dom
<template> <button v-longpress="longpress">Long press</button> </template> <script> export default { methods: { longpress () { alert('Long press the instruction to take effect') } } } </script>
v-debounce
Background: in development, some submit save buttons are sometimes clicked many times in a short time, which will repeatedly request the back-end interface, resulting in data confusion. For example, for the submit button of a new form, multiple clicks will add multiple duplicate data.
Requirement: prevent the button from being clicked many times in a short time, Front end training Use the anti shake function to limit one click within a specified time.
Idea:
Define a method to delay execution. If the method is called again 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: add v-debounce and callback function to Dom
<template> <button v-debounce="debounceClick">Anti shake</button> </template> <script> export default { methods: { debounceClick () { console.log('Trigger only once') } } } </script>
v-emoji
Background: in the form input encountered in development, there are often restrictions on the input content, such as unable to input expressions and special characters, and only numbers or letters.
Our normal method is to handle 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>
This code is large and difficult to maintain, so we need to customize an instruction to solve this problem.
Requirements: according to regular expressions, design user-defined instructions for processing form input rules. The following takes the prohibition of inputting expressions 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: add v-emoji to the input box to be verified
<template> <input type="text" v-model="note" v-emoji /> </template>
v-LazyLoad
Background: in e-commerce projects, there are often a large number of pictures, such as banner advertising map, menu navigation map, meituan and other business list header map, etc. The large number of pictures and the large size of pictures often affect the page loading speed and cause bad user experience, so it is imperative to optimize the lazy loading of pictures.
Requirements: implement a lazy image loading instruction to load only the images in the visible area of the browser.
Idea:
The principle of image lazy loading is mainly to judge whether the current image has reached the visual area
Get all the pictures Dom, traverse each picture, and judge whether the current picture is within the visual area
Set the src attribute of the picture if it arrives, otherwise the default picture will be displayed
There are two ways to load images lazily: one is to bind the sroll event to listen, and the other is to use the IntersectionObserver to judge whether the image has reached the visual area, but there is a browser compatibility problem.
The following encapsulates a lazy loading instruction, which is compatible with two methods to judge whether the browser supports IntersectionObserver API. If so, use IntersectionObserver to realize lazy loading. Otherwise, use sroll event listening + throttling.
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) }, // Monitoring el with IntersectionObserver 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 pictures 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
Use to replace the src of the tag in the component with v-LazyLoad
<img v-LazyLoad="xxx.jpg" />
v-permission
Background: in some background management systems, we may need to judge some operation permissions according to the user role. Most of the time, we roughly add v-if / v-show to an element for display and hiding. However, if the judgment conditions are cumbersome and judgment is required in multiple places, the code in this way is not elegant and redundant. web front end training In this case, we can handle it through global user-defined instructions.
Requirement: customize a permission instruction to display and hide DOMS requiring permission judgment.
Idea:
Customize a permission array
Judge whether the user's permission is in this array. If yes, it will be displayed. Otherwise, Dom will be removed
function checkArray(key) { let arr = ['1', '2', '3', '4'] let index = arr.indexOf(key) if (index > -1) { return true // Have authority } 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) { // You do not have permission to remove Dom elements el.parentNode && el.parentNode.removeChild(el) } } }, } export default permission
Use: assign a value to v-permission to judge
<div class="btns"> <!-- display --> <button v-permission="'1'">Permission button 1</button> <!-- Do not display --> <button v-permission="'10'">Permission button 2</button> </div>
vue-waterMarker
Requirement: add background watermark to the whole page
Idea:
Use canvas feature to generate base64 format picture file, and set its font size, color, etc.
Set it as the background picture to realize the page or component watermark 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
Use to set the watermark copy, color and font size
<template> <div v-waterMarker="{text:'lzg copyright',textColor:'rgba(180, 180, 180, 0.4)'}"></div> </template>
The effect is shown in the figure
v-draggable
Requirements: implement a drag and drop instruction, which can drag and drop elements arbitrarily in the visual area of the page.
Idea:
Set the element to be dragged as relative positioning and its parent element as absolute positioning.
Record the current left and top values of the target element when the mouse is pressed (onmousedown).
When the mouse moves (onmousemove), calculate the change values of the horizontal distance and vertical distance of each movement, and change the left and top values of the element
When the mouse is released (onmouseup), a drag is completed
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: add v-draggable to Dom
<template> <div class="el-dialog" v-draggable></div> </template>