好用的 vue 自定义指令,封装对于 Dom 操作的指令(相当于 v-model 语法糖)
vue 自定义指令
自定义指令的钩子函数
(1)bind:指令第一次绑定要元素时调用,这个自定义指令只能用一次(日抛型)
(2)unbind:指令与元素解绑时,只使用一次
(3)inserted:被绑定的元素,插入到父节点的dom上的时候调用
(4)update:组件更新时调用
(5)componentUpdated:组件和子组件更新时调用。 注意点:
(1)el指的是绑定这个指令的DOM元素,el的参数是js对象
(2)除了el,其他的都是可读的
(3)除了update和componentUpdated之外,其他的钩子函数都有el,binding,vnode这三个参数
(4)binding里有很多属性:name,value,oldValue,expression、arg、modifiers
(5)oldVnode只在update和componentUpdated这两个钩子函数中生效(Vnode是虚拟节点的意思)
如何使用?
全局注册
1 2 3 4 5 6 7 8
| Vue.directive("focus", { inserted: function (el) { el.focus(); }, });
|
局部组件注册
1 2 3 4 5 6 7 8
| directives: { focus: { inserted: function (el) { el.focus() } } }
|
在标签中使用
vue-cli 批量注册全局指令
新建directives/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const modulesFiles = require.context("./modules", true, /\.js$/); const directives = modulesFiles.keys().reduce((modules, modulePath) => { const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1"); const value = modulesFiles(modulePath); modules[moduleName] = value.default; return modules; }, {});
export default { install(Vue) { Object.keys(directives).forEach((key) => { Vue.directive(key, directives[key]); }); }, };
|
在 main.js 引入并调用
1 2 3
| import Vue from "vue"; import Directives from "@/directives"; Vue.use(Directives);
|
limit 限制数字和浮点数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| const trigger = (el, type) => { const e = document.createEvent("HTMLEvents"); e.initEvent(type, true, true); el.dispatchEvent(e); }; export default { bind: function (el, binding, vNode) { let { arg = "", value: decimal = "2", modifiers: { int, float, minus }, } = binding; const handler = (e) => { let result = el.value; let symbol = ""; if (minus && result.charAt(0) == "-") { result = el.value.slice(1); symbol = "-"; } else { symbol = ""; } if (float) { result = result.match( new RegExp("^\\d*(\\.?\\d{0," + decimal + "})", "g") )[0]; if ( (result.length > 1 && result.charAt(0) == 0 && result.charAt(1) != ".") || result.charAt(0) == "." ) { result = result.slice(1); } trigger(el, "input"); } else if (int | true) { result = result.replace(/[^\d]/g, ""); result = result != "0" ? result.replace(/^0*/g, "") : 0; trigger(el, "input"); } el.value = symbol + result; }; el.addEventListener("keyup", handler); el.addEventListener("blur", handler); }, unbind(el) { el.removeEventListener("keyup", el.handler); el.removeEventListener("blur", el.handler); }, };
|
使用 v-limit 后可带多个 modifiers
1 2 3 4 5 6 7 8 9
| <input type="text" v-limit />//默认大于0的正整数 <input type="text" v-limit.int />//默认大于0的正整数 <input type="text" v-limit.minus />//整数(带负号-) <input type="text" v-limit.minus.int />//整数(带负号-)
<input type="text" v-limit.float />//0以上浮点数,默认小数点为2位 <input type="text" v-limit.float="3" />//0以上浮点数,小数点为3位 <input type="text" v-limit.minus.float />//浮点数(可带负号-),默认小数点为2位 <input type="text" v-limit.minus.float="4" />//浮点数(可带负号-),小数点为4位
|
color
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| export default { inserted: function (el, binding) { const interval = binding.arg ? binding.arg : 1000; if (Array.isArray(binding.value)) { let i = 0; el.style.color = binding.value[i]; el.dataset.time = setInterval(() => { if (i > binding.value.length - 1) { i = 0; } el.style.color = binding.value[i]; i++; }, interval); } else { el.style.color = binding.value; } }, componentUpdated: function (el, binding) { clearInterval(el.dataset.time); const interval = binding.arg ? binding.arg : 1000; if (Array.isArray(binding.value)) { let i = 0; el.style.color = binding.value[i]; el.dataset.time = setInterval(() => { if (i > binding.value.length - 1) { i = 0; } el.style.color = binding.value[i]; i++; }, interval); } else { el.style.color = binding.value; } }, unbind: function (el, binding) { clearInterval(el.dataset.time); }, };
|
Sticky 吸顶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| const vueSticky = {}; let listenAction; export default { inserted(el, binding) { const params = binding.value || {}; const stickyTop = params.stickyTop || 0; const zIndex = params.zIndex || 1000; const elStyle = el.style;
elStyle.position = "-webkit-sticky"; elStyle.position = "sticky"; const elHeight = el.getBoundingClientRect().height; const elWidth = el.getBoundingClientRect().width; elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`;
const parentElm = el.parentNode || document.documentElement; const placeholder = document.createElement("div"); placeholder.style.display = "none"; placeholder.style.width = `${elWidth}px`; placeholder.style.height = `${elHeight}px`; parentElm.insertBefore(placeholder, el);
let active = false;
const getScroll = (target, top) => { const prop = top ? "pageYOffset" : "pageXOffset"; const method = top ? "scrollTop" : "scrollLeft"; let ret = target[prop]; if (typeof ret !== "number") { ret = window.document.documentElement[method]; } return ret; };
const sticky = () => { if (active) { return; } if (!elStyle.height) { elStyle.height = `${el.offsetHeight}px`; }
elStyle.position = "fixed"; elStyle.width = `${elWidth}px`; placeholder.style.display = "inline-block"; active = true; };
const reset = () => { if (!active) { return; }
elStyle.position = ""; placeholder.style.display = "none"; active = false; };
const check = () => { const scrollTop = getScroll(window, true); const offsetTop = el.getBoundingClientRect().top; if (offsetTop < stickyTop) { sticky(); } else { if (scrollTop < elHeight + stickyTop) { reset(); } } }; listenAction = () => { check(); };
window.addEventListener("scroll", listenAction); },
unbind() { window.removeEventListener("scroll", listenAction); }, };
|
使用
1
| <div v-Sticky>我在顶部</div>
|
copy 复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| export default { 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); }, };
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <button v-copy="copyText">复制</button> </template>
<script> export default { data() { return { copyText: "a copy directives", }; }, }; </script>
|
longpress 长按事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| export default { bind: function (el, binding, vNode) { let { arg: delay = 2000 } = binding; 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(); }, delay); } }; 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); }, };
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <button v-longpress="longpress">长按</button>//默认两秒 <button v-longpress:4000="longpress">长按</button>//可设置4000毫秒 </template>
<script> export default { methods: { longpress() { alert("长按指令生效"); }, }, }; </script>
|
debounce 按钮防抖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export default { bind: function (el, binding) { let { arg: delay = 1000 } = binding; let timer; const handler = () => { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { binding.value(); }, delay); }; el.addEventListener("click", handler); }, unbind(el) { el.removeEventListener("click", el.handler); }, };
|
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <button v-debounce="debounceClick">防抖</button>//默认1000毫秒内只执行一次 <button v-debounce:400="debounce">防抖</button>//400毫秒内只执行一次 </template>
<script> export default { methods: { debounceClick() { console.log("只触发一次"); }, }, }; </script>
|
LazyLoad 图片懒加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| let defaultSrc = "";
const load = (el) => { let windowHeight = document.documentElement.clientHeight; let elTop = el.getBoundingClientRect().top; let elBtm = el.getBoundingClientRect().bottom; let realSrc = el.dataset.src; if (elTop - windowHeight < 0 && elBtm > 0) { if (realSrc) { el.src = realSrc; el.removeAttribute("data-src"); } } };
const throttle = (fn, delay) => { let timer; let prevTime; return function (...args) { let currTime = Date.now(); let 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 { bind(el, binding) { el.setAttribute("data-src", binding.value); el.setAttribute("src", defaultSrc); }, inserted(el) { if (IntersectionObserver) { var io = new IntersectionObserver((entries) => { let realSrc = el.dataset.src; if (entries[0].isIntersecting) { if (realSrc) { el.src = realSrc; el.removeAttribute("data-src"); } } }); io.observe(el); } else { let handler = throttle(load, 1000); load(el); window.addEventListener("scroll", () => { handler(el); }); } }, };
|
使用
1
| <img v-lazyLoad="'https://wang-z.gitee.io/img/author.jpg'" />
|
draggable 拖拽指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| export default { 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; }; }; }, };
|
使用
需要设置需要拖拽的元素为相对定位,其父元素为绝对定位
1 2 3
| <template> <div class="el-dialog" v-draggable></div> </template>
|
waterMarker添加背景水印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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
|
使用
设置水印文案,颜色,字体大小即可
1 2 3 4
| <template> <div v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div> </template>
|