Vue自定义指令directive的使用方法分享
1. 一个指令定义对象可以提供如下几个钩子函数(均为可选)
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
update:只要当前元素不被移除,其他操作几乎都会触发这2个生命周期,先触发update后触发componentUpdate。虚拟DOM什么时候更新:只要涉及到元素的隐藏、显示(display)值的改变、内容的改变等都会触发虚拟DOM更新.
componentUpdated:组件更新
unbind:当使用指令的元素被卸载的时候会执行,就是当前元素被移除的时候,只调用一次
Vue.directive 内置了五个钩子函数 :
bind(绑定触发)、inserted(插入触发)、update(更新触发)、componentUpdated(组件更新触发)和unbind(解绑触发)函
// 注册
Vue.directive(
'my-directive'
,{
bind:
function
() {},
inserted:
function
() {},
update:
function
() {},
componentUpdated:
function
() {},
unbind:
function
() {}
})
2.指令钩子函数会被传入以下参数
el:指定所绑定的元素,可以用来直接操作DOM
binding:一个对象,包含以下属性:
name:指令名,不包含前缀v-
value:指令的绑定值,例如:v-my-directive="1+1"中,绑定值为2
oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用,无论值是否改变都可用。
expression: 绑定值的字符串形式。 例如 v-my-directive=“1 + 1” , expression 的值是 “1 + 1”。
arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 “foo”。
modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是{ foo: true, bar: true }。
vnode: Vue 编译生成的虚拟节点
oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
vnode:vue编译生成的虚拟节点
oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用
除了el之外,其他参数都应该是只读的,不能修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
3.使用自定义指令场景的示例
drag.js
import Vue from
'vue'
import { Message } from
'element-ui'
;
// 自定义指令示例1:弹窗拖拽
Vue.directive(
'dialogDrag'
,{
bind(el,binding,vnode,oldVnode){
const dialogHeader = el.querySelector(
'.el-dialog__header'
);
const dialogDom = el.querySelector(
'.el-dialog'
);
dialogHeader.style.cursor =
'move'
/**
* 不同浏览器获取行内样式属性
* ie浏览器: dom元素.currentStyle
* 火狐浏览器:window.getComputedStyle(dom元素, null)
*/
const sty = dialogDom.currentStyle || window.getComputedStyle(dialogDom,
null
)
dialogHeader.onmousedown = (e) => {
//按下鼠标,获取点击的位置 距离 弹窗的左边和上边的距离
//点击的点距离左边窗口的距离 - 弹窗距离左边窗口的距离
const distX = e.clientX - dialogHeader.offsetLeft;
const distY = e.clientY - dialogHeader.offsetTop;
//弹窗的left属性值和top属性值
let styL, styT
//注意在ie中,第一次获取到的值为组件自带50%,移动之后赋值为Px
if
(sty.left.includes(
'%'
)){
styL = +document.body.clientWidth * (+sty.left.replace(/%/g,
''
) / 100)
styT = +document,body.clientHeight * (+sty.top.replace(/%/g,
''
) / 100)
}
else
{
styL = +sty.left.replace(/px/g,
''
);
styT = +sty.top.replace(/px/g,
''
);
}
document.onmousemove =
function
(e) {
//通过事件委托,计算移动距离
const l = e.clientX - distX
const t = e.clientY - distY
//移动当前的元素
dialogDom.style.left = `${l + styL}px`
dialogDom.style.top = `${t + styT}px`
}
document.onmouseup =
function
(e){
document.onmousemove =
null
document.onmouseup =
null
}
}
}
})
//自定义指令示例2:v-dialogDragWidth 可拖动弹窗宽度(右侧边)
Vue.directive(
'dragWidth'
,{
bind(el) {
const dragDom = el.querySelector(
'.el-dialog'
);
const lineEl = document.createElement(
'div'
);
lineEl.style =
'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;'
;
lineEl.addEventListener(
'mousedown'
,
function
(e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
// 当前宽度
const curWidth = dragDom.offsetWidth;
document.onmousemove =
function
(e) {
e.preventDefault();
// 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const l = e.clientX - disX;
if
(l > 0){
dragDom.style.width = `${curWidth + l}px`;
}
};
document.onmouseup =
function
(e) {
document.onmousemove =
null
;
document.onmouseup =
null
;
};
},
false
);
dragDom.appendChild(lineEl);
}
})
// 自定义指令示例3:v-dialogDragWidth 可拖动弹窗高度(右下角)
Vue.directive(
'dragHeight'
,{
bind(el) {
const dragDom = el.querySelector(
'.el-dialog'
);
const lineEl = document.createElement(
'div'
);
lineEl.style =
'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;'
;
lineEl.addEventListener(
'mousedown'
,
function
(e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
const disY = e.clientY - el.offsetTop;
// 当前宽度 高度
const curWidth = dragDom.offsetWidth;
const curHeight = dragDom.offsetHeight;
document.onmousemove =
function
(e) {
e.preventDefault();
// 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const xl = e.clientX - disX;
const yl = e.clientY - disY
dragDom.style.width = `${curWidth + xl}px`;
dragDom.style.height = `${curHeight + yl}px`;
};
document.onmouseup =
function
(e) {
document.onmousemove =
null
;
document.onmouseup =
null
;
};
},
false
);
dragDom.appendChild(lineEl);
}
})
// 自定义指令示例4:图片加载前填充背景色
Vue.directive(
'imgUrl'
,
function
(el,binding){
var
color=Math.floor(Math.random()*1000000);
//设置随机颜色
el.style.backgroundColor=
'#'
+color;
var
img=
new
Image();
img.src=binding.value;
// -->binding.value指的是指令后的参数
img.onload=
function
(){
el.style.backgroundColor=
''
;
el.src=binding.value;
}
})
// 自定义指令示例5:输入框聚焦
Vue.directive(
"focus"
, {
// 当被绑定的元素插入到 DOM 中时……
inserted (el) {
// 聚焦元素
el.querySelector(
'input'
).focus()
},
});
// 自定义指令示例6:设置防抖自定义指令
Vue.directive(
'throttle'
, {
bind: (el, binding) => {
let setTime = binding.value;
// 可设置防抖时间
if
(!setTime) {
// 用户若不设置防抖时间,则默认2s
setTime = 1000;
}
let cbFun;
el.addEventListener(
'click'
, event => {
if
(!cbFun) {
// 第一次执行
cbFun = setTimeout(() => {
cbFun =
null
;
}, setTime);
}
else
{
/*如果多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,
它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用*/
event && event.stopImmediatePropagation();
}
},
true
);
},
});
// 自定义指令示例7:点击按钮操作频繁给出提示
Vue.directive(
'preventReClick'
, {
inserted:
function
(el, binding) {
el.addEventListener(
'click'
, () => {
if
(!el.disabled) {
el.disabled =
true
Message.warning(
'操作频繁'
)
setTimeout(() => {
el.disabled =
false
//可设置时间
}, binding.value || 3000)
}
})
}
})
main.js中引入文件:
import
'@/assets/js/drag.js'
页面使用:
<
template
>
<
div
>
<
el-button
type
=
"text"
@
click
=
"dialogVisible = true"
>点击打开 Dialog</
el-button
>
<
div
style
=
"display:flex"
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
<
img
v-imgUrl
=
"url"
></
img
>
</
div
>
<
div
>
<
el-input
placeholder
=
"请选择日期"
suffix-icon
=
"el-icon-date"
v-model
=
"input1"
></
el-input
>
<
el-input
v-focus
placeholder
=
"请输入内容"
prefix-icon
=
"el-icon-search"
v-model
=
"input2"
></
el-input
>
</
div
>
<
div
>
<
el-button
type
=
"danger"
v-throttle @
click
=
"throttleBtn"
>危险按钮</
el-button
>
<
el-button
@
click
=
"submitForm()"
>创建</
el-button
>
</
div
>
<
el-dialog
title
=
"提示"
v-dialogDrag v-dragWidth v-dragHeight
:visible.sync
=
"dialogVisible"
width
=
"30%"
:before-close
=
"handleClose"
>
<
span
>这是一段信息</
span
>
<
span
slot
=
"footer"
class
=
"dialog-footer"
>
<
el-button
@
click
=
"dialogVisible = false"
>取 消</
el-button
>
<
el-button
type
=
"primary"
@
click
=
"dialogVisible = false"
>确 定</
el-button
>
</
span
>
</
el-dialog
>
</
div
>
</
template
>
<
script
>
export default {
data() {
return {
dialogVisible: false,
url:'//www.baidu.com/img/flexible/logo/pc/result.png',
input1: '',
input2: '',
}
},
methods: {
handleClose(done) {
console.log('弹窗打开')
},
throttleBtn(){
console.log('我的用来测试防抖的按钮')
},
submitForm(){
this.$message.error('Message 消息提示每次只能1个');
}
},
}
</
script
>
<
style
>
img{
width: 100px;
height: 100px;
}
</
style
>
看下效果吧:
首先进入页面,
1.第二个输入框会鼠标聚焦,
2.点击按钮,会有防止重复点击
3.图片加载前有默认背景色
4.弹窗 可以随处移动。右边可拖拽变宽,右下角可以整体变大
