文章目录
- v-expandClick ----元素点击范围扩展指令
- v-copy ----文本内容复制指令
- v-screenfull ----元素全屏指令
- v-tooltip ----元素说明指令
- v-ellipsis ----文字超出省略指令
- v-backtop ----元素点击回到顶部指令
- v-empty ----空状态指令
- v-badge ----元素徽标指令
- v-draggable ----元素拖拽指令
- v-resize ----响应缩放指令
- v-format ----字符串整形指令
- v-LazyLoad ----图片懒加载指令
- v-debounce ----函数防抖指令
- v-permission ----权限指令
- 如何使用这些指令?
我们常常在引入全局功能时,主要都是写于 js 文件、组件中。不同于他们在使用时每次需要引用或注册,在使用上指令更加简洁。
除了将功能封装成组件,还可以多多考虑将一些简洁实用的功能放到 deirect 中。例如:常用的 css 样式、js 的一些操作、utils 中的一些工具方法、甚至是一个完整的组件都可以放进去(不过需要考虑一下性能和复杂度)。
v-expandClick ----元素点击范围扩展指令
使用该指令可以隐式的扩展元素的点击范围,由于借用伪元素实现,故不会影响元素在页面上的排列布局。
可传入的参数:上右下左扩展的范围,单位 px,默认向外扩展 10px
export default 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)
}
}
模板中元素上使用 v-expandClick
:
<div v-expandClick="20,30,40,50" @click="glabClickoutside"> 点击范围扩大</div>
v-copy ----文本内容复制指令
使用该指令可以复制元素的文本内容(指令支持单击复制
v-copy
、双击复制v-copy.dblclick
、点击icon复制v-copy.icon
三种模式),不传参数时,默认使用单击复制。
可传入参数:dblclick: 双击复制文本内容, icon: 单击icon复制文本内容
export default {
bind (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')
}
模板中元素上使用 v-copy
:
<div v-copy> 单击复制 </div>
<div v-copy.dblclick> 双击复制 </div>
<div v-copy.icon> icon复制 </div>
v-screenfull ----元素全屏指令
全屏指令,点击元素进行全屏/退出全屏的操作。支持元素后面是否插入 element-ui 的全屏图标 el-icon-full-screen。
可传入的参数:icon : 是否添加 icon
import screenfull from 'screenfull'
export default {
bind (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()
}
模板中元素上使用 v-screenfull
:
<div v-screenfull.icon> 全屏 </div>
v-tooltip ----元素说明指令
为元素添加说明,如同 element-ui 的 el-tooltip(问号 icon 在鼠标覆盖后,展示说明文字)。
可传入的参数:content: 传给指令的参数。例如 v-tooltip:content 中,参数为 “content” ,tooltip中展示的内容为:“content”。 tootipParams: element-ui 支持的 tooltip 属性
import Vue from 'vue'
export default function (el, binding) {
if (el.hasIcon) return
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
}
模板中元素上使用 v-tooltip
:
<div v-tooltip:content='tootipParams'> 提示 </div>
<div v-tooltip:提示内容为XXX1> 提示1</div>
<div v-tooltip:提示内容为XXX='tootipParams'> 提示2 </div>
// 为指令传入 element-ui 支持的参数:
data() {
return {
tootipParams: {
placement: 'top',
effect: 'light',
}
}
}
v-ellipsis ----文字超出省略指令
使用该指令当文字内容超出宽度(默认 100 px)时自动变为省略形式。等同于使用 css:
width: 100px;
whiteSpace: nowrap
overflow: hidden;
textOverflow: ellipsis;
可传入的参数:width: 元素宽度
export default function (el, binding) {
el.style.width = binding.arg || 100 + 'px'
el.style.whiteSpace = 'nowrap'
el.style.overflow = 'hidden';
el.style.textOverflow = 'ellipsis';
}
模板中元素上使用 v-ellipsis
:
<div v-ellipsis:100> 需要省略的文字是阿萨的副本阿萨的副本阿萨的副本阿萨的副本</div>
v-backtop ----元素点击回到顶部指令
使用该指令可以让页面或指定元素回到顶部。
可选指定元素,如果不指定则全局页面回到顶部。可选在元素偏移多少 px 后显示 backtop 元素,例如在滚动 400px 后显示回到顶部按钮。
可传入的参数:id: 给需要回到顶部的元素添加的id。 offset:偏移距离为 height 时显示指令绑定的元素
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) {
const target = binding.arg ? document.getElementById(binding.arg) : window
target.removeEventListener('scroll')
el.removeEventListener('click')
}
}
模板中元素上使用 v-backtop
:
<!-- id 为 app 的元素滚动 400px -->
<div v-backtop:app="400"> 回到顶部 </div>
<!-- 表示为一直显示绑定指令的元素,并且是全局页面回到顶部 -->
<div v-backtop> 回到顶部 </div>
v-empty ----空状态指令
使用该指令可以显示缺省的空状态。可以传入默认图片(可选,默认无图片)、默认文字内容(可选,默认为暂无数据)、以及标示是否显示空状态(必选)
可传入的参数:emptyValue:包含文字内容 content、图片 img、是否显示 visible,仅 visible 必传
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%"></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)
}
},
}
模板中元素上使用 v-empty
:
<div style="height:500px;width:500px" v-empty="emptyValue"> 内容</div>
//需要传入一个参数对象,例如显示文字为:暂无列表,图片路径为 ../../assets/images/blue_big.png,控制标示 visible:
emptyValue = {
content: '暂无列表',
img: require('../../assets/images/blue_big.png'),
visible: true,
}
v-badge ----元素徽标指令
使用该指令在元素右上角显示徽标。
支持配置徽标的背景颜色、徽标形状;支持传入徽标上显示的数字。
可传入的参数:normal、dot: 徽标形状normal为正常徽标;dot 仅为一个点。 success、error、info、warning: 徽标颜色。 number: 徽标上显示的数字
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)
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
}
}
模板中元素上使用 v-badge
:
<div v-badge.dot.info="badgeCount" style="height:50px;width:50px;background:#999"> </div>
v-draggable ----元素拖拽指令
使用该指令可以对元素进行拖拽。
可传入的参数:
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
}
}
},
}
模板中元素上使用 v-draggable
:
<div v-draggable> 支持拖拽的元素 </div>
v-resize ----响应缩放指令
使用该指令可以响应元素宽高改变时执行的方法。
可传入的参数:resize(): 传入元素改变 size 后执行的方法
export default {
bind(el, binding) {
let width = '', height = '';
function isReize() {
const style = document.defaultView.getComputedStyle(el);
if (width !== style.width || height !== style.height) {
binding.value(); // 执行传入的方法
}
width = style.width;
height = style.height;
}
el.__timer__ = setInterval(isReize, 300); // 周期性监听元素是否改变
},
unbind(el) {
clearInterval(el.__timer__);
}
}
模板中元素上使用 v-
:
v-format ----字符串整形指令
使用该指令可以修改字符串,如使用 v-format.toFixed 保留两位小数、 v-format.price 将内容变成金额(每三位逗号分隔),可以同时使用,如 v-format.toFixed.price。
例如将数字 243112.331 变成 243112.33,或 243,112.33。
**可传入的参数:toFixed: 保留两位小数 price: 整形成金额(三位逗号分隔) **
export default {
update (el, binding, vnode) {
const { value, modifiers } = binding
if (!value) return
let formatValue = value
if (modifiers.toFixed) {
formatValue = value.toFixed(2)
}
console.log(formatValue)
if (modifiers.price) {
formatValue = formatNumber(formatValue)
}
el.innerText = formatValue
},
}
function formatNumber (num) {
num += '';
let strs = num.split('.');
let x1 = strs[0];
let x2 = strs.length > 1 ? '.' + strs[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2
}
模板中元素上使用 v-format
:
<div v-format.toFixed.price="123333"> 123 </div>
v-LazyLoad ----图片懒加载指令
可传入的参数:
export default {
// install方法
install(Vue, options) {
const defaultSrc = options.default
Vue.directive('lazy', {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc)
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el)
} else {
LazyLoad.listenerScroll(el)
}
},
})
},
// 初始化
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
// 利用IntersectionObserver监听el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
const handler = LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
// 加载真实图片
load(el) {
const windowHeight = document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm = el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
},
// 节流
throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const 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)
}
},
}
模板中元素上使用 v-LazyLoad
:
<img v-LazyLoad="xxx.jpg" />
v-debounce ----函数防抖指令
可传入的参数:
export default {
inserted: function (el, binding) {
let timer
el.addEventListener('click', () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
binding.value()
}, 1000)
})
},
}
模板中元素上使用 v-debounce
:
<button v-debounce="debounceClick">防抖</button>
v-permission ----权限指令
**可传入的参数: **
function checkArray(key) {
let arr = ['1', '2', '3', '4']
let index = arr.indexOf(key)
if (index > -1) {
return true // 有权限
} else {
return false // 无权限
}
}
export default {
inserted: function (el, binding) {
let permission = binding.value // 获取到 v-permission的值
if (permission) {
let hasPermission = checkArray(permission)
if (!hasPermission) {
// 没有权限 移除Dom元素
el.parentNode && el.parentNode.removeChild(el)
}
}
},
}
模板中元素上使用 v-permission
:
<div class="btns">
<!-- 显示 -->
<button v-permission="'1'">权限按钮1</button>
<!-- 不显示 -->
<button v-permission="'10'">权限按钮2</button>
</div>
如何使用这些指令?
为了便于管理指令,我们将每个指令都存在于单独的 js 文件中。在项目的 src 下建一个 directives 目录,目录下新建 index.js 文件用于引入并注册指令。
├── src
| ├── directive
| | ├── index.js
| | ├── backtop.js
| | ├── badge.js
| | ├── copy.js
| | ├── ellipsis.js
| | ├── empty.js
| | ├── expandClick.js
| | ├── screenfull.js
| | └── tooltips.js
| ├── main.js
举个例子使用ellipsis.js
// directives 的 index.js 文件中引入 ellipsis 指令并注册:
import Vue from 'vue'
import ellipsis from './ellipsis' // 引入指令
// import other directives
const directives = {
ellipsis
// other directives
}
Object.keys(directives).forEach(name => Vue.directive(name, directives[name]))
最后在 main.js 中引入 index.js 文件:
import '@/directives/index'
这样就可以正常使用这些指令了.
因为工作太忙了要努力搬砖,本文是从网上copy过来得,没试这些指令好不好使,有没有bug啥的,大概搂了一眼,有些指令写的很鸡肋…
可以看看别人的写法思路,不一定非要一模一样,根据具体业务来做调整…有问题可以一起研究,这玩意很简单得。