这是我参与更文挑战的第15天,活动详情查看 更文挑战
前段时间在用框架开发H5页面时,碰到框架中的组件内置了一个属性用于适配异形屏,虽然是组件内部实现的,但这个方式让我萌生一个想法:能不能自己写一个属性来实现这样的功能?
经过一番思索,我发现Vue的指令模式就很像属性的写法,在Vue中,我们利用模板指令诸如v-if v-for
等完成了许多工作,而Vue同样也支持自定义属性:
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 当被绑定的元素挂载到 DOM 中时……
mounted(el) {
// 聚焦元素
el.focus()
}
})
然后你可以在模板中任何元素上使用新的 v-focus attribute,如下
<input v-focus />
注:这里除了全局注册,也可以采用局部注册的方式,实际开发中可以使用vue另一项方便的功能mixin来将对应的指令混入你想使用的文件中,以达到代码的复用,那么开始进入正题吧。
底部安全区适配
首先页面必须在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值
directives: {
safeAreaBottom: {
bind(el, binding) {
const addHigh = binding.value || 0
el.setAttribute('style', el.style.cssText + `padding-bottom: calc(${addHigh} + constant(safe-area-inset-bottom));padding-bottom: calc(${addHigh} + env(safe-area-inset-bottom));`);
}
}
}
使用:
<div v-safe-area-bottom></div>
如果设计图本身存在一个边距,则可以动态适配:
<div v-safe-area-bottom="'1rem'"></div>
<div v-safe-area-bottom="'10px'"></div>
是不是很方便?我们再来看看另一个移动端H5会遇到的问题,并且还是用Vue指令来解决它。
弹窗背景页不滚动
在移动端开发中,页面弹出滚动窗口时,需要将背景页固定住不动,否则会出现"滚动穿透"的现象。
touchScroll: {
inserted() {
const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;';
},
unbind() {
const body = document.body || document.documentElement;
body.style.position = '';
const top = body.style.top;
document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top, 10);
body.style.top = '';
}
}
<div v-touch-scroll>是的,我是一个弹窗,当我出现时我的背景会吓得不敢动</div>
实现一个copy工具
有时我们需要页面点击可以"一键复制"的功能,可能大家都有用到一个叫vue-clipboard
的库,知道了指令的使用,实现一个copy自然也不在话下,那么就自己动手写一个vueCopy,为今后开发项目减少一个第三方库的使用吧。
首先我们看看这个工具是怎么使用的:
可以看出作者也是利用了指令,就照他这个思路,动手撸了一个,这里就直接上代码了,具体思路点见注释:
clipboard: {
bind(el, binding, { context }) {
const _this = context
// 利用arg用来注入回调函数
if (binding.arg === 'success') {
_this.__clipboardSuccess = _this[binding.expression]
} else if (binding.arg === 'error') {
_this.__clipboardError = _this[binding.expression]
} else { // 正常情况下就将文字缓存起来
_this.__clipboardValue = binding.value
}
el.handler = () => {
if (!_this.__clipboardValue) {
this.__clipboardError && this.__clipboardError('无内容')
return
}
if (binding.arg) { // 这里是因为属性被我们用了多次会多次执行,所以限制了执行次数
return
}
try {
const textarea = document.createElement('textarea')
textarea.readOnly = 'readonly' // 禁止输入, readonly 防止手机端错误聚焦自动唤起键盘
textarea.setAttribute('style', 'position:fixed;top:-9999px;left:-9999px;') // 它是可见的,但它又是不可见的
textarea.value = binding.value
document.body.appendChild(textarea)
textarea.select()
const result = document.execCommand('Copy')
if (result) {
_this.__clipboardSuccess && _this.__clipboardSuccess(binding.value) // 这里可以定义成功回调返回的数据
}
document.body.removeChild(textarea)
} catch (e) {
this.__clipboardError && this.__clipboardError(e)
}
}
el.addEventListener('click', el.handler)
},
componentUpdated(el, { arg, value }, { context }) { // 更新值时候触发
const _this = context
if (!arg) { // 注册回调的部分不要赋值
_this.__clipboardValue = value
}
},
unbind(el) {
el.removeEventListener('click', el.handler)
},
}
简单使用:
<div v-clipboard="'copy copy Text'">点击直接复制到剪贴板</div>
带回调的使用:
<template>
<div v-clipboard="text" v-clipboard:success="success" v-clipboard:error="error">copy copy Text</div>
</template>
<script>
export default {
data() {
return {
text: 123
}
},
methods: {
success(e) {
console.log(e); // 复制成功回调
},
error(e) {
console.log(e); // 复制失败回调
}
}
}
</script>
通过我以上分享的三个例子,相信大家都感受到了Vue自定义指令的强大与方便,我们还可以实现更多常见的东西:比如长按功能,或者把防抖节流做成指令等等,这里就不过多展开,或许看到这里你也想到了一些可以运用自定义指令能做的事呢?又或者在工作中也经常用到自定义指令进行开发?欢迎一起分享讨论哦。
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 茶无味的一天 原文链接:https://juejin.im/post/6973833760429047845