bind : 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。(常用)
inserted : 被绑定元素插入父节点时调用(保证父节点存在,但不一定已被插入文档中)。(常用)
update : 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。 指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新(详细的钩子函数参数下见)。(基本没咋用)
componentUpdated : 指令所在组件的VNode 及其子 VNode 全部更新后调用。(基本没咋用)
unbind : 只调用一次,指令与元素解绑时调用。(常用)
el : 指令所绑定的元素,可以用来直接操作DOM。(常用)
binding : 一个对象,包含以下属性:(常用)
- name : 指令名,不包括 v- 前缀。
-
- value : 指令表达式的最终返回结果
-
- oldValue :指令表达式的最终返回结果前一个值,仅在 update 和 componentUpdated 钩子中可用。
- expression :表达式。
-
- arg :传给指令的参数,可选。例如 v-copy:foo 中,参数为 "foo"。
-
- modifiers :一个包含修饰符的对象。例如:v-copy:foo.a 中的a
vnode :Vue 编译生成的虚拟节点。(基本没咋用)
oldVnode :上一个虚拟节点,仅在 update和 componentUpdated 钩子中可用。(基本没咋用)
- // directive.js
- import directive0 from './directive0'
- import directive1 from './directive1'
- import directive2 from './directive2'
-
- const install = (Vue) => {
- Vue.directive('directive0', copy)
- Vue.directive('directive1', copy)
- Vue.directive('directive2', copy)
- }
-
- export default {
- install
- }
-
- // main.js
- import directive from './directive'
-
- Vue.use(directive)
调用:
- <el-input v-model="url" readonly>
- <template slot="append">
- <el-button icon="el-icon-copy-document" v-copy="url">复制el-button>
- template>
- el-input>
- <script>
- export default {
- data () {
- return {
- url: 'xxx'
- }
- }
- }
- script>
代码:
- const copy = {
- /**
- * 初始化
- * @param {DOM} el 指令所绑定的元素DOM节点
- * @param {*} value 指令的绑定值 即 v-copy="value" 的value值
- */
- bind(el, { value }) {
- // 给元素赋值一个$value值,即指令绑定的值
- el.$value = value
- el.handler = () => {
- // 如果可复制的值为空的时候,给出提示;
- if (!el.$value) {
- console.log('无复制内容')
- return
- }
- // 动态创建 textarea 标签
- const textarea = document.createElement('textarea');
- // 将该 textarea 设为 readonly 防止 IOS 下自动唤起键盘,同时将 textarea 移除可视区域
- textarea.readOnly = 'readonly';
- textarea.style.position = 'absolute';
- textarea.style.left = '-9999px';
- // 将要copy的值赋值给textarea 标签的value属性
- textarea.value = el.$value;
- // 将textarea 插入到body中
- document.body.appendChild(textarea);
- // 选中值并复制
- textarea.select()
- const result = document.execCommand('Copy');
- if (result) {
- console.log('复制成功');
- }
- document.body.removeChild(textarea);
- }
- // 绑定点击事件,点击的时候copy值
- el.addEventListener('click', el.handler);
- },
- // 当传递进来的值更新的时候触发
- componentUpdated(el, { value }) {
- el.$value = value;
- },
- // 指令与元素解绑的时候,移除事件绑定
- unbind(el) {
- el.removeEventListener('click', el.handler);
- }
- }
-
- export default copy;
调用:直接v-longpress则默认长按1秒执行函数,当然也可以自己传参,单位为毫秒
- <div>
- <el-button v-longpress:2000="longpress">长按el-button>
- div>
-
- <script>
- export default {
- methods: {
- longpress() {
- // xxx
- }
- }
- }
- script>
代码:
- /**
- * 1. 创建一个计时器,n s 后执行函数
- * 2. 当用户按下按钮时触发 mousedown 事件,启动计时器;
- * 用户松开按钮时调用 mouseout 事件。
- * 3. 如果 mouseup 事件 n s内被触发,就清除计时器,当作一个普通的点击事件。
- * 4. 如果 计时器没有在n s内清除,则判定为移除长按,可以执行关联的函数。
- * 5. 在移动端要考虑 touchstart, touchend 事件。
- */
- const longpress = {
- bind (el, binding) {
- if (typeof binding.value !== 'function') {
- throw 'callback must be a function'
- }
- const time = binding.arg || 1000 // n秒后执行,默认1秒
- let pressTimer = null
- // 创建计时器(1s后执行函数)
- let start = e => {
- if (e.type === 'click' && e.button !== 0) return
- if (pressTimer === null) {
- pressTimer = setTimeout(() => {
- handler()
- }, time)
- }
- }
- // 取消计时器
- let cancel = e => {
- if (pressTimer !== null) {
- clearTimeout(pressTimer)
- pressTimer = null
- }
- }
- // 运行函数
- const handler = e => {
- binding.value()
- }
- // 添加事件监听器
- 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)
- }
- }
-
- export default longpress
调用:分为立即/非立即防抖,传参方式不同
- <div>
- <el-button v-debounce="{ event: debounce, delay: 500, immediate: true}">立即/非立即防抖el-button>
- <el-button v-debounce="debounce">常规防抖el-button>
- div>
-
- <script>export default {
- methods: {
- debounce() {
- // xxx
- }
- }
- }
- script>
代码:
- // 在指定的时间段内,多次点击只会执行一次
- const debounce = {
- inserted(el ,binding) {
- let timer
- el.addEventListener('click', () => {
- if (timer) {
- clearTimeout(timer)
- }
- // 传值为 v-debounce="{ event: func, delay: 500, immediate: true}"
- if (typeof binding.value !== 'function') {
- const { event, delay = 500, immediate = false } = binding.value
- if (immediate) {
- let now = !timer
- timer = setTimeout(() => {
- timer = null
- }, delay)
- now && event()
- } else {
- timer = setTimeout(() => {
- timer = null
- event()
- }, delay)
- }
- } else {
- // 传值为 v-debounce="func"
- timer = setTimeout(() => {
- binding.value() // 延迟执行回调方法
- }, 1000)
- }
- })
- }
- }
- export default debounce
调用:
- <div>
- <input v-model="name" v-emoji />
- div>
- <script>
- export default {
- data () {
- return {
- name: ''
- }
- }
- }
- script>
代码:
-
- let findEle = (parent, type) => {
- return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
- }
- const trigger = (el, type) => {
- // 创建一个指定类型的事件
- const e = document.createEvent('HTMLEvents')
- // 定义事件名为 type
- e.initEvent(type, true, true)
- // 触发对象可以是任何元素或其他事件目标
- el.dispatchEvent(e)
- }
- const emoji = {
- bind: function (el, binding) {
- // 正则规则可根据需求自定义
- var regRule = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/g
- let $inp = findEle(el, 'input')
- el.$inp = $inp
- $inp.handle = function () {
- let val = $inp.value
- $inp.value = val.replace(regRule, '')
- trigger($inp, 'input')
- }
- $inp.addEventListener('keyup', $inp.handle)
- },
- unbind: function (el) {
- el.$inp.removeEventListener('keyup', el.$inp.handle)
- },
- }
-
- export default emoji
调用:
- <div style="width: 100%;height:200px" v-copy="{name:'林大大版权所有',color:'rgba(100, 100, 100, 0.4)',width: 100, height: 100}">div>

