前段时间在用框架开发H5页面时,碰到框架中的组件内置了一个属性用于适配异形屏,虽然是组件内部实现的,但这个方式让我萌生一个想法:能不能自己写一个属性来实现这样的功能?
经过一番思索,我发现Vue的指令模式就很像属性的写法,在Vue中,我们利用模板指令诸如v-if v-for
等完成了许多工作,而Vue同样也支持自定义属性:
1 2 3 4 5 6 7 8 9
| const app = Vue.createApp({})
app.directive('focus', { mounted(el) { el.focus() } })
|
然后你可以在模板中任何元素上使用新的 v-focus attribute,如下
注:这里除了全局注册,也可以采用局部注册的方式,实际开发中可以使用vue另一项方便的功能mixin来将对应的指令混入你想使用的文件中,以达到代码的复用,那么开始进入正题吧。
底部安全区适配
首先页面必须在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值
1 2 3 4 5 6 7 8
| 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));`); } } }
|
使用:
1
| <div v-safe-area-bottom></div>
|
如果设计图本身存在一个边距,则可以动态适配:
1 2
| <div v-safe-area-bottom="'1rem'"></div> <div v-safe-area-bottom="'10px'"></div>
|
是不是很方便?我们再来看看另一个移动端H5会遇到的问题,并且还是用Vue指令来解决它。
弹窗背景页不滚动
在移动端开发中,页面弹出滚动窗口时,需要将背景页固定住不动,否则会出现”滚动穿透”的现象。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 = ''; } }
|
1
| <div v-touch-scroll>是的,我是一个弹窗,当我出现时我的背景会吓得不敢动</div>
|
实现一个copy工具
有时我们需要页面点击可以”一键复制”的功能,可能大家都有用到一个叫vue-clipboard
的库,知道了指令的使用,实现一个copy自然也不在话下,那么就自己动手写一个vueCopy,为今后开发项目减少一个第三方库的使用吧。
首先我们看看这个工具是怎么使用的:
可以看出作者也是利用了指令,就照他这个思路,动手撸了一个,这里就直接上代码了,具体思路点见注释:
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
| clipboard: { bind(el, binding, { context }) { const _this = context 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' 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) }, }
|
简单使用:
1
| <div v-clipboard="'copy copy Text'">点击直接复制到剪贴板</div>
|
带回调的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <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>
|
表单防止重复提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Vue.directive('throttle', { bind: (el, binding) => { let throttleTime = binding.value; if (!throttleTime) { throttleTime = 2000; } let cbFun; el.addEventListener('click', event => { if (!cbFun) { cbFun = setTimeout(() => { cbFun = null; }, throttleTime); } else { event && event.stopImmediatePropagation(); } }, true); }, });
|
使用:
1
| <button @click="sayHello" v-throttle>提交</button>
|
图片懒加载
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 87 88
| const LazyLoad = { install(Vue,options){ let defaultSrc = options.default; Vue.directive('lazy',{ bind(el,binding){ LazyLoad.init(el,binding.value,defaultSrc); }, inserted(el){ if('IntersectionObserver' in window){ LazyLoad.observe(el); }else{ LazyLoad.listenerScroll(el); } }, }) }, init(el,val,def){ el.setAttribute('data-src',val); el.setAttribute('src',def); }, observe(el){ let 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); }, listenerScroll(el){ let handler = LazyLoad.throttle(LazyLoad.load,300); LazyLoad.load(el); window.addEventListener('scroll',() => { handler(el); }); }, 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'); } } }, 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 LazyLoad;
|
以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写的不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~