乐趣区

关于前端:利用Vue自定义指令让你的开发变得更优雅

前段时间在用框架开发 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>

表单避免反复提交

// 设置 v-throttle 自定义指令
Vue.directive('throttle', {bind: (el, binding) => {
    let throttleTime = binding.value; // 节流工夫
    if (!throttleTime) { // 用户若不设置节流工夫,则默认 2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {if (!cbFun) { // 第一次执行
        cbFun = setTimeout(() => {cbFun = null;}, throttleTime);
      } else {event && event.stopImmediatePropagation();
      }
    }, true);
  },
});

应用:

<button @click="sayHello" v-throttle> 提交 </button>

图片懒加载

const LazyLoad = {
    // install 办法
    install(Vue,options){
          // 代替图片的 loading 图
        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){
        // data-src 贮存实在 src
        el.setAttribute('data-src',val);
        // 设置 src 为 loading 图
        el.setAttribute('src',def);
    },
    // 利用 IntersectionObserver 监听 el
    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);
    },
    // 监听 scroll 事件
    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 一天,心愿与你独特成长~

退出移动版