vue2 自定义指令
官网链接https://v2.cn.vuejs.org/v2/guide/custom-directive.html
指令注册
这里是一个 Vue2 的指令合集,详细的指令移步下面具体的指令文章,现在我们在这里要介绍如何在项目中统一管理和使用这些指令。
注册指令
单文件引入注册
import Vue from 'vue'
import ellipsis from './ellipsis'
// import other directives
const directives = {
ellipsis
// other directives
}
Object.keys(directives).forEach(name => Vue.directive(name, directives[name]))
全局注册
使用 Vue.deirect
注册。
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
使用
<div v-指令名称 />
1.回到顶部指令 v-backtop
v-backtop
使用该指令可以让页面或指定元素回到顶部。
可选指定元素,如果不指定则全局页面回到顶部。可选在元素偏移多少 px
后显示 backtop
元素,例如在滚动 400px
后显示回到顶部按钮。
代码 Code
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 | 可选 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-backtop
property
,如下表示在 id
为 app
的元素滚动 400px
后显示绑定指令的元素:
<div v-backtop:app="400"> 回到顶部 </div>
也可以这样使用,表示为一直显示绑定指令的元素,并且是全局页面回到顶部:
<div v-backtop> 回到顶部 </div>
2.徽标指令 v-badge
v-badge
使用该指令在元素右上角显示徽标。
支持配置徽标的背景颜色、徽标形状;支持传入徽标上显示的数字。
代码 Code
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 | 可选 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-badge
property
,如下:
<div v-badge.dot.info="badgeCount" style="height:50px;width:50px;background:#999"> </div>
3.文本内容复制指令 v-copy
v-copy
使用该指令可以复制元素的文本内容。
支持单击复制 v-copy
、双击复制 v-copy.dblclick
、点击icon复制 v-copy.icon
三种模式。默认使用单击复制。
代码 Code
export default {
bind (el, binding) {
// 双击触发复制
if (binding.modifiers.dblclick) {
el.addEventListener('dblclick', () => handleClick(el.innerText))
el.style.cursor = 'copy'
}
// 点击icon触发复制
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')
// alert('复制成功')
}
参数 Attributes
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
dblclick | 双击复制文本内容 | / | String | 可选 |
icon | 单击icon复制文本内容 | / | String | 可选 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-copy
property
,如下:
<div v-copy> 单击复制 </div>
<div v-copy.dblclick> 双击复制 </div>
<div v-copy.icon> icon复制 </div>
4.文字超出省略 v-ellipsis
v-ellipsis
使用该指令当文字内容超出宽度(默认100 px)时自动变为省略形式。等同于使用 css:
width: 100px;
whiteSpace: nowrap
overflow: hidden;
textOverflow: ellipsis;
代码 Code
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 | 必填 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-ellipsis
property
,如下:
<div v-ellipsis:100> 需要省略的文字是阿萨的副本阿萨的副本阿萨的副本阿萨的副本</div>
5.空状态指令 v-empty
v-empty
使用该指令可以显示缺省的空状态。
可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无数据)、以及标示是否显示空状态(必选)。
代码 Code
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 | 必须 |
使用 Usage
然后你可以在模板中任何元素上使用新的 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,
},
6.元素点击范围扩展指令 v-expandClick
v-expandClick
使用该指令可以扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。
可传入的参数为:上右下左扩展的范围,单位 px,默认向外扩展 10px
。
代码 Code
export default function (el, binding) {
const s = document.styleSheets[document.styleSheets.length - 1]
const DEFAULT = -10 // 默认向外扩展10px
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 { /* IE */
s.addRule('.expand_click_range::before', ruleStr, -1)
}
}
参数 Attributes
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
top, right, bottom, left | 上右下左扩展宽度(逗号分割), 单位px | 10,10,10,10 | String | 可填 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-expandClick
property
,如下:
<div v-expandClick="20,30,40,50" @click="glabClickoutside"> 点击范围扩大</div>
7.元素全屏指令 v-screenfull
v-screenfull
全屏指令,点击元素进行全屏/退出全屏的操作。可选元素后面是否插入 element-ui
的全屏图标 el-icon-full-screen
。
代码 Code
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 | 可选 |
使用 Usage
然后你可以在模板中任何元素上使用新的 v-screenfull
property
,如下:
<div v-screenfull.icon> 全屏 </div>
8.元素说明指令 v-tooltip
v-tooltip
为元素添加说明,如同 element-ui
的 el-tooltip
。
代码 Code
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
})
// 创建一个 tooltip 实例并返回 dom 节点
const component = new tooltip().$mount()
return component.$el
}
参数 Attributes
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
content | 传给指令的参数。例如 v-tooltip:content 中,参数为 “content” ,tooltip中展示的内容为:“content” | / | String | 可选 |
tootipParams | element-ui 支持的 tooltip 属性 | / | Object | 可选 |
使用 Usage
然后你可以在模板中任何元素上使用新的 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',
}
}
}
vue3自定义指令(相比vue2属于破坏性更新)
官网链接https://cn.vuejs.org/api/sfc-script-setup.html#using-custom-directives
1. Vue3指令的钩子函数
- created 元素初始化的时候
- beforeMount 指令绑定到元素后调用 只调用一次
- mounted 元素插入父级dom调用
- beforeUpdate 元素被更新之前调用
- update 这个周期方法被移除 改用updated
- beforeUnmount 在元素被移除前调用
- unmounted 指令被移除后调用 只调用一次
Vue2 指令 bind inserted update componentUpdated unbind
2.在setup内定义局部指令
但这里有一个需要注意的限制:必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
<template>
<button @click="show = !show">开关{{show}} ----- {{title}}</button>
<Dialog v-move-directive="{background:'green',flag:show}"></Dialog>
</template>
const vMoveDirective: Directive = {
created: () => {
console.log("初始化====>");
},
beforeMount(...args: Array<any>) {
// 在元素上做些操作
console.log("初始化一次=======>");
},
mounted(el: any, dir: DirectiveBinding<Value>) {
el.style.background = dir.value.background;
console.log("初始化========>");
},
beforeUpdate() {
console.log("更新之前");
},
updated() {
console.log("更新结束");
},
beforeUnmount(...args: Array<any>) {
console.log(args);
console.log("======>卸载之前");
},
unmounted(...args: Array<any>) {
console.log(args);
console.log("======>卸载完成");
},
};
3.生命周期钩子参数详解
第一个 el 当前绑定的DOM 元素
第二个 binding
instance:使用指令的组件实例。
value:传递给指令的值。例如,在 v-my-directive=“1 + 1” 中,该值为 2。
oldValue:先前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否有更改都可用。
arg:传递给指令的参数(如果有的话)。例如在 v-my-directive:foo 中,arg 为 “foo”。
modifiers:包含修饰符(如果有的话) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}。
dir:一个对象,在注册指令时作为参数传递。例如,在以下指令中
第三个 当前元素的虚拟DOM 也就是Vnode
第四个 prevNode 上一个虚拟节点,仅在 beforeUpdate 和 updated 钩子中可用
函数简写
你可能想在 mounted 和 updated 时触发相同行为,而不关心其他的钩子函数。那么你可以通过将这个函数模式实现
<template>
<div>
<input v-model="value" type="text" />
<A v-move="{ background: value }"></A>
</div>
</template>
<script setup lang='ts'>
import A from './components/A.vue'
import { ref, Directive, DirectiveBinding } from 'vue'
let value = ref<string>('')
type Dir = {
background: string
}
const vMove: Directive = (el, binding: DirectiveBinding<Dir>) => {
el.style.background = binding.value.background
}
</script>
<style>
</style>
1.自定义拖拽指令
<template>
<div v-move class="box">
<div class="header"></div>
<div>
内容
</div>
</div>
</template>
<script setup lang='ts'>
import { Directive } from "vue";
const vMove: Directive = {
mounted(el: HTMLElement) {
let moveEl = el.firstElementChild as HTMLElement;
const mouseDown = (e: MouseEvent) => {
//鼠标点击物体那一刻相对于物体左侧边框的距离=点击时的位置相对于浏览器最左边的距离-物体左边框相对于浏览器最左边的距离
console.log(e.clientX, e.clientY, "-----起始", el.offsetLeft);
let X = e.clientX - el.offsetLeft;
let Y = e.clientY - el.offsetTop;
const move = (e: MouseEvent) => {
el.style.left = e.clientX - X + "px";
el.style.top = e.clientY - Y + "px";
console.log(e.clientX, e.clientY, "---改变");
};
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", () => {
document.removeEventListener("mousemove", move);
});
};
moveEl.addEventListener("mousedown", mouseDown);
},
};
</script>
<style lang='less'>
.box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 1px solid #ccc;
.header {
height: 20px;
background: black;
cursor: move;
}
}
</style>
2.自定义权限指令
<template>
<div class="btns">
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive, } from 'vue'
import type {Directive} from 'vue'
//permission
localStorage.setItem('userId','xiaoman-zs')
//mock后台返回的数据
const permission = [
'xiaoman-zs:shop:edit',
'xiaoman-zs:shop:create',
'xiaoman-zs:shop:delete'
]
const userId = localStorage.getItem('userId') as string
const vHasShow:Directive<HTMLElement,string> = (el,bingding) => {
if(!permission.includes(userId+':'+ bingding.value)){
el.style.display = 'none'
}
}
</script>
<style scoped lang='less'>
.btns{
button{
margin: 10px;
}
}
</style>
3.图片懒加载
滑动到可是区域加载图片
<template>
<div>
<div v-for="item in arr">
<img height="500" :data-index="item" v-lazy="item" width="360" alt="">
</div>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
import type { Directive } from 'vue'
const images: Record<string, { default: string }> = import.meta.globEager('./assets/images/*.*')
let arr = Object.values(images).map(v => v.default)
let vLazy: Directive<HTMLImageElement, string> = async (el, binding) => {
let url = await import('./assets/vue.svg')
el.src = url.default;
let observer = new IntersectionObserver((entries) => {
console.log(entries[0], el)
if (entries[0].intersectionRatio > 0 && entries[0].isIntersecting) {
setTimeout(() => {
el.src = binding.value;
observer.unobserve(el)
}, 2000)
}
})
observer.observe(el)
}
</script>
<style scoped lang='less'></style>
以上个别内容收集与网络