Vue 的自定义指令
作用
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动
。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
钩子函数
bind: 只调用一次,指令第一次绑定到元素时调用,表示初始化
inserted: 被绑定的元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
update: 所在的组件的vnode更新时调用,但是可能发生在其子vnode更新之前,
componentUpdated: 指令所在组件的vnode及其vnode全部更新后调用
unbind: 只调用一次,指令与元素解绑时调用, 可以通过v-if可以触发,手动删除不可以触发的
钩子函数的参数
指令钩子函数会被传入以下参数:
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 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
动态指令
指令的参数可以动态的,这样自定义指令可以在应用中被灵活使用,可以把独立的逻辑封装成一个指令,通过不同的参数区分开不同的逻辑。
export default {
bind:(el, binding) => {
console.log(el,binding)
el.innerHTML = binding.value;
el.style.color = binding.arg;
},
update: (el, binding) =>{
console.log(el,binding)
el.innerHTML = binding.value;
el.style.color = binding.arg;
},
}
<h4>动态指令:实现动态控制字体颜色和显示内容</h4>
<p v-dynamic:[color]="content"></p>
<button @click="changeFont">改变颜色和内容</button>
把常用的方法通过指令封装(防抖,节流,懒加载,...等)
- 防抖 debounce
export default {
bind(el,binding) {
let callback = typeof binding.value === "function"? binding.value : () => { throw new Error("callback must be function") };
let timer = null;
el.handler = () => {
if(timer) { clearTimeout(timer) }
timer = setTimeout(() => {
callback();
},2000)
}
el.addEventListener('click', el.handler);
},
unbind(el) {
el.removeEventListener('click', el.handler);
}
}
- 节流 throttle
export default {
bind(el,binding) {
let callback = typeof binding.value === "function"? binding.value : () => { throw new Error("callback must be function") };
let previous = 0;
el.handler = () => {
let now = now Date().getTime();
if(now - previous > 2000) {
callback();
previous = now;
}
}
el.addEventListener('click', el.handler);
},
unbind(el) {
el.removeEventListener('click', el.handler);
}
}
- 懒加载
/**
* 图片懒加载
* 背景: 在类电商项目中,往往有大量的图片,如banner广告图,商品图很多
* 需求:通过一个图片加载指令,只加载浏览器可见区域的图片
* 思路:
* 1. 图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
* 2. 拿到所有的图片Dom,遍历每个图片的判断当前图片是否达到了可视范围内
* 3. 如果到了就设置 src 属性, 否则显示默认图片
* 图片懒加载有两种方式可以实现
* 1. srcoll事件监听, 性能有点差,需不停去监听用户的滚动,可以通过节流优化
* 2. 使用 Intersection Observer 判断图片是否到了可视区域, 但是有浏览器兼容性问题
*/
const lazyLoad = {
//install方法
install(Vue,options) {
console.log("fasdf",options)
const defaultSrc = options.default;//占位图
Vue.directive("lazyLoad", {
bind(el,binding) {//初始化
lazyLoad.init(el, binding.value,defaultSrc);
},
inserted(el) {//第一次插入节点
if(IntersectionObserver ) {
lazyLoad.observe(el);
}else {
lazyLoad.listenerScroll(el);
}
// lazyLoad.listenerScroll(el);
}
})
},
//初始化操作
init(el,val,def) {
el.setAttribute("data-src", val);//把需要加载的图片放入到自定义属性
el.setAttribute("src", def);// 把占位图片放入到 真实的src属性
},
//通过监听IntersectionObserver 监听el属性
observe(el) {
const options = {
// root: con,
// threshold:1,//交会处
// rootMargin:"0px"//对视口进行收缩和扩张
}
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src;
if(entries[0].isIntersecting) {
if(realSrc) {
el.src = realSrc;
el.removeAttribute("data-src");
}
}
},options)
io.observe(el);
},
//监听scroll事件
listenerScroll(el) {
lazyLoad.load(el);//第一次触发回调函数
const handler = lazyLoad.throttle(lazyLoad.load, 300);
window.addEventListener("scroll", () => {
handler(el);
})
},
//加载真实的图片
load(el) {
//1. 获取当前图片的偏移高度
//2. 获取当前滚动区域的偏移高度
//3. 获取可视的区域
const windowHeight = document.documentElement.clientHeight;
const imgTop = el.getBoundingClientRect().top;
const imgBtm = el.getBoundingClientRect().bottom;
const realSrc = el.dataset.src;
console.log(windowHeight, imgTop, imgBtm);
if(imgTop - windowHeight < 0 && imgBtm > 0) {
if(realSrc) {
el.src = realSrc;
el.removeAttribute("data-src");
}
}
},
//节流
throttle(fn,delay) {
let previous = 0;
return function() {
let _this = this;
let args = arguments;
let now = new Date().getTime();
if(now - previous > delay) {
fn.apply(_this, args);
previous = now;
}
}
}
}
export default lazyLoad;
注意
这个指令是通过 install方法全局注册的,需要单独引入,使用 Vue.use();注入全局。