1. Custom instruction registration
User defined instructions can only be used after registration. Vue provides two registration methods:
- Global registration: Vue directive(id, [definition]); ID: instruction name, [definition]: user-defined object or function object. The function to be realized by the instruction is defined in this object.
- Local registration: use the directives option to register in the option object of the Vue instance
new Vue({ el: '#app', directives: { // Register a local custom directive focue: { ... } } })
2. Hook function
The functions of user-defined instructions are implemented in user-defined objects, while the definition objects are composed of hook functions. Vue provides the following hook functions, which are 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. Unnecessary template updates are ignored by comparing the 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
- Facial expressions and special characters are prohibited v-emoji
- Image lazy loading v-LazyLoad
- Permission verification instruction v-permission
- Realize page watermark v-waterMarker
- Drag command v-draggable
2.1,v-copy
- Requirements: copy text content with one click and paste with the right mouse button.
- 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 successful') // 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>
2.2,v-longpress
- Requirements: to realize 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, clear the timer 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>
2.3,v-debounce
- Background: in development, some submit and 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, and use the anti shake function to limit that it can only be clicked once in the specified time.
- Idea:
- Define a method that delays execution. If the method is called again within the delay time, the execution time will be recalculated.
- Bind time to the click method.
const debounce = { inserted: function (el, binding) { let timer el.addEventListener('keyup', () => { 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>
2.4,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.
- Idea: 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|rns,.?!,. ?!...—&$=()-+/*{}[]]|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|rns,.?!,.?!... - & $= () - + / * {} []] |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>
2.5,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 volume 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 realized by the core logic of judging 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 sroll events to listen, and the other is to use 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)}}, })}, / / initialize init (EL, Val, DEF) {el. SetAttribute ('data SRC ', Val) el. SetAttribute ('src', DEF)}, //Use 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.removeaattribute ('data SRC ')}}} io Observe (EL)}, / / listen to the scroll event listenerscroll (EL) {const handler = lazyload.throttle (lazyload.load, 300) lazyload.load (EL) window. Addeventlistener ('scroll ', () = > {handler (EL)})}, //Load real picture load (EL) {const windowsheight = document. Documentelement. Clientheight const eltop = el. Getboundingclientrect(). Top const elbtm = el. Getboundingclientrect(). Bottom const realsrc = el.dataset.src if (eltop - windowsheight < 0 & & elbtm > 0) {if (realsrc) {el.src = realsrc el.removeattribute ('data-src') } } }, //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" />
2.6,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 only inelegant but also redundant. In this case, we can handle it through global user-defined instructions.
- Requirement: customize a permission instruction to display and hide the Dom that needs 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 // 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) {/ / do not have permission to remove Dom element 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 show --> <button v-permission="'10'">Permission button 2</button></div>
2.7,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 watermark effect of 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
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>
2.8,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 value of the horizontal distance and vertical distance of each movement, and change the left and top values of the element
- A drag is completed 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: add v-draggable to Dom
<template> <div class="el-dialog" v-draggable></div> </template>