代码:
- /**
- * @param {*} name 文字
- * @param {*} width 文字宽度
- * @param {*} height 文字高度
- * @param {*} color 文字颜色
- */
- const waterMarker = {
- bind(el, binding) {
- const { name, width, height, color } = binding.value
- var can = document.createElement('canvas')
- el.appendChild(can)
- can.width = width
- can.height = height
- can.style.display = 'none'
- var cans = can.getContext('2d')
- cans.rotate((-20 * Math.PI) / 180)
- cans.fillStyle = color || 'rgba(180, 180, 180, 0.3)'
- cans.textAlign = 'left'
- cans.textBaseline = 'Middle'
- cans.fillText(name, can.width / 10, can.height / 2)
- el.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
- }
- }
-
- export default waterMarker
调用:针对于el-dialog的拖动
- <el-dialog v-draggable>
- el-dialog>
代码:
- const draggable = {
- bind(el, binding, vnode) {
- const dialogHeaderEl = el.querySelector('.el-dialog__header') // 点击能拖动的地方
- const dragDom = el.querySelector('.el-dialog') // 被拖动的dom
- dialogHeaderEl.style.cssText += ';cursor:move;'
- dragDom.style.cssText += ';top:0px;'
-
- // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
- const getStyle = (function() {
- if (window.document.currentStyle) {
- return (dom, attr) => dom.currentStyle[attr]
- } else {
- return (dom, attr) => getComputedStyle(dom, false)[attr]
- }
- })()
- dialogHeaderEl.onmousedown = (e) => {
- // 鼠标按下,计算当前元素距离可视区的距离
- const disX = e.clientX - dialogHeaderEl.offsetLeft
- const disY = e.clientY - dialogHeaderEl.offsetTop
-
- const dragDomWidth = dragDom.offsetWidth
- const dragDomHeight = dragDom.offsetHeight
-
- const screenWidth = document.body.clientWidth
- const screenHeight = document.body.clientHeight
-
- const minDragDomLeft = dragDom.offsetLeft
- const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
-
- const minDragDomTop = dragDom.offsetTop
- const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
-
- // 获取到的值带px 正则匹配替换
- let styL = getStyle(dragDom, 'left')
- let styT = getStyle(dragDom, 'top')
-
- if (styL.includes('%')) {
- styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
- styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
- } else {
- styL = +styL.replace(/\px/g, '')
- styT = +styT.replace(/\px/g, '')
- }
- document.onmousemove = function(e) {
- // 通过事件委托,计算移动的距离
- let left = e.clientX - disX
- let top = e.clientY - disY
-
- // 边界处理
- if (-(left) > minDragDomLeft) {
- left = -minDragDomLeft
- } else if (left > maxDragDomLeft) {
- left = maxDragDomLeft
- }
-
- if (-(top) > minDragDomTop) {
- top = -minDragDomTop
- } else if (top > maxDragDomTop) {
- top = maxDragDomTop
- }
- // 移动当前元素
- dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
- }
- document.onmouseup = function(e) {
- document.onmousemove = null
- document.onmouseup = null
- }
- }
- }
- }
-
- export default draggable
调用:其实传什么值 和 如何验证都是你可控制的,由你自己逻辑来定
- <el-button v-permission="5">不被展示el-button>
- <el-button v-permission="1">展示el-button>
代码:
- /**
- * 实现你有无权限的地方,返回true则展示,返回false则隐藏,下面的1234则是举例子
- * @param {*} key
- * @returns
- */
- function checkPermission(key) {
- let arr = [1,2,3,4]
- return arr.indexOf(key) > -1
- }
-
- const permission = {
- inserted: function (el, binding) {
- let permission = binding.value // 获取到 v-permission的值
- if (permission) {
- let hasPermission = checkPermission(permission)
- if (!hasPermission) {
- // 没有权限 移除Dom元素
- el.parentNode && el.parentNode.removeChild(el)
- }
- }
- },
- }
-
- export default permission
调用:(我个人觉得没啥必要用这个,你自己写个css样式就可以了)
- <el-button v-overflow-tooltip>超出实体宽度隐藏展示,鼠标移上来展示全部el-button>
代码:
- /**
- * 超出设置宽度显示文字提示指令
- * 用法:v-overflow-tooltip / v-overflow-tooltip:width
- * width 可选
- * 只要当dom元素内容超出设置的宽度时,超出文字省略号显示,鼠标画上去有全部文字提示
- */
- export default {
- name: 'overflow-tooltip',
- bind (el, binding) {
- const width = binding.arg
- if (width) {
- el.style.width = `${width}px`
- }
- const style = {
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis'
- }
- setStyle(el, style)
- },
- inserted (el, binding) {
- addTooltip(el, binding)
- },
- unbind (el) {
- if (!el.tooltip) return
- el.removeEventListener('mouseenter', el.elMouseEnterHandler)
- el.removeEventListener('mouseleave', el.elMouseOutHandler)
- el.tooltip.destroy()
- }
- }
-
- function addTooltip (el, binding) {
- el.oldOffsetWidth = el.offsetWidth
- if (!el.textWidth) {
- // 计算文本宽度
- const range = document.createRange()
- range.setStart(el, 0)
- range.setEnd(el, el.childNodes.length)
- const rangeWidth = range.getBoundingClientRect().width
- const padding = (parseInt(getStyle(el, 'paddingLeft'), 10) || 0) +
- (parseInt(getStyle(el, 'paddingRight'), 10) || 0)
- const textWidth = rangeWidth + padding
- el.textWidth = textWidth
- }
-
- // 监听元素宽度变化
- const resizeObserver = new ResizeObserver(entry => {
- const target = entry[0].target
- el.oldOffsetWidth !== target.offsetWidth && addTooltip(el, binding)
- })
- resizeObserver.observe(el)
-
- // Math.max(el.offsetWidth, binding.arg) 处理offsetWidth不是设置宽度时的情况
- if (el.textWidth > Math.max(el.offsetWidth, binding.arg || 0)) {
- let tooltip = null
-
- const elMouseEnterHandler = el.elMouseEnterHandler = debounce((event) => {
- if (!tooltip) {
- const tooltipContent = el.innerText || el.textContent
- tooltip = new Tooltip()
- tooltip.create(tooltipContent)
- el.tooltip = tooltip
- }
- // 400为tootip最大宽度
- tooltip.show(event, Math.min(el.textWidth, 400))
- }, 300)
- const elMouseOutHandler = el.elMouseOutHandler = debounce(() => {
- tooltip && tooltip.hide()
- }, 300)
-
- el.addEventListener('mouseenter', elMouseEnterHandler)
- el.addEventListener('mouseleave', elMouseOutHandler)
- } else {
- el.tooltip && el.tooltip.destroy()
- el.elMouseEnterHandler && el.removeEventListener('mouseenter', el.elMouseEnterHandler)
- el.elMouseOutHandler && el.removeEventListener('mouseleave', el.elMouseOutHandler)
- }
- }
-
- function debounce(fn, delay = 500) {
- let timer
- return function() {
- const th = this
- const args = arguments
- if (timer) {
- clearTimeout(timer)
- }
- timer = setTimeout(function() {
- timer = null
- fn.apply(th, args)
- }, delay)
- }
- }
-
- const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
- const MOZ_HACK_REGEXP = /^moz([A-Z])/
- const ieVersion = Number(document.documentMode)
- const camelCase = function(name) {
- return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
- return offset ? letter.toUpperCase() : letter
- }).replace(MOZ_HACK_REGEXP, 'Moz$1')
- }
-
- const getStyle = ieVersion < 9 ? function(element, styleName) {
- if (!element || !styleName) return null
- styleName = camelCase(styleName)
- if (styleName === 'float') {
- styleName = 'styleFloat'
- }
- try {
- switch (styleName) {
- case 'opacity':
- try {
- return element.filters.item('alpha').opacity / 100
- } catch (e) {
- return 1.0
- }
- default:
- return (element.style[styleName] || element.currentStyle ? element.currentStyle[styleName] : null)
- }
- } catch (e) {
- return element.style[styleName]
- }
- } : function(element, styleName) {
- if (!element || !styleName) return null
- styleName = camelCase(styleName)
- if (styleName === 'float') {
- styleName = 'cssFloat'
- }
- try {
- var computed = document.defaultView.getComputedStyle(element, '')
- return element.style[styleName] || computed ? computed[styleName] : null
- } catch (e) {
- return element.style[styleName]
- }
- }
-
- function setStyle(element, styleName, value) {
- if (!element || !styleName) return
-
- if (typeof styleName === 'object') {
- for (const prop in styleName) {
- if (styleName.hasOwnProperty(prop)) {
- setStyle(element, prop, styleName[prop])
- }
- }
- } else {
- styleName = camelCase(styleName)
- if (styleName === 'opacity' && ieVersion < 9) {
- element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')'
- } else {
- element.style[styleName] = value
- }
- }
- }
-
- class Tooltip {
- constructor () {
- this.id = 'autoToolTip'
- this.styleId = 'autoToolTipStyle'
- this.tooltipContent = ''
- this.styleElementText = `
- #autoToolTip {
- display: none;
- position: absolute;
- border-radius: 4px;
- padding: 10px;
- z-index: 99999;
- font-size: 12px;
- line-height: 1.2;
- min-width: 10px;
- max-width: 400px;
- word-break: break-word;
- color: #fff;
- background: #303133;
- transform-origin: center top;
- }
- #autoToolTip #arrow::after {
- content: " ";
- border-width: 5px;
- position: absolute;
- display: block;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- bottom: -10px;
- left: calc(50% - 5px);
- border-top-color: #303133;
- }
- `
- this.tooltipElement = null
- this.styleElement = null
- this.showStatus = false
- }
-
- create (tooltipContent) {
- this.tooltipContent = tooltipContent
- const autoToolTip = document.querySelector('#' + this.id)
- // 同时只添加一个
- if (autoToolTip) {
- this.tooltipElement = autoToolTip
- return
- }
-
- const styleElement = document.createElement('style')
- styleElement.id = this.styleId
- styleElement.innerHTML = this.styleElementText
- document.head.append(styleElement)
- this.styleElement = styleElement
-
- const element = document.createElement('div')
- element.id = this.id
-
- const arrowElement = document.createElement('div')
- arrowElement.id = 'arrow'
- element.append(arrowElement)
-
- document.body.append(element)
- this.tooltipElement = element
- }
-
- show (event, textWidth) {
- if (this.showStatus) return
-
- const targetElement = event.target
- const targetElementRect = targetElement.getBoundingClientRect()
- const { left, top, width } = targetElementRect
-
- this.showStatus = true
- this.removeTextNode()
- this.tooltipElement.insertAdjacentText('afterbegin', this.tooltipContent)
- const style = {
- left: `${left - (textWidth + 20 - width) / 2}px`,
- top: `${top - 38}px`,
- display: 'block'
- }
- setStyle(this.tooltipElement, style)
- }
-
- hide () {
- const style = {
- left: '0px',
- top: '0px',
- display: 'none'
- }
- setStyle(this.tooltipElement, style)
-
- this.removeTextNode()
- this.showStatus = false
- }
-
- removeTextNode () {
- const { firstChild } = this.tooltipElement
- if (Object.prototype.toString.call(firstChild) === '[object Text]') {
- this.tooltipElement.removeChild(firstChild)
- }
- }
-
- destroy () {
- const { tooltipElement, styleElement } = this
- tooltipElement && tooltipElement.remove()
- styleElement && styleElement.remove()
- }
- }
调用:
- <el-input v-limit-input:digit />
代码:
- /**
- * 用法:v-limit-input:digit 只允许输入数字
- * v-limip-input:reg="your reg expression" 支持传正则表达式,处理一些特殊的场景
- */
- const copy = {
- bind (el, binding, vnode, oldvnode) {
- const typeMap = {
- // 只输入数字
- digit: /\D/g,
- // 只输入正整数
- positiveInteger: /^(0+)|\D+/g,
- // 只输入基本中文
- chinese: /[^\u4E00-\u9FA5]/g,
- // 只输入中文英文字母
- chineseAlphabet: /[^\u4E00-\u9FA5A-Za-z]/g,
- // 只输入大写字母及数字
- uppercaseLetterDigit: /[^A-Z0-9]/g,
- // 只输入字母及数字
- letterDigit: /[^0-9a-zA-Z]/,
- // 只输入合法的金额格式
- price: /(\d+)(\.\d{0,2})?/
- }
- const { arg, value } = binding
- console.log(binding);
- if (!arg) {
- throw Error('one arg is required')
- }
- if (arg && !typeMap.hasOwnProperty(arg)) {
- throw Error('arg is not in typeMap')
- }
- if (arg === 'reg' && !value) {
- throw Error('reg arg requires a reg expression value')
- }
- const tagName = el.tagName.toLowerCase()
- const input = tagName === 'input' ? el : el.querySelector('input')
- const regKey = arg || (arg === 'reg' && value)
- // 输入法气泡窗弹出,开始拼写
- el.compositionstartHandler = function () {
- el.inputLocking = true
- }
- // 输入法气泡窗关闭,输入结束
- el.compositionendHandler = function () {
- el.inputLocking = false
- input.dispatchEvent(new Event('input'))
- }
- el.inputHandler = function (e) {
- if (el.inputLocking) return
- const oldValue = e.target.value
- const newValue = oldValue.replace(typeMap[regKey], '')
- // price 正则在safar报错,导致页面无法打开,新增的判断
- if (regKey === 'price') {
- const rege = /(\d+)(\.\d{0,2})?/
- const target = e.target
- if (rege.test(target.value)) {
- const value = target.value.match(rege)[0]
- if (value.split('.').length === 1 && target.value === value) {
- input.value = Number(value)
- } else if (target.value !== value) {
- input.value = value
- input.dispatchEvent(new Event('input')) // 通知v-model更新
- }
- } else {
- input.value = ''
- input.dispatchEvent(new Event('input'))
- }
- } else {
- // 判断是否需要更新,避免进入死循环
- if (newValue !== oldValue) {
- input.value = newValue
- input.dispatchEvent(new Event('input')) // 通知v-model更新
- }
- }
- }
- input.addEventListener('compositionstart', el.compositionstartHandler)
- input.addEventListener('compositionend', el.compositionendHandler)
- input.addEventListener('input', el.inputHandler)
- },
- unbind (el) {
- const tagName = el.tagName.toLowerCase()
- const input = tagName === 'input' ? el : el.querySelector('input')
- input.removeEventListener('compositionstart', el.compositionstartHandler)
- input.removeEventListener('compositionend', el.compositionendHandler)
- input.removeEventListener('input', el.inputHandler)
- }
- }
- export default copy
---持续更新---