Vue 2.x 常用自定义指令
简介
除了Vue核心功能默认内置指令(v-mode l和 v-show),Vue也允许注册自定义指令。注意,在Vue2.0中,代码复用和抽象的主要形式是组件。然而有的情况下,你仍然需要对普通DOM元素进行底层操作,这时候就会用到自定义指令。
指令的作用:将代码的复用; 将逻辑抽象成组件指令, 它的作用价值在于当开发人员在某些场景下需要对普通DOM元素进行操作。
钩子函数
一个指令定义对象可以提供如下几个钩子函数(均为可选):
bind
: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
: 被绑定元素插入父节点时调用(保证父节点存在,但不一定已被插入文档中)。update
: 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。 指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新(详细的钩子函数参数下见)。componentUpdated
: 指令所在组件的VNode 及其子 VNode 全部更新后调用。unbind
: 只调用一次,指令与元素解绑时调用。
钩子函数参数
指令钩子函数会被传入以下参数:
el
: 指令所绑定的元素,可以用来直接操作DOM。binding
: 一个对象,包含以下属性property(可解构成{ value }
使用):name
: 指令名,不包括v-
前缀。value
: 指令的绑定值,例如v-my-directive="1+1"
中绑定值 为 2。或v-copy="value"
中的value
;value
可以为data中的值,或一个函数。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可以用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
Vue 自定义指令有全局注册和局部注册两种方式
全局注册使用
在vue-cli搭建的项目中:
- 创建指令(示例…创建v-copy):
- 创建
src/directives/index.js
文件
- 在
src/main.js
引入注册并使用
- 在组件中使用:
局部注册使用
- 在组件
export default {}
中使用directives
来定义指令<script> export default { directives: { // 1.指令名称 copy: { bind (el, binding) {}, componentUpdated(el, { value }) {}, // .... }, // 2.指令名称 lazyload: { /*指令*/ } } } </script>
- 局部组件内自定义指令的使用方式和全局相同:
<el-button icon="el-icon-copy-document" v-copy="url">复制</el-button>
分享几个实用的 Vue 自定义指令
v-copy
:复制粘贴指令<template> <div> <el-divider content-position="left">v-copy 复制粘贴</el-divider> <el-input v-model="url" readonly> <template slot="append"> <el-button icon="el-icon-copy-document" v-copy="url">复制</el-button> </template> </el-input> </div> </template> <script> export default { data () { return { url: 'https://gitee.com/evan_origin_admin/vue-directives.git' } } } </script>
const copy = { /** * 初始化 * @param {DOM} el 指令所绑定的元素DOM节点 * @param {*} value 指令的绑定值 即 v-copy="value" 的value值 */ bind(el, { value }) { // 给元素赋值一个$value值,即指令绑定的值 el.$value = value el.handler = () => { // 如果可复制的值为空的时候,给出提示; if (!el.$value) { console.log('无复制内容') Message.error('无复制内容') return } // 动态创建 textarea 标签 const textarea = document.createElement('textarea'); // 将该 textarea 设为 readonly 防止 IOS 下自动唤起键盘,同时将 textarea 移除可视区域 textarea.readOnly = 'readonly'; textarea.style.position = 'absolute'; textarea.style.left = '-9999px'; // 将要copy的值赋值给textarea 标签的value属性 textarea.value = el.$value; // 将textarea 插入到body中 document.body.appendChild(textarea); // 选中值并复制 textarea.select() const result = document.execCommand('Copy'); if (result) { console.log('复制成功'); Message.success('复制成功'); } document.body.removeChild(textarea); } // 绑定点击事件,点击的时候copy值 el.addEventListener('click', el.handler); }, // 当传递进来的值更新的时候触发 componentUpdated(el, { value }) { el.$value = value; }, // 指令与元素解绑的时候,移除事件绑定 unbind(el) { el.removeEventListener('click', el.handler); } } export default copy;
v-longpress
:长按指令:<template> <div> <el-divider content-position="left">v-longpress 长按</el-divider> <el-button v-longpress="longpress">长按</el-button> </div> </template> <script> import {Message} from 'element-ui' export default { methods: { longpress() { Message.success('长按指令生效') } } } </script>
// 需求:实现长按, 用户需要按下并按住按钮几秒钟,触发相应的事件 /** * 1. 创建一个计时器,2s 后执行函数 * 2. 当用户按下按钮时触发 mousedown 事件,启动计时器; * 用户松开按钮时调用 mouseout 事件。 * 3. 如果 mouseup 事件 2s内被触发,就清除计时器,当作一个普通的点击事件。 * 4. 如果 计时器没有在2s内清除,则判定为移除长按,可以执行关联的函数。 * 5. 在移动端要考虑 touchstart, touchend 事件。 */ const longpress = { bind (el, binding) { if (typeof binding.value !== 'function') { throw 'callback must be a function' } let pressTimer = null // 创建计时器(2s后执行函数) let start = e => { if (e.type === 'click' && e.button !== 0) return if (pressTimer === null) { pressTimer = setTimeout(() => { handler() }, 2000) } } // 取消计时器 let cancel = e => { if (pressTimer !== null) { clearTimeout(pressTimer) pressTimer = null } } // 运行函数 const handler = e => { binding.value() } // 添加事件监听器 el.addEventListener('mousedown', start) el.addEventListener('touchstart', start) // 取消计时器 el.addEventListener('click', cancel) el.addEventListener('mouseout', cancel) el.addEventListener('touchend', cancel) el.addEventListener('touchcancel', cancel) }, // 当传递进来的值更新的时候触发 componentUpdated(el, { value }) { el.$value = value }, // 指令与元素解绑的时候,移除事件绑定 unbind(el) { el.removeEventListener('click', el.handler) } } export default longpress
v-debounce
: 输入框防抖指令<template> <div> <el-divider content-position="left">v-debounce 防抖</el-divider> <el-button v-debounce="debounce">防抖</el-button> </div> </template> <script> import { Message } from 'element-ui' export default { methods: { debounce() { Message.success('防抖执行') } } } </script>
/** * 背景: * 在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口, * 造成数据的混乱和服务端的压力,比如新增表单的提交按钮,多次点击就会新增很多条重复的数据。 * * 1. 定义一个延迟执行的方法,如果在延迟事件内再调用该方法,则重新计算执行时间。 * 2. 将事件绑定再click方法上。 */ // 在指定的时间段内,多次点击只会执行一次 const debounce = { inserted(el ,binding) { let timer el.addEventListener('click', () => { // 如果再等待执行中就清除,重新开始计算执行 if (timer) clearTimeout(timer) timer = setTimeout(() => { binding.value() // 延迟执行回调方法 }, 1000) }) } } export default debounce
v-emoji
: 禁止表情及特殊字符<template> <div> <el-divider content-position="left">v-emoji 禁止表情特殊符号的输入</el-divider> <input v-model="name" v-emoji /> </div> </template> <script> export default { data () { return { name: '' } } } </script>
/** * 开发中遇到表单输入,往往会有对输入内容的限制,如不能输入表情和特殊字符,只能输入数字或字母等 * 我们常规的做法是在一个表单的 on-change 事件上做处理 * 但是这样的方式代码量很大,不能复用,不好维护。这时自定义指令就可以解决这个问题 * * 根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例 */ let findEle = (parent, type) => { return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type) } // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createEvent const trigger = (el, type) => { // 创建一个指定类型的事件 const e = document.createEvent('HTMLEvents') // 定义事件名为 type e.initEvent(type, true, true) // 触发对象可以是任何元素或其他事件目标 el.dispatchEvent(e) } const emoji = { bind: function (el, binding) { // 正则规则可根据需求自定义 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
v-waterMarker
: 实现页面水印<template> <div style="height: 500px" v-waterMarker="{text:'版权所有',textColor:'rgba(100, 100, 100, 0.4)'}"> <el-divider content-position="left">v-waterMarker 给页面添加水印</el-divider> </div> </template>
/** * 给整个页面添加背景水印 * 1.使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。 * 2.将其设置为背景图片,从而实现页面或组件水印效果 */ /** * @param {*} str 文字水印 * @param {*} parentNode * @param {*} font 字体 * @param {*} textColor 文字颜色 */ function addWaterMarker(str, parentNode, font, textColor) { // 水印文字,父元素,字体,文字颜色 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