Vue 自定义指令合集
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 你可以将一些 css 样式抽象到指令中,也可以将一些 js 操作放到指令中去执行。就使用上来说,指令不用像组件一样需要引入和注册,注册后使用非常简洁方便。 对于在项目中常用到的指令,在此做了一个合集介绍,附源码可以直接在项目中使用。
v-debounce
背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。
需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。
思路:
定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
将时间绑定在 click 方法上。
const debounce = {
inserted: function (el, binding) {
let timer
el.addEventListener('keyup', () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
binding.value()
}, 1000)
})
},
}
export default debounce
使用:给 Dom 加上 v-debounce 及回调函数即可
<template>
<button v-debounce="debounceClick">防抖</button>
</template>
<script>
export default {
methods: {
debounceClick () {
console.log('只触发一次')
}
}
}
</script>
v-emoji
背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。
我们常规方法是在每一个表单的 on-change 事件上做处理。
<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>
这样代码量比较大而且不好维护,所以我们需要自定义一个指令来解决这问题。
需求:根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。
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) {
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
使用:将需要校验的输入框加上 v-emoji 即可
<template>
<input type="text" v-model="note" v-emoji />
</template>
v-LazyLoad
背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。
需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。
思路:
图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的 拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内 如果到了就设置图片的 src 属性,否则显示默认图片 图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。
下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
const LazyLoad = {
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)
}
},
})
},
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
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)
},
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
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(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
使用,将组件内 标签的 src 换成 v-LazyLoad
<img v-LazyLoad="xxx.jpg" />
v-permission
背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。
需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。
思路:
自定义一个权限数组
判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom
function checkArray(key) {
let arr = ['1', '2', '3', '4']
let index = arr.indexOf(key)
if (index > -1) {
return true
} else {
return false
}
}
const permission = {
inserted: function (el, binding) {
let permission = binding.value
if (permission) {
let hasPermission = checkArray(permission)
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
},
}
export default permission
使用:给 v-permission 赋值判断即可
<div class="btns">
<button v-permission="'1'">权限按钮1</button>
<button v-permission="'10'">权限按钮2</button>
</div>
vue-waterMarker
需求:给整个页面添加背景水印
思路:
使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
将其设置为背景图片,从而实现页面或组件水印效果
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
使用,设置水印文案,颜色,字体大小即可
<template>
<div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
</template>
效果如图所示
v-draggable
需求:实现一个拖拽指令,可在页面可视区域任意拖拽元素。
思路:
设置需要拖拽的元素为相对定位,其父元素为绝对定位。
鼠标按下(onmousedown) 时记录目标元素当前的 left 和 top 值。
鼠标移动(onmousemove) 时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
鼠标松开(onmouseup) 时完成一次拖拽
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
使用: 在 Dom 上加上 v-draggable 即可
<template>
<div class="el-dialog" v-draggable></div>
</template>
v-longpress
需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件
思路:
1.创建一个计时器, 2 秒后执行函数
2.当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。
3.如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
4.如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
5.在移动端要考虑 touchstart ,touchend 事件
const longpress = {
bind: function (el, binding, vNode) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function'
}
let pressTimer = null
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(e)
}
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
使用:给 Dom 加上 v-longpress 及回调函数即可
<template>
<button v-longpress="longpress">长按</button>
</template>
<script>
export default {
methods: {
longpress () {
alert('长按指令生效')
}
}
}
</script>
v-copy
const copy = {
bind(el, { value }) {
el.$value = value
el.handler = () => {
if (!el.$value) {
console.log('无复制内容')
return
}
const textarea = document.createElement('textarea')
textarea.readOnly = 'readonly'
textarea.style.position = 'absolute'
textarea.style.left = '-9999px'
textarea.value = el.$value
document.body.appendChild(textarea)
textarea.select()
const result = document.execCommand('Copy')
if (result) {
console.log('复制成功')
}
document.body.removeChild(textarea)
}
el.addEventListener('click', el.handler)
},
componentUpdated(el, { value }) {
el.$value = value
},
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
export default copy
使用:给 Dom 加上 v-copy 及复制的文本即可
<template>
<button v-copy="copyText">复制</button>
</template>
<script> export default {
data() {
return {
copyText: 'a copy directives',
}
},
}
</script>
元素点击范围扩展指令 v-expandClick
使用该指令可以隐式的扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。 可传入的参数为:上右下左扩展的范围,单位 px,默认向外扩展 10px。指令的代码如下:
export default function (el, binding) {
const s = document.styleSheets[document.styleSheets.length - 1]
const DEFAULT = -10
const ruleStr = `content:"";position:absolute;top:-${top || DEFAULT}px;bottom:-${bottom || DEFAULT}px;right:-${right || DEFAULT}px;left:-${left || DEFAULT}px;`
const [top, right, bottom, left] = binding.expression && binding.expression.split(',') || []
const classNameList = el.className.split(' ')
el.className = classNameList.includes('expand_click_range') ? classNameList.join(' ') : [...classNameList, 'expand_click_range'].join(' ')
el.style.position = el.style.position || "relative"
if (s.insertRule) {
s.insertRule('.expand_click_range::before' + '{' + ruleStr + '}', s.cssRules.length)
} else {
s.addRule('.expand_click_range::before', ruleStr, -1)
}
}
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
top, right, bottom, left | 上右下左扩展宽度(逗号分割) | | | | 单位px | 10,10,10,10 | String | 可填 | |
然后你可以在模板中任何元素上使用新的 v-expandClick property,如下:
<div v-expandClick="20,30,40,50" @click="glabClickoutside"> 点击范围扩大</div>
文本内容复制指令 v-copy
使用该指令可以复制元素的文本内容(指令支持单击复制 v-copy、双击复制 v-copy.dblclick、点击icon复制 v-copy.icon 三种模式),不传参数时,默认使用单击复制。 指令的代码如下:
export default {
bind (el, binding) {
if (binding.modifiers.dblclick) {
el.addEventListener('dblclick', () => handleClick(el.innerText))
el.style.cursor = 'copy'
}
else if (binding.modifiers.icon) {
if (el.hasIcon) return
const iconElement = document.createElement('i')
iconElement.setAttribute('class', 'el-icon-document-copy')
iconElement.setAttribute('style', 'margin-left:5px')
el.appendChild(iconElement)
el.hasIcon = true
iconElement.addEventListener('click', () => handleClick(el.innerText))
iconElement.style.cursor = 'copy'
}
else {
el.addEventListener('click', () => handleClick(el.innerText))
el.style.cursor = 'copy'
}
}
}
function handleClick (text) {
if (!document.getElementById('copyTarget')) {
const copyTarget = document.createElement('input')
copyTarget.setAttribute('style', 'position:fixed;top:0;left:0;opacity:0;z-index:-1000;')
copyTarget.setAttribute('id', 'copyTarget')
document.body.appendChild(copyTarget)
}
const input = document.getElementById('copyTarget')
input.value = text
input.select()
document.execCommand('copy')
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
dblclick | 双击复制文本内容 | / | String | 可选 | icon | 单击icon复制文本内容 | / | String | 可选 |
然后你可以在模板中任何元素上使用新的 v-copy property,如下:
<div v-copy> 单击复制 </div>
<div v-copy.dblclick> 双击复制 </div>
<div v-copy.icon> icon复制 </div>
元素全屏指令 v-screenfull
全屏指令,点击元素进行全屏/退出全屏的操作。支持元素后面是否插入 element-ui 的全屏图标 el-icon-full-screen。 指令的代码如下:
import screenfull from 'screenfull'
export default {
bind (el, binding) {
if (binding.modifiers.icon) {
if (el.hasIcon) return
const iconElement = document.createElement('i')
iconElement.setAttribute('class', 'el-icon-full-screen')
iconElement.setAttribute('style', 'margin-left:5px')
el.appendChild(iconElement)
el.hasIcon = true
}
el.style.cursor = el.style.cursor || 'pointer'
el.addEventListener('click', () => handleClick())
}
}
function handleClick () {
if (!screenfull.isEnabled) {
alert('浏览器不支持全屏')
return
}
screenfull.toggle()
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
icon | 是否添加 icon | / | String | 可选 |
然后你可以在模板中任何元素上使用新的 v-screenfull property,如下:
<div v-screenfull.icon> 全屏 </div>
元素说明指令 v-tooltip
为元素添加说明,如同 element-ui 的 el-tooltip(问号 icon 在鼠标覆盖后,展示说明文字)。 指令的代码如下:
import Vue from 'vue'
export default function (el, binding) {
if (el.hasIcon) return
const iconElement = structureIcon(binding.arg, binding.value)
el.appendChild(iconElement)
el.hasIcon = true
}
function structureIcon (content, attrs) {
let attrStr = ''
for (let key in attrs) {
attrStr += `${key}=${attrs[key]} `
}
const a = `<el-tooltip content=${content} ${attrStr}><i class="el-icon-question" style="margin:0 10px"></i></el-tooltip>`
const tooltip = Vue.extend({
template: a
})
const component = new tooltip().$mount()
return component.$el
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
content | 传给指令的参数。例如 v-tooltip:content 中,参数为 “content” ,tooltip中展示的内容为:“content” | / | String | 可选 | tootipParams | element-ui 支持的 tooltip 属性 | / | Object | 可选 |
然后你可以在模板中任何元素上使用新的 v-tooltip property,如下:
<div v-tooltip:content='tootipParams'> 提示 </div>
举例:
<div v-tooltip:提示内容为XXX1> 提示1</div>
<div v-tooltip:提示内容为XXX='tootipParams'> 提示2 </div>
为指令传入 element-ui 支持的参数:
data() {
return {
tootipParams: {
placement: 'top',
effect: 'light',
}
}
}
文字超出省略指令 v-ellipsis
使用该指令当文字内容超出宽度(默认 100 px)时自动变为省略形式。等同于使用 css:
width: 100px;
whiteSpace: nowrap
overflow: hidden;
textOverflow: ellipsis;
使用指令效果:
image.png 指令的代码如下:
export default function (el, binding) {
el.style.width =`${binding.arg || 100}px`
el.style.whiteSpace = 'nowrap'
el.style.overflow = 'hidden';
el.style.textOverflow = 'ellipsis';
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
width | 元素宽度 | 100 | Number | 必填 |
然后你可以在模板中任何元素上使用新的 v-ellipsis property,如下:
<div v-ellipsis:100> 需要省略的文字是阿萨的副本阿萨的副本阿萨的副本阿萨的副本</div>
回到顶部指令 v-backtop
使用该指令可以让页面或指定元素回到顶部。 可选指定元素,如果不指定则全局页面回到顶部。可选在元素偏移多少 px 后显示 backtop 元素,例如在滚动 400px 后显示回到顶部按钮。 图片
指令的代码如下:
export default {
bind (el, binding, vnode) {
el.addEventListener('click', () => {
const target = binding.arg ? document.getElementById(binding.arg) : window
target.scrollTo({
top: 0,
behavior: 'smooth'
})
})
},
update (el, binding, vnode) {
const target = binding.arg ? document.getElementById(binding.arg) : window
if (binding.value) {
target.addEventListener('scroll', (e) => {
if (e.srcElement.scrollTop > binding.value) {
el.style.visibility = 'unset'
} else {
el.style.visibility = 'hidden'
}
})
}
if (target.scrollTop < binding.value) {
el.style.visibility = 'hidden'
}
},
unbind (el) {
const target = binding.arg ? document.getElementById(binding.arg) : window
target.removeEventListener('scroll')
el.removeEventListener('click')
}
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
id | 给需要回到顶部的元素添加的id | / | String | 可选 | offset | 偏移距离为 height 时显示指令绑定的元素 | / | Number | 可选 |
然后你可以在模板中任何元素上使用新的 v-backtop property,如下表示在 id 为 app 的元素滚动 400px 后显示绑定指令的元素:
<div v-backtop:app="400"> 回到顶部 </div>
也可以这样使用,表示为一直显示绑定指令的元素,并且是全局页面回到顶部:
<div v-backtop> 回到顶部 </div>
空状态指令 v-empty
使用该指令可以显示缺省的空状态。可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无数据)、以及标示是否显示空状态(必选)。
指令的代码如下:
import Vue from "vue";
export default {
update (el, binding, vnode) {
el.style.position = el.style.position || 'relative'
const { offsetHeight, offsetWidth } = el
const { visible, content, img } = binding.value
const image = img ? `<img src="${img}" height="30%" width="30%"></img>` : ''
const defaultStyle = "position:absolute;top:0;left:0;z-index:9999;background:#fff;display:flex;justify-content: center;align-items: center;"
const empty = Vue.extend({
template: `<div style="height:${offsetHeight}px;width:${offsetWidth}px;${defaultStyle}">
<div style="text-align:center">
<div>${image}</div>
<div>${content || '暂无数据'}</div>
</div>
</div>`
})
const component = new empty().$mount().$el
if (visible) {
el.appendChild(component)
} else {
el.removeChild(el.lastChild)
}
},
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
emptyValue | 包含文字内容 content、图片 img、是否显示 visible,仅 visible 必传 | / | Object | 必须 |
然后你可以在模板中任何元素上使用新的 v-empty property,如下传入对象 emptyValue:
<div style="height:500px;width:500px" v-empty="emptyValue">
原本内容
需要传入一个参数对象,例如显示文字为:暂无列表,图片路径为 …/…/assets/images/blue_big.png,控制标示 visible:
emptyValue = {
content: '暂无列表',
img: require('../../assets/images/blue_big.png'),
visible: true,
},
徽标指令 v-badge
使用该指令在元素右上角显示徽标。 支持配置徽标的背景颜色、徽标形状;支持传入徽标上显示的数字。
指令的代码如下:
import Vue from 'vue'
const SUCCESS = '#72c140'
const ERROR = '#ed5b56'
const WARNING = '#f0af41'
const INFO = '#4091f7'
const HEIGHT = 20
let flag = false
export default {
update (el, binding, vnode) {
const { modifiers, value } = binding
const modifiersKey = Object.keys(modifiers)
let isDot = modifiersKey.includes('dot')
let backgroundColor = ''
if (modifiersKey.includes('success')) {
backgroundColor = SUCCESS
} else if (modifiersKey.includes('warning')) {
backgroundColor = WARNING
} else if (modifiersKey.includes('info')) {
backgroundColor = INFO
} else {
backgroundColor = ERROR
}
const targetTemplate = isDot
? `<div style="position:absolute;top:-5px;right:-5px;height:10px;width:10px;border-radius:50%;background:${backgroundColor}"></div>`
: `<div style="background:${backgroundColor};position:absolute;top:-${HEIGHT / 2}px;right:-${HEIGHT / 2}px;height:${HEIGHT}px;min-width:${HEIGHT}px;border-radius:${HEIGHT / 2}px;text-align:center;line-height:${HEIGHT}px;color:#fff;padding:0 5px;">${value}</div>`
el.style.position = el.style.position || 'relative'
const badge = Vue.extend({
template: targetTemplate
})
const component = new badge().$mount().$el
if (flag) {
el.removeChild(el.lastChild)
}
el.appendChild(component)
flag = true
}
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
normal、dot | 徽标形状normal为正常徽标;dot 仅为一个点 | normal | String | 可选 | success、error、info、warning | 徽标颜色 | error | String | 可选 | number | 徽标上显示的数字 | / | Number | 可选 |
然后你可以在模板中任何元素上使用新的 v-badge property,如下:
<div v-badge.dot.info="badgeCount" style="height:50px;width:50px;background:#999"> </div>
拖拽指令 v-drag
使用该指令可以对元素进行拖拽。 指令的代码如下:
export default {
let _el = el
document.onselectstart = function() {
return false
}
_el.onmousedown = e => {
let disX = e.clientX - _el.offsetLeft
let disY = e.clientY - _el.offsetTop
document.onmousemove = function(e){
let l = e.clientX - disX
let t = e.clientY - disY;
_el.style.left = l + "px"
_el.style.top = t + "px"
}
document.onmouseup = e => {
document.onmousemove = document.onmouseup = null
}
return false
}
}
然后你可以在模板中任何元素上使用新的 v-drag property,如下:
<div v-drag> 支持拖拽的元素 </div>
响应缩放指令 v-resize
使用该指令可以响应元素宽高改变时执行的方法。 指令的代码如下:
export default {
bind(el, binding) {
let width = '', height = '';
function isReize() {
const style = document.defaultView.getComputedStyle(el);
if (width !== style.width || height !== style.height) {
binding.value();
}
width = style.width;
height = style.height;
}
el.__timer__ = setInterval(isReize, 300);
},
unbind(el) {
clearInterval(el.__timer__);
}
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
resize() | 传入元素改变 size 后执行的方法 | / | Function | 必选 |
然后你可以在模板中任何元素上使用新的 v-resize property,如下:
// 传入 resize() 方法
<div v-resize="resize"></div>
字符串整形指令 v-format
使用该指令可以修改字符串,如使用 v-format.toFixed 保留两位小数、 v-format.price 将内容变成金额(每三位逗号分隔),可以同时使用,如 v-format.toFixed.price。 例如将数字 243112.331 变成 243112.33,或 243,112.33。 指令的代码如下:
export default {
update (el, binding, vnode) {
const { value, modifiers } = binding
if (!value) return
let formatValue = value
if (modifiers.toFixed) {
formatValue = value.toFixed(2)
}
console.log(formatValue)
if (modifiers.price) {
formatValue = formatNumber(formatValue)
}
el.innerText = formatValue
},
}
function formatNumber (num) {
num += '';
let strs = num.split('.');
let x1 = strs[0];
let x2 = strs.length > 1 ? '.' + strs[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2
}
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|
toFixed | 保留两位小数 | / | String | 可选 | price | 整形成金额(三位逗号分隔) | / | String | 可选 |
然后你可以在模板中任何元素上使用新的 v-format property,如下:
<div v-format.toFixed.price="123333"> 123 </div>
如何使用这些指令?
为了便于管理指令,我们将每个指令都存在于单独的 js 文件中。在项目的 src 下建一个 directives 目录,目录下新建 index.js 文件用于引入并注册指令。
├── src
| ├── directive
| | ├── index.js
| | ├── backtop.js
| | ├── badge.js
| | ├── copy.js
| | ├── ellipsis.js
| | ├── empty.js
| | ├── expandClick.js
| | ├── screenfull.js
| | └── tooltips.js
| ├── main.js
举个🌰: directives 目录下新建 ellipsis.js 文件:
export default function (el, binding) {
el.style.width = binding.arg || 100 + 'px'
el.style.whiteSpace = 'nowrap'
el.style.overflow = 'hidden';
el.style.textOverflow = 'ellipsis';
}
directives 的 index.js 文件中引入 ellipsis 指令并注册:
import Vue from 'vue'
import ellipsis from './ellipsis'
const directives = {
ellipsis
}
Object.keys(directives).forEach(name => Vue.directive(name, directives[name]))
最后在 mian.js 中引入 index.js 文件:
import '@/directives/index'
这样就可以正常使用这些指令了:
<div v-指令名称 />
directives:{
copy:{
bind:test,
}
}
总结
我们常常在引入全局功能时,主要都是写于 js 文件、组件中。不同于他们在使用时每次需要引用或注册,在使用上指令更加简洁。 除了将功能封装成组件,还可以多多考虑将一些简洁实用的功能放到 deirect 中。例如:常用的 css 样式、js 的一些操作、utils 中的一些工具方法、甚至是一个完整的组件都可以放进去(不过需要考虑一下性能和复杂度)。
|