- A+
所属分类:Web前端
常见自定义指令
一、响应缩放指令
- 使用
<div v-resize="resize"></div>
- 代码
/** * 响应缩放指令 * @direction 使用该指令可以响应元素宽高改变时执行的方法。 * @resize {function} 传入对应resize方法 * v-resize="resize" **/ export default { bind(el, binding) { let width = '', height = ''; function isResize() { const style = document.defaultView.getComputedStyle(el); if (width !== style.width || height !== style.height) { binding.value(); // 执行传入的方法 } width = style.width; height = style.height; } el.__timer__ = setInterval(isResize, 300); // 周期性监听元素是否改变 }, unbind(el) { clearInterval(el.__timer__); } }
二、元素点击范围扩展指令
- 使用
<div v-expandClick="20,30,40,50"></div>
- 代码
/** * 元素点击范围扩展指令 * @direction 使用该指令可以隐式的扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。法。 * @top {string} 上扩展的范围,单位 px,默认向外扩展 10px * @right {string} 右扩展的范围,单位 px,默认向外扩展 10px * @bottom {string} 下扩展的范围,单位 px,默认向外扩展 10px * @left {string} 左扩展的范围,单位 px,默认向外扩展 10px * v-expandClick="top, right, bottom, left" (10, 10, 10, 10) **/ export default { bind(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) } } }
三、文本内容复制指令
- 使用
<div v-copy> 单击复制 </div> <div v-copy.dblclick> 双击复制 </div> <div v-copy.icon> icon复制 </div>
- 代码
/** * 文本内容复制指令 * @direction 使用该指令可以复制元素的文本内容(指令支持单击复制 v-copy、双击复制 v-copy.dblclick、点击icon复制 v-copy.icon三种模式),不传参数时,默认使用单击复制。 * @dblclick {string} dblclick 双击复制文本内容 * @icon {string} icon 单击icon复制文本内容 * v-copy 单击复制 * v-copy.dblclick 双击复制 * v-copy.icon icon复制 **/ export default { bind(el, binding) { // 双击触发复制 if (binding.modifiers.dblclick) { el.addEventListener('dblclick', () => handleClick(el.innerText)) el.style.cursor = 'copy' } else if (binding.modifiers.icon) { // 点击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' } } }
四、元素说明指令
- 使用
<div v-tooltip:content='tootipParams'> 提示 </div>
- 代码
/** * 元素说明指令 * @direction 为元素添加说明,同 element-ui 的el-tooltip * @content {string} 传给指令的参数 * @tooltipParams {Object} element-ui 支持的 tooltip属性 * v-tooltip:content='tooltipParams' **/ import Vue from 'vue' function structureIcon(content, attrs) { // 拼接绑定属性 let attrStr = '' for (const 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 { bind(el, binding) { if (el.hasIcon) return const iconElement = structureIcon(binding.arg, binding.value) el.appendChild(iconElement) el.hasIcon = true } }
五、文字超出省略指令
- 使用
<div v-ellipsis:100> 需要省略的文字是阿萨的副本阿萨的副本阿萨的副本阿萨的副本</div>
- 代码
/** * 文字超出省略指令 * @direction 使用该指令当文字内容超出宽度。 * @width {number} 元素宽度 * v-ellipsis:width **/ export default { bind(el, binding) { el.style.width = binding.arg || 100 + 'px' el.style.whiteSpace = 'nowrap' el.style.overflow = 'hidden'; el.style.textOverflow = 'ellipsis'; } }
六、回到顶部指令
- 使用
<div v-backtop:app="400"> 回到顶部 </div>
- 代码
/** * 回到顶部指令 * @direction 使用该指令可以让页面或指定元素回到顶部。 * @id {string} 给需要回到顶部的元素添加的id * @offset {number} 偏移距离为 height 时显示指令绑定的元素 * v-backtop:id="offset" **/ export default { bind(el, binding, vnode) { // 响应点击后滚动到元素顶部 el.addEventListener('click', () => { const target = binding.arg ? document.getElementById(binding.arg) : window target.scrollTo({ top: 0, behavior: 'smooth' }) }) }, update(el, binding, vnode) { // 滚动到达参数值才出现绑定指令的元素 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') } }
七、拖拽指令
- 使用
<div v-drag> 支持拖拽的元素 </div>
- 代码
/** * 拖拽指令 * @direction 使用该指令可以对元素进行拖拽。 * v-drag=“[width, height]” **/ export default { inserted(el, binding) { let { value } = binding; if (!value || value.length === 0) value = [0, 0] el.style.cursor = 'move' el.onmousedown = function(e) { const disx = e.pageX - el.offsetLeft const disy = e.pageY - el.offsetTop document.onmousemove = function(e) { let x = e.pageX - disx let y = e.pageY - disy const maxX = document.body.clientWidth - parseInt(window.getComputedStyle(el).width) - value[0] const maxY = document.body.clientHeight - parseInt(window.getComputedStyle(el).height) - value[1] 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 } } } }
八、长按指令
- 使用
<div v-longpress="longPress"></div>
- 代码
/** * 长按指令 * @direction 实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件 * @longPress {function} 长按的回调 * v-longpress="longPress" **/ export default { bind: function(el, binding, vNode) { if (typeof binding.value !== 'function') { throw 'callback must be a function' } // 定义变量 let pressTimer = null // 创建计时器( 2秒后执行函数 ) const start = (e) => { if (e.type === 'click' && e.button !== 0) { return } if (pressTimer === null) { pressTimer = setTimeout(() => { handler() }, 2000) } } // 取消计时器 const 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) } }
九、防抖指令
- 使用
<el-button v-debounce="debounceClick"> 防抖 </el-button>
- 代码
/** * 防抖指令 * @direction 防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。 * @debounceClick {function} 按钮的点击事件 * v-debounce="debounceClick" **/ export default { inserted(el, binding) { let timer el.addEventListener('keyup', () => { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { binding.value() }, 1000) }) } }
十、水印指令
- 使用
<div v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)', font:'16px Microsoft JhengHei'}"></div>
- 代码
/** * 水印指令 * @direction 给整个页面添加背景水印 * @data {Object} 水印文案,颜色,字体 * v-waterMarker="{text:'版权所有',textColor:'rgba(180, 180, 180, 0.4)', font:'16px Microsoft JhengHei'}" **/ function addWaterMarker(str, parentNode, font, textColor) { // 水印文字,父元素,字体,文字颜色 const can = document.createElement('canvas') parentNode.appendChild(can) can.width = 200 can.height = 150 can.style.display = 'none' const 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') + ')' } export default { bind(el, binding) { addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor) } }
十一、懒加载指令select
- 使用
<el-select v-loadselect="loadmore"></el-select>
- 代码
/** * el-select懒加载指令 * @direction 下拉框数据较多,需要下拉的时候懒加载数据 * @load {function} 懒加载调用方法 * v-loadselect="load" **/ export default { inserted(el, binding) { const selectWarpDom = el.querySelector( '.el-select-dropdown .el-select-dropdown__wrap' ) selectWarpDom.addEventListener('scroll', function() { if (this.scrollHeight - this.scrollTop <= this.clientHeight) { binding.value() } }) } }
十二、徽标指令
- 使用
<div v-badge.dot.info="10"></div> >
- 代码
/** * 徽标指令 * @direction 使用该指令在元素右上角显示徽标。 * @shape {string} 形状 [normal(正常徽标), dot(仅展示一个点)] * @type {string} 徽标颜色类型 [success, error, info, warning] * @num {number} 懒加载调用方法 * v-badge.shape.type="num" **/ import Vue from 'vue' const SUCCESS = '#72c140' const ERROR = '#ed5b56' const WARNING = '#f0af41' const INFO = '#4091f7' const HEIGHT = 20 let flag = false export default { update(el, binding, vnode) { const { modifiers, value } = binding const modifiersKey = Object.keys(modifiers) const 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 } }
十三、空状态指令
- 使用
<div v-empty="emptyValue" style="height:500px;width:500px"> 原本内容 </div>
- 代码
/** * 空状态指令 * @direction 使用该指令可以显示缺省的空状态, 可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无数据)、以及标示是否显示空状态(必选)。 * @emptyValue {Object} 懒加载调用方法 * @content: '暂无列表', 可选 * @img: require('../../assets/images/xxx.png'), 可选 * @visible: 'true', 是否显示. 必传 * v-empty="emptyValue " **/ import Vue from 'vue'; export default { update(el, binding, vnode) { 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%">` : '' 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) } } }
十四、输入框聚焦指令
- 使用
<div v-focus></div>
- 代码
/** * 输入框聚焦指令 * @direction 页面加载时,输入框自动聚焦。 * v-focus **/ export default { inserted(el) { // 聚焦元素 el.focus() } }