zoukankan      html  css  js  c++  java
  • Vue 自定义的几个指令

    Vue 自定义指令: Vue.directive(id, [definition])

    参数:

    • {string}: id
    • {Function | Object}  [definition]

    用法:

    // 注册
    Vue.directive('my-directive', {
      bind: function () {},
      inserted: function () {},
      update: function () {},
      componentUpdated: function () {},
      unbind: function () {}
    })
    
    // 注册 (指令函数)
    Vue.directive('my-directive', function () {
      // 这里将会被 `bind` 和 `update` 调用
    })
    
    // getter,返回已注册的指令
    var myDirective = Vue.directive('my-directive')

    自定义指令有五个钩子函数,分别表示的意义如下:

    • bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
    • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
    • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。
    • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
    • unbind: 只调用一次, 指令与元素解绑时调用。

    结合工作中的一些使用场景,我们可以自定义一些常用的指令。

    directives.js:

    // v-copy
    const copy = {
        bind (el, { value }) {
            el.$value = value
            el.handler = () => {
                if (!el.$value) {
                    // 值为空时,提示 可根据UI 另设计
                    console.log('无复制内容')
                    return
                }
                // 动态创建textarea 
                const textarea = document.createElement('textarea')
                // 将该textarea 设为 readonly 防止 iOS 下自动唤起键盘, 同时将 textarea 移除可视区域
                textarea.readonly = 'readonly'
                textarea.style.display = 'none'
                textarea.style.left = '-9999px'
                textarea.value = el.$value
    
                document.body.appendChild(textarea)
    
                textarea.select()
    
                const result = document.execCommand('Copy')
                if (result) {
                    //  可根据UI 另设计
                    console.log('复制成功')
                }
                document.body.removeChild(textarea)
            }
            // 绑定点击事件 一键 copy
            el.addEventListener('click', el.handler)
        },
        componentUpdated(el, { value }) {
            el.$value = value
        },
        // 指令与元素解绑时 移除绑定事件
        unbind (el) {
            el.removeEventListener('click', el.handler)
        }
    }
    
    /**
     *
     * <template>
            <button v-copy="copyText">复制</button>
        </template>
    
        <script>
            export default {
                data() {
                    return {
                        copyText: 'a copy directives',
                    }
                },
            }
        </script>
     */
    
    // v-longpress
    const longpress = {
        bind (el, binding, vNode) {
            if (typeof binding.value !== 'function') {
                throw 'callback must be a function'
            }
            let pressTimer = null
    
            let start = (e) => {
                if (e.type === 'click' && e.button !== 0) {
                    return
                }
                if (pressTimer === null) {
                    pressTimer = setTimeout(() => {
                        handler()
                    }, 2000)
                }
            }
            
            let 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)
        }
    }
    /**
     * <template>
      <button v-longpress="longpress">长按</button>
    </template>
    
    <script>
    export default {
      methods: {
        longpress () {
          alert('长按指令生效')
        }
      }
    }
    </script>
     * 
     */
    
    // 防抖函数限制规定 规定时间内之只能点击一次
    const debounce = {
        inserted (el, binding) {
            let timer
            el.addEventListener('click', () => {
                if (timer) {
                    clearTimeout(timer)
                } 
                timer = setTimeout(() => {
                    binding.value()
                }, 1000)
            })
        }
    }
    /**
     * <template>
      <button v-debounce="debounceClick">防抖</button>
    </template>
    
    <script>
    export default {
      methods: {
        debounceClick () {
          console.log('只触发一次')
        }
      }
    }
    </script>
     */
    
    // 限制输入内容 不能输入表情和特殊字符,只能时数字或字母等 常规方法就是在 change 中做正则校验
    /**
     * <template>
      <input type="text" v-model="note" @change="vaidateEmoji" />
    </template>
    
    <script>
      export default {
        methods: {
          vaidateEmoji() {
            var reg = /[^u4E00-u9FA5|d|a-zA-Z|
    s,.?!,。?!…—&$=()-+/*{}[]]|s/g
            this.note = this.note.replace(reg, '')
          },
        },
      }
    </script>
     */
    // 按照正则校验的规则来设计一个指令
    let findEle = (parent, type) => {
        return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
    }
    const trigger = (el, type) => {
        const e = document.createEvent('HTMLEvents')
        e.initEvent(type, true, true)
        el.dispatchEvent(e)
    }
    const emoji = {
        bind (el, binding, vNode) {
            // 正则规则可根据需求自定义
            var regRule = /[^u4E00-u9FA5|d|a-zA-Z|
    s,.?!,。?!…—&$=()-+/*{}[]]|s/g
            let $inp = findEle(el, 'input')
            el.$inp = $inp
            $inp.handle = function () {
                let val = $inp.value
                $inp.value = val.replace(regRule, '')
    
                trigger($inp, 'input')
            }
            $inp.addEventListener('keyup', $inp.handle)
        },
        unbind (el) {
            el.$inp.removeEventListener('keyup', el.$inp.handle)
        }
    }
    /**
     * <template>
      <input type="text" v-model="note" v-emoji />
    </template>
     */
    
    // 实现一个图片懒加载指令,只加载浏览器可见区域的图片
    /**
     *  图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
          拿到所有的图片 Dom ,遍历每个图片判断当前图片是否到了可视区范围内
            如果到了就设置图片的 src 属性,否则显示默认图片    
            图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,
            二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。
    
        下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,
        如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
     */
    
    const Lazyload = {
        install (Vue, options) {
            const defalutSrc = options.default
            Vue.directive('lazy', {
                bind(el, binding) {
                    Lazyload.init(el, binding.value, defalutSrc)
                },
                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(() => {
                    prevTime = Date.now()
                    timer = null
                    fn.apply(context, args)
                }, delay)
            }
        }
    }
    /**
     * <img v-LazyLoad="xxx.jpg" />
     */
    
    /**
     * 需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。
         自定义一个权限数组
       判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom
     */
    
    function checkArray (key) {
        let arr = ['1', '2', '3', '4']
        let index = arr.indexOf(key)
        if (index > -1) {
            return true
        } else {
            return false
        }
    }
    
    const permission = {
        inserted (el, binding) {
            let permission = binding.value
            if (permission) {
                let hasPermission = checkArray(permission)
                if (!hasPermission) {
                    el.parentNode && el.parentNode.removeChild(el)
                }
            }
        }
    }
    /**
     * <div class="btns">
      <!-- 显示 -->
      <button v-permission="'1'">权限按钮1</button>
      <!-- 不显示 -->
      <button v-permission="'10'">权限按钮2</button>
    </div>
     */
    
    function addWaterMarker(str, parentNode, font, textColor) {
      // 水印文字,父元素,字体,文字颜色
      var can = document.createElement('canvas')
      parentNode.appendChild(can)
      can.width = 200
      can.height = 150
      can.style.display = 'none'
      var 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') + ')'
    }
    
    const waterMarker = {
        bind (el, binding) {
            addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
        }
    }
    /**
     *  <template>
                <div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
            </template>
     */
    
    /**
     * 实现一个拖拽指令,可以在页面可视区域任意拖拽元素
     * 
     * 设置需要拖拽的元素为相对定位,其父元素为绝对定位。
        鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
        鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
        鼠标松开(onmouseup)时完成一次拖拽
     */
    const draggable = {
        inserted (el) {
            el.style.cursor = 'move'
            el.onmosedown = 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
                }
            }
        }
    }
    /**
     * <template>
            <div class="el-dialog" v-draggable></div>
        </template>
     */
    
    
    export default {
        copy,
        longpress,
        debounce,
        emoji,
        Lazyload,
        permission,
        waterMarker,
        draggable
    }

    引入方法:

    新建 directives/index.js 文件:

    import {
        copy,
        longpress,
        debounce,
        emoji,
        Lazyload,
        permission,
        waterMarker,
        draggable
    } from './directives'
    
    const directives = {
        copy,
        longpress,
        debounce,
        emoji,
        Lazyload,
        permission,
        waterMarker,
        draggable
    }
    
    export default {
        install(Vue) {
            Object.keys(directives).forEach((key) => {
                Vue.directive(key, directives[key])
            })
        }
    }

    在 main.js 中注册 Directives 插件:

    import Vue from 'vue'
    import Directives from './directives'
    Vue.use(Directives)

     注:内容来自于网络,侵删。

  • 相关阅读:
    ll command not found 当ll无法识别的解决办法
    idea控制台全屏
    查看centos版本号
    java Error: 无法访问org.apache.http.annotation.ThreadSafe 找不到org.apache.http.annotation.ThreadSafe的类文件
    DigestUtils.md5Hex()加密
    JAVA 8 '::' 关键字
    CVE-2020-1472 NetLogon特权提升漏洞
    OpenSSH的scp命令注入漏洞(CVE-2020-15778)
    redis未授权访问漏洞&简单利用&总结
    常见web信息泄露
  • 原文地址:https://www.cnblogs.com/matthewkuo24/p/14150644.html
Copyright © 2011-2022 走看看