全局指令
// 注册
Vue.directive('my-directive', {
bind: function () {},
inserted: function () {},
update: function () {},s
componentUpdated: function () {},
unbind: function () {}
});
// 注册 (指令函数)
Vue.directive('my-directive', function () {
// 这里将会被 `bind` 和 `update` 调用
});
// getter,返回已注册的指令
var myDirective = Vue.directive('my-directive');
私有指令
<script>
export default {
directives: {
私有自定义指令名称: {
/* 私有自定义指令配置对象 */
}
}
};
</script>
自定义指令
案例
import Vue from 'vue';
const color = Vue.directive('color', {
bind: function (el, binding) {
// binding.value 就是指令传入数据
el.style.color = binding.value;
}
});
export default color;
使用:
<h1 v-color="'red'">测试 v-color 指令</h1>
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改 变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
接下来我们来看一下钩子函数的参数 (即 el
、binding
、vnode
和 oldVnode
)。
钩子函数参数
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。- binding:一个对象,包含以下 property:
name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。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
钩子中可用。
测试小案例
import Vue from 'vue';
export default Vue.directive('pin', {
bind: function (el, binding) {
el.style.position = 'fixed';
var s = binding.arg == 'left' ? 'left' : 'top';
el.style[s] = binding.value + 'px';
}
});
使用:
<template>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</template>
<script>
export default {
data: function () {
return {
direction: 'left'
};
}
};
</script>
函数简写
在很多时候,你可能想在 bind
和 update
时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value;
});
对象字面量
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color); // => "white"
console.log(binding.value.text); // => "hello!"
});
其他自定义指令
v-foucs
import Vue from 'vue';
const focus = Vue.directive('focus', {
inserted: function (el) {
el.focus();
el.setAttribute('placeholder', '请输入');
}
});
export default focus;
v-drag
import Vue from 'vue';
const drag = Vue.directive('drag', {
inserted: function (el) {
//inserted 钩子函数:当元素被插入父元素时触发,可省略
let oDiv = el; //el --> 触发的DOM元素
oDiv.style.position = 'relative';
oDiv.onmousedown = function (e) {
let l = e.clientX - oDiv.offsetLeft;
let t = e.clientY - oDiv.offsetTop;
document.onmousemove = function (e) {
oDiv.style.left = e.clientX - l + 'px';
oDiv.style.top = e.clientY - t + 'px';
};
oDiv.onmouseup = function () {
document.onmousemove = null;
oDiv.onmouseup = null;
};
};
}
});
export default drag;
v-expandClick
使用该指令可以隐式的扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。
import Vue from 'vue';
const expandClick = Vue.directive('expandClick', 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);
}
});
export default expandClick;
使用:
<template>
<div
v-expandClick="'20,30,40,50'"
@click="cl"
>
点击范围扩大
</div>
</template>
<script>
export default {
methods: {
cl() {
console.log('click');
}
}
};
</script>
v-copy
使用该指令可以复制元素的文本内容(指令支持单击复制 v-copy
、双击复制 v-copy.dblclick
、点击 icon
复制 v-copy.icon
三种模式),不传参数时,默认使用单击复制。
import Vue from 'vue';
const copy = Vue.directive('copy', function (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');
}
export default copy;
<template>
<div>
<div v-copy>单击复制</div>
<div v-copy.dblclick>双击复制</div>
<div v-copy.icon>icon复制</div>
</div>
</template>
v-screenfull
全屏指令,点击元素进行全屏/退出全屏的操作。支持元素后面是否插入 element-ui
的全屏图标
el-icon-full-screen
。
import Vue from 'vue';
import screenfull from 'screenfull';
const full = Vue.directive('screenfull', function (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();
}
export default full;
<div v-screenfull.icon>全屏</div>
v-tooltip
为元素添加说明,如同 element-ui
的 el-tooltip
(问号 icon
在鼠标覆盖后,展示说明文字)。
import Vue from 'vue';
const tool_tip = Vue.directive('tooltip', function (el, binding) {
if (el.hasIcon) return;
console.log(binding);
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;
}
export default tool_tip;
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
content | 传给指令的参数。例如 v-tooltip:content 中,参数为 "content" ,tooltip 中展示的内容为:"content" | / | String | 可选 |
tootipParams | element-ui 支持的 tooltip 属性 | / | Object | 可选 |
然后你可以在模板中任何元素上使用新的 v-tooltip
property
,如下:
<div v-tooltip:content='tootipParams'> 提示 </div>
举例:
<template>
<div>
<div v-tooltip:[content]>提示1</div>
<div v-tooltip:[content]="tootipParams">提示2</div>
</div>
</template>
<script>
export default {
data() {
return {
content: '内容',
tootipParams: { placement: 'top', effect: 'light' }
};
}
};
</script>
v-ellipsis
使用该指令当文字内容超出宽度(默认 100 px
)时自动变为省略形式。等同于使用 css
:
import Vue from 'vue';
const ellipsis = Vue.directive('ellipsis', function (el, binding) {
el.style.width = (binding.arg || 100) + 'px';
el.style.whiteSpace = 'nowrap';
el.style.overflow = 'hidden';
el.style.textOverflow = 'ellipsis';
});
export default ellipsis;
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
width | 元素宽度 | 100 | Number | 必填 |
然后你可以在模板中任何元素上使用新的 v-ellipsis
property
,如下:
<template>
<div>
<div v-ellipsis:140>提示1提示1提示1提示1提示1</div>
<div v-ellipsis:[width]>提示1提示1提示1提示1提示1</div>
</div>
</template>
<script>
export default {
data() {
return {
width: 160
};
}
};
</script>
v-backtop(need fix)
使用该指令可以让页面或指定元素回到顶部。
可选指定元素,如果不指定则全局页面回到顶部。可选在元素偏移多少 px
后显示 backtop
元素,例如在滚
动 400px
后显示回到顶部按钮。
import Vue from 'vue';
const backtop = Vue.directive('backtop', {
bind(el, binding) {
// 响应点击后滚动到元素顶部
el.addEventListener('click', () => {
const target = binding.arg ? document.getElementById(binding.arg) : window;
target.scrollTo({
top: 0,
behavior: 'smooth'
});
});
},
update(el, binding) {
// 滚动到达参数值才出现绑定指令的元素
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, binding) {
const target = binding.arg ? document.getElementById(binding.arg) : window;
target.removeEventListener('scroll');
el.removeEventListener('click');
}
});
export default backtop;
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
id | 给需要回到顶部的元素添加的id | / | String | 可选 |
offset | 偏移距离为 height 时显示指令绑定的元素 | / | Number | 可选 |
然后你可以在模板中任何元素上使用新的 v-backtop
property
,如下表示在 id
为 app
的元素滚动
400px
后显示绑定指令的元素:
<template>
<div>
<h1
v-for="i in 6"
:key="i"
>
高度{{ i }}
</h1>
<div id="my-goal">hight</div>
<div v-backtop>回到顶部</div>
<div v-backtop:[hight]="400">回到hight</div>
<h1
v-for="i in 30"
:key="i + 30"
>
高度{{ i + 30 }}
</h1>
</div>
</template>
<script>
export default {
data() {
return {
hight: 'my-goal'
};
}
};
</script>
v-empty
使用该指令可以显示缺省的空状态。可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无 数据)、以及标示是否显示空状态(必选)。
import Vue from 'vue';
const empty = Vue.directive('empty', {
update(el, binding) {
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);
}
}
});
export default empty;
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
emptyValue | 包含文字内容 content、图片 img、是否显示 visible,仅 visible 必传 | / | Object | 必须 |
然后你可以在模板中任何元素上使用新的 v-empty
property
,如下传入对象 emptyValue
:
<template>
<div>
<button @click="show = !show">show</button>
<div
style="height: 500px; width: 500px"
v-empty="emptyValue"
>
<span v-if="show">原本内容</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
show: false,
emptyValue: {
content: '暂无列表',
img: require('../assets/logo.png'),
visible: true
}
};
}
};
</script>
v-badge
使用该指令在元素右上角显示徽标。
支持配置徽标的背景颜色、徽标形状;支持传入徽标上显示的数字。
import Vue from 'vue';
const SUCCESS = '#72c140';
const ERROR = '#ed5b56';
const WARNING = '#f0af41';
const INFO = '#4091f7';
const HEIGHT = 20;
let flag = false;
const badge = Vue.directive('badge', {
update(el, binding) {
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;
}
});
export default badge;
参数 Attributes:
参数 | 说明 | 默认值 | 类型 | 可选 |
---|---|---|---|---|
normal、dot | 徽标形状 normal 为正常徽标;dot 仅为一个点 | normal | String | 可选 |
success、error、info、warning | 徽标颜色 | error | String | 可选 |
number | 徽标上显示的数字 | / | Number | 可选 |
然后你可以在模板中任何元素上使用新的 v-badge
property
,如下:
<template>
<div>
<div
v-badge.dot.info="badgeCount"
style="height: 50px; width: 50px; background: #999"
>
<span v-if="show">哈</span>
</div>
<button @click="show = !show">show</button>
</div>
</template>
<script>
export default {
data() {
return {
badgeCount: 10,
show: false
};
}
};
</script>
实战指令
active-nav
// navCurrent.js
import Vue from 'vue';
const navCurrent = Vue.directive('navCurrent', {
bind(el, binding, vnode) {
console.log('bind', vnode);
const _ops = binding.value,
_c = el.getElementsByClassName(_ops.className);
_c[_ops.curIdx].className += ` ${_ops.activeClass}`;
},
update(el, binding, vnode) {
console.log('update', vnode);
const _ops = binding.value,
_oOps = binding.oldValue,
_c = el.getElementsByClassName(_ops.className);
_c[_ops.curIdx].className += ` ${_ops.activeClass}`;
_c[_oOps.curIdx].className = `${_oOps.className}`;
}
});
export default navCurrent;
使用:
<template>
<div
class="nav-bar"
v-nav-current="{
curIdx,
className: 'nav-item',
activeClass: 'nav-current'
}"
>
<div
class="nav-item"
v-for="(item, index) in items"
:key="index"
@click="changeNav(index)"
>
{{ item }}
</div>
</div>
</template>
<script>
import navCurrent from '@/directive/navCurrent';
export default {
name: 'Navbar',
directives: {
navCurrent
},
data() {
return {
curIdx: 0,
items: ['选项1', '选项2', '选项3', '选项4']
};
},
methods: {
changeNav(idx) {
this.curIdx = idx;
}
}
};
</script>
<style scoped>
.nav-bar {
margin: 20px auto;
display: flex;
justify-content: center;
border: 1px solid #000;
width: 480px;
}
.nav-item {
width: 120px;
height: 40px;
line-height: 40px;
cursor: pointer;
}
.nav-item.nav-current {
color: #fff;
background-color: #000;
}
</style>
v-click-outside
自定义日历组件,点击日历以外部分,隐藏弹出日历。