精华内容
下载资源
问答
  • 先去vue官网看看自定义指令: 有了钩子和钩子参数,具体的就是怎么实现水印效果了。这里采用的是 canvas 的方式,具体的: 'use strict'; exports.__esModule = true; /** * 自定义指令'v-watermark' * 可以...

    最近遇到个给页面添加水印的需求(之前没弄过也没见过这种效果),然后组长还要求我使用指令的方式实现,做到水印内容、样式基本可配置,说是以后再有这种需求的话就不用再开发了(嗯,道理我都懂,就是有点儿迷茫…)

    先去vue官网看看自定义指令
    在这里插入图片描述
    在这里插入图片描述
    有了钩子和钩子参数,具体的就是怎么实现水印效果了。这里采用的是 canvas 的方式,具体的:

    'use strict';
    
    exports.__esModule = true;
    
    /**
     * 自定义指令'v-watermark'
     * 可以传入自定义参数(v-watermark="markObj"),参数(markObj)格式为
     * {
     *      text: '水印内容',  // 水印文字内容
     *      angle: 25,  // 水印倾斜度
     *      color: 'rgba(0,0,0,.15)',  // 水印文字颜色
     *      fontSize: '16px',  //   水印文字大小
     *      top: 70  // 水印区域距离浏览器可视区域顶部的距离,单位px
     * }
     * 参数是可选的,不传的时候可以直接使用 v-watermark 指令即可
     */
    
    // 记录上一次生成水印的 div 元素 id
    let prevDivId = 0;
    
    const number180 = 180;
    
    // 生成水印
    const setWatermark = markObj => {
        // 默认设置
        const defaultObj = {
            text: '水印内容',
            angle: 25,
            color: 'rgba(0,0,0,.15)',
            fontSize: '16px',
            top: 70
        }
        Object.assign(defaultObj, markObj);
        // 角度转成负数
        defaultObj.angle = -Math.abs(defaultObj.angle);
        // 随机生成一个 id
        const id = Math.random();
        // 清除上次的水印 div[解决:窗口大小调整时(onresize方法)水印会叠加,导致颜色加深的问题]
        if(prevDivId && document.getElementById(prevDivId) !== null) {
            document.body.removeChild(document.getElementById(prevDivId));
        }
        // 记录 id
        prevDivId = id;
    
        // 创建画布
        const canvas = document.createElement('canvas');
        // 设置画布的长、宽
        canvas.width = 240;
        canvas.height = 180;
    
        const context = canvas.getContext('2d');
    
        // 旋转角度(以弧度计)
        context.rotate((defaultObj.angle*Math.PI)/number180);
        context.font = defaultObj.fontSize + ' 微软雅黑';
        // 设置填充绘画的颜色、渐变或者模式
        context.fillStyle = defaultObj.color;
        context.textAlign = 'left';
        context.textBaseline = 'middle';
        context.fillText(defaultObj.text, 0, canvas.height);
    
        const div = document.createElement('div');
        div.id = prevDivId;
        div.style.pointerEvents = 'none';
        div.style.position = 'absolute';
        div.style.top = defaultObj.top + 'px';
        div.style.left = '0px';
        div.style.zIndex = '9999';
        div.style.width = document.documentElement.scrollWidth + 'px';
        div.style.height = document.documentElement.scrollHeight + 'px';
        div.style.background = 'url('+canvas.toDataURL('image/png')+') left top repeat';
        document.body.appendChild(div);
        return prevDivId;
    }
    
    exports.default = {
        bind: function bind(binding) {
            setWatermark(binding.value || {});
            // 监听窗口变化事件
            window.onresize = () => {
                if(prevDivId !== 0) {
                    setWatermark(binding.value || {});
                }
            }
            // 监听页面滚动
            window.onscroll = () => {
                if(prevDivId !== 0) {
                    setWatermark(binding.value || {});
                }
            }
        },
        unbind: function unbind() {
            document.body.removeChild(document.getElementById(prevDivId));
            prevDivId = 0;
        }
    }
    

    在需要使用指令的页面引入 js 文件,然后在 directive 里注册指令,就可以直接在 dom 元素上使用了(比如:注册的自定义指令名为 watermark ,使用 v-watermark 就可以),如果需要传入参数,可以将参数对象传入自定义指令(比如:v-watermark=“markObj")。

    展开全文
  • 几个实用的 Vue 自定义指令 复制粘贴指令 v-copy 长按指令 v-longpress 输入框防抖指令 v-debounce 禁止表情及特殊字符 v-emoji 图片懒加载 v-LazyLoad 权限校验指令 v-premission 实现页面水印 v-waterMarker 拖拽...

    几个实用的 Vue 自定义指令

    复制粘贴指令 v-copy
    长按指令 v-longpress
    输入框防抖指令 v-debounce
    禁止表情及特殊字符 v-emoji
    图片懒加载 v-LazyLoad
    权限校验指令 v-premission
    实现页面水印 v-waterMarker
    拖拽指令 v-draggable在这里插入图片描述

    代码地址:
    https://github.com/mouday/Vue-Demo/tree/main/vue-directive

    参考

    1. https://cn.vuejs.org/v2/guide/custom-directive.html
    2. 8 个非常实用的 Vue 自定义指令
    展开全文
  • vue自定义指令 v-directives 基于 vue 的自定义指令集合,包含 复制粘贴指令 v-copy 长按指令 v-longpress 输入框防抖指令 v-debounce 禁止表情及特殊字符 v-emoji 图片懒加载 v-LazyLoad 权限校验指令 v-...

    vue自定义指令 v-directives (来源网络)

    基于 vue 的自定义指令集合,包含

    • 复制粘贴指令 v-copy
    • 长按指令 v-longpress
    • 输入框防抖指令 v-debounce
    • 禁止表情及特殊字符 v-emoji
    • 图片懒加载 v-LazyLoad
    • 权限校验指令 v-premission
    • 实现页面水印 v-waterMarker
    • 拖拽指令 v-draggable

    v-copy

    需求:实现一键复制文本内容,用于鼠标右键粘贴。

    思路:

    1. 动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
    2. 将要 copy 的值赋给 textarea 标签的 value 属性,并插入到 body
    3. 选中值 textarea 并复制
    4. 将 body 中插入的 textarea 移除
    5. 在第一次调用时绑定事件,在解绑时移除事件
    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.position = 'absolute'
          textarea.style.left = '-9999px'
          // 将要 copy 的值赋给 textarea 标签的 value 属性
          textarea.value = el.$value
          // 将 textarea 插入到 body 中
          document.body.appendChild(textarea)
          // 选中值并复制
          textarea.select()
          const result = document.execCommand('Copy')
          if (result) {
            console.log('复制成功') // 可根据项目UI仔细设计
          }
          document.body.removeChild(textarea)
        }
        // 绑定点击事件,就是所谓的一键 copy 啦
        el.addEventListener('click', el.handler)
      },
      // 当传进来的值更新的时候触发
      componentUpdated(el, { value }) {
        el.$value = value
      },
      // 指令与元素解绑的时候,移除事件绑定
      unbind(el) {
        el.removeEventListener('click', el.handler)
      },
    }
    
    export default copy
    

    使用:给 Dom 加上 v-copy 及复制的文本即可

    <template>
      <button v-copy="copyText">复制</button>
    </template>
    
    <script>
      export default {
        data() {
          return {
            copyText: 'a copy directives',
          }
        },
      }
    </script>
    

    v-longpress

    需求:实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件

    思路:

    1. 创建一个计时器, 2 秒后执行函数
    2. 当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用 mouseout 事件。
    3. 如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
    4. 如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
    5. 在移动端要考虑 touchstart,touchend 事件
    const longpress = {
      bind: function (el, binding, vNode) {
        if (typeof binding.value !== 'function') {
          throw 'callback must be a function'
        }
        // 定义变量
        let pressTimer = null
        // 创建计时器( 2秒后执行函数 )
        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)
      },
    }
    
    export default longpress
    

    使用:给 Dom 加上 longpress 及回调函数即可

    <template>
      <button v-longpress="longpress">长按</button>
    </template>
    
    <script>
    export default {
      methods: {
        longpress () {
          alert('长按指令生效')
        }
      }
    }
    

    v-debounce

    背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。

    需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。

    思路:

    1. 定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
    2. 将时间绑定在 click 方法上。
    const debounce = {
      inserted: function (el, binding) {
        let timer
        el.addEventListener('keyup', () => {
          if (timer) {
            clearTimeout(timer)
          }
          timer = setTimeout(() => {
            binding.value()
          }, 1000)
        })
      },
    }
    
    export default debounce
    

    使用:给 Dom 加上 v-debounce 及回调函数即可

    <template>
      <button v-debounce="debounceClick">防抖</button>
    </template>
    
    <script>
    export default {
      methods: {
        debounceClick () {
          console.log('只触发一次')
        }
      }
    }
    

    v-emoji

    背景:开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。

    我们常规方法是在每一个表单的@change 事件上做处理。

    <template>
      <input type="text" v-model="note" @change="vaidateEmoji" />
    </template>
    
    <script>
      export default {
        methods: {
          vaidateEmoji() {
            var reg = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\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: function (el, binding, vnode) {
        // 正则规则可根据需求自定义
        var regRule = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\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: function (el) {
        el.$inp.removeEventListener('keyup', el.$inp.handle)
      },
    }
    
    export default emoji
    

    使用:将需要校验的输入框加上 v-emoji 即可

    <template>
      <input type="text" v-model="note" v-emoji />
    </template>
    

    v-LazyLoad

    背景:在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。

    需求:实现一个图片懒加载指令,只加载浏览器可见区域的图片。

    思路:

    1. 图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的
    2. 拿到所有的图片 dome ,遍历每个图片判断当前图片是否到了可视区范围内
    3. 如果到了就设置图片的 src 属性,否则显示默认图片

    图片懒加载有两种方式可以实现,一是绑定 srcoll 事件进行监听,二是使用 IntersectionObserver 判断图片是否到了可视区域,但是有浏览器兼容性问题。

    下面封装一个懒加载指令兼容两种方法,判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。

    const LazyLoad = {
      // 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)
        }
      },
    }
    
    export default LazyLoad
    

    使用,将组件内 标签的 src 换成 v-LazyLoad

    <img v-LazyLoad="xxx.jpg" />
    

    v-permission

    背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏,但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。

    需求:自定义一个权限指令,对需要权限判断的 dom 进行显示隐藏。

    思路:

    1. 自定义一个权限数组
    2. 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 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: function (el, binding) {
        let permission = binding.value // 获取到 v-permission的值
        if (permission) {
          let hasPermission = checkArray(permission)
          if (!hasPermission) {
            // 没有权限 移除Dom元素
            el.parentNode && el.parentNode.removeChild(el)
          }
        }
      },
    }
    
    export default permission
    

    使用:给 v-permission 赋值判断即可

    <div class="btns">
      <!-- 显示 -->
      <button v-permission="'1'">权限按钮1</button>
      <!-- 不显示 -->
      <button v-permission="'10'">权限按钮2</button>
    </div>
    

    vue-waterMarker

    需求:给整个页面添加背景水印

    思路:

    1. 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
    2. 将其设置为背景图片,从而实现页面或组件水印效果
    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: function (el, binding) {
        addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
      },
    }
    
    export default waterMarker
    

    使用,设置水印文案,颜色,字体大小即可

    <template>
      <div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
    </template>
    

    v-draggable

    需求:实现一个拖拽指令,可在页面可视区域任意拖拽元素。

    思路:

    1. 设置需要拖拽的元素为相对定位,其父元素为绝对定位。
    2. 鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
    3. 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
    4. 鼠标松开(onmouseup)时完成一次拖拽
    const draggable = {
      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
          }
        }
      },
    }
    export default draggable
    

    使用: 在 Dom 上加上 v-draggable 即可

    <template>
      <div class="el-dialog" v-draggable></div>
    </template>
    
    展开全文
  • 文章目录vue2中如何实现自定义指令vue2与vue3自定义指令对比v-LazyLoad懒加载v-debounce防抖v-throttle节流v-formatter格式化指令v-copy一键复制v-longpress长按v-emoji禁止输入表情和特殊字符v-permission权限vue-...

    vue中提供了一套为数据驱动视图更为方便的操作,这些操作被称为指令系统。

    v-开头的行内属性,都是指令,不同的指令可以完成或实现不同的功能。

    除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令

    指令使用的几种方式:

    //会实例化一个指令,但这个指令没有参数 
    v-xxx
    
    // -- 将值传到指令中
    v-xxx="value"
    
    // -- 将字符串传入到指令中,如`v-html="'<p>内容</p>'"`
    v-xxx="'string'"
    
    // -- 传参数(`arg`),如`v-bind:class="className"`
    v-xxx:arg="value"
    
    // -- 使用修饰符(`modifier`)
    v-xxx:arg.modifier="value"
    

    在 Vue 3 中自定义组件发生了哪些改变:

    • 自定义指令的 API 改了名字,名字更贴近组件的生命周期
    • 自定义指令可以通过子组件的 v-bind="$attr"传递

    vue2中如何实现自定义指令

    vue2中,注册一个自定义指令有全局注册局部注册

    先来看看全局注册。

    全局注册主要是用过Vue.directive方法进行注册。

    Vue.directive第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数

    // 注册一个全局自定义指令 `v-focus`
    Vue.directive('focus', {
      // 当被绑定的元素插入到 DOM 中时……
      inserted: function (el) {
        // 聚焦元素
        el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能
      }
    })
    

    在工程情况下,批量注册指令,最好单独建立相应的文件夹来存放指令代码,再一次性全局挂载到VUE实例上,例如:新建 util/directives/index.js 文件如下。

    import copy from './copy'
    import longpress from './longpress'
    
    // 自定义指令
    const directives = {
      copy,
      longpress,
    }
    
    export default {
      install(Vue) {
        Object.keys(directives).forEach((key) => {
          Vue.directive(key, directives[key])
        })
      },
    }
    

    main.js 引入并调用

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

    局部注册没啥好说的,直接在组件options选项中设置directive属性

    directives: {
      focus: {
        // 指令的定义
        inserted: function (el) {
          el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
        }
      }
    }
    

    然后你可以在模板中任何元素上使用新的 v-focus property,如下:

    <input v-focus />
    

    自定义指令也像组件那样存在钩子函数:

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
    • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
    • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
    • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用
    • unbind:只调用一次,指令与元素解绑时调用

    所有的钩子函数的参数都有以下:

    • el:指令所绑定的元素,可以用来直接操作 DOM

    • binding:一个对象,包含以下 property

      • name:指令名,不包括 v- 前缀。
      • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
      • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
      • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
      • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
      • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
    • vnodeVue 编译生成的虚拟节点

    • oldVnode:上一个虚拟节点,仅在 updatecomponentUpdated 钩子中可用

    除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行

    举个例子:

    <div v-demo="{ color: 'white', text: 'hello!' }"></div>
    <script>
        Vue.directive('demo', function (el, binding) {
        	console.log(binding.value.color) // "white"
        	console.log(binding.value.text)  // "hello!"
        })
    </script>
    

    vue2与vue3自定义指令对比

    vue2中的一个自定义指令示例如下。

    <h1 v-highlight="red">这是一串被高亮为红色的字</h1>
    <script>
    Vue.directive('highlight', {
      bind(el, binding, vnode) {
        el.style.background = binding.value;
      }
    });
    </script>
    

    如上是一个很灵活的做法,通过指令传值的做法,可以供开发者根据使用场景的不同提供不同的参数,以达到不同的效果。

    而在 Vue 3 中,自定义指令的 API 被打造得更加“被人所熟悉”,更方便开发者从生命周期的角度去理解其工作原理。各钩子具体变动如下:

    • bind => beforeMount
    • inserted => mounted
    • beforeUpdate: 新的钩子,会在元素自身更新前触发
    • update => 移除!
    • componentUpdated => updated
    • beforeUnmount: 新的钩子,当元素自身被卸载前触发
    • unbind => unmounted

    搭配上vue3的reactive api,新版的自定义指令的完整形式大抵如下:

    const app = Vue.createApp({});
    app.directive('highlight', {
      beforeMount(el, binding, vnode, prevVnode) {},
      mounted() {},
      beforeUpdate() {},
      updated() {},
      beforeUnmount() {},
      unmounted() {},
    })
    

    上面的那个v-highlight例子就会被改写成:

    <h1 v-highlight="red">这是一串被高亮为红色的字</h1>
    
    <script>
    const app = Vue.createApp({});
    app.directive('highlight', {
      beforeMount(el, binding, vnode) {
        el.style.background = binding.value;
      },
    });
    </script>
    

    下面分享几个及其实用的 Vue 自定义指令

    • 复制粘贴 v-copy
    • 长按 v-longpress
    • 输入框防抖 v-debounce
    • 格式化v-formatter
    • 禁止表情及特殊字符 v-emoji
    • 图片懒加载 v-LazyLoad
    • 权限校验指令 v-premission
    • 实现页面水印 v-waterMarker
    • 拖拽 v-draggable

    v-LazyLoad懒加载

    关于懒加载,我的相关博文中有所详细地介绍了,主要有以下两种方式。

    • IntersectionObserver

    • srcoll事件监听 + 节流

    // lazyLoad.js
    const LazyLoad = {
      // 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)
        }
      },
    }
    
    export default LazyLoad
    

    使用,将组件内 标签的 src 换成 v-LazyLoad

    <img v-LazyLoad="xxx.jpg" />
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-debounce防抖

    防抖是基本常识了,不多说。

    let debounce = {
      inserted: function (el, binding) {
        let timer
        el.addEventListener('keyup', () => {
          if (timer) {
            clearTimeout(timer)
          }
          timer = setTimeout(() => {
            binding.value()
          }, 1000)
        })
      },
    }
    
    export default debounce
    

    使用:给 Dom 加上 v-debounce 及回调函数即可

    <template>
      <button v-debounce="debounceClick">防抖</button>
    </template>
    
    <script>
    export default {
      methods: {
        debounceClick () {
          console.log('点击了一次')
        }
      }
    }
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-throttle节流

    防抖这种情况设置一个v-throttle自定义指令来实现

    举个例子:

    // 1.设置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);
      },
    });
    // 2.为button标签设置v-throttle自定义指令
    <button @click="sayHello" v-throttle>提交</button>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-formatter格式化指令

    有些时候需要给数字按3位加逗号的,我们可以使用自定义指令和正则表达式来完成这。

    <body>
      <div id="app">
        <input type="text" v-model="total">
        <br>
        <span v-formatter="total"></span>
      </div>
      <script>
        let Utils = {
          formmatter(el,value){
            value = value + "";
            let hasDot = /\./g;
            let patt = /\B(?=(\d{3})+\.)/g
            if (!hasDot.test(value)) {
              //没有小数点的正则表达式
              patt = /\B(?=(\d{3})+$)/g
            }
            let text = value.replace(patt, ",");
            el.innerText = text;
          }
        }
        
        Vue.directive('formatter', {
          bind:function(el,binding){
            let { value } = binding;
            Utils.formmatter(el,value);
          },
          update: function (el, binding) {
            let { value,oldValue } = binding;
            if (value === oldValue) {
              return;
            }
            Utils.formmatter(el,value)
          },
        });
        
        let vm = new Vue({
          el: "#app",
          data: {
            total: 1234567890,
          }
        });
      </script>
    </body>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-copy一键复制

    实现一键复制文本内容,用于鼠标右键粘贴。

    1. 动态创建 textarea 标签,并设置 readOnly 属性及移出可视区域
    2. 将要复制的值赋给 textarea 标签的 value 属性,并插入到 body
    3. 选中值 textarea 并复制
    4. body 中插入的 textarea 移除
    5. 在第一次调用时绑定事件,在解绑时移除事件
    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.position = 'absolute'
          textarea.style.left = '-9999px'
          // 将要 copy 的值赋给 textarea 标签的 value 属性
          textarea.value = el.$value
          // 将 textarea 插入到 body 中
          document.body.appendChild(textarea)
          // 选中值并复制
          textarea.select()
          const result = document.execCommand('Copy')
          if (result) {
            console.log('复制成功') // 可根据项目UI仔细设计
          }
          document.body.removeChild(textarea)
        }
        // 绑定点击事件,就是所谓的一键 copy 啦
        el.addEventListener('click', el.handler)
      },
      // 当传进来的值更新的时候触发
      componentUpdated(el, { value }) {
        el.$value = value
      },
      // 指令与元素解绑的时候,移除事件绑定
      unbind(el) {
        el.removeEventListener('click', el.handler)
      },
    }
    
    export default copy
    

    使用:给 Dom 加上 v-copy 及复制的文本即可

    <template>
      <button v-copy="copyText">复制</button>
    </template>
    
    <script>
      export default {
        data() {
          return {
            copyText: 'a copy directives',
          }
        },
      }
    </script>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-longpress长按

    实现长按,用户需要按下并按住按钮几秒钟,触发相应的事件

    1. 创建一个计时器, 2 秒后执行函数
    2. 当用户按下按钮时触发 mousedown 事件,启动计时器;用户松开按钮时调用mouseout 事件。
    3. 如果 mouseup 事件 2 秒内被触发,就清除计时器,当作一个普通的点击事件
    4. 如果计时器没有在 2 秒内清除,则判定为一次长按,可以执行关联的函数。
    5. 在移动端要考虑 touchstarttouchend 事件
    const longpress = {
      bind: function (el, binding, vNode) {
        if (typeof binding.value !== 'function') {
          throw 'callback must be a function'
        }
        // 定义变量
        let pressTimer = null
        // 创建计时器( 2秒后执行函数 )
        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)
      },
    }
    
    export default longpress
    

    使用:给 Dom 加上 v-longpress 及回调函数即可

    <template>
      <button v-longpress="longpress">长按</button>
    </template>
    
    <script>
    export default {
      methods: {
        longpress () {
          alert('长按指令生效')
        }
      }
    }
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-emoji禁止输入表情和特殊字符

    开发中遇到的表单输入,往往会有对输入内容的限制,比如不能输入表情和特殊字符,只能输入数字或字母等。

    <template>
      <input type="text" v-model="note" @change="vaidateEmoji" />
    </template>
    
    <script>
      export default {
        methods: {
          vaidateEmoji() {
            var reg = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\s,.?!,。?!…—&$=()-+/*{}[\]]|\s/g
            this.note = this.note.replace(reg, '')
          },
        },
      }
    </script>
    

    根据正则表达式,设计自定义处理表单输入规则的指令,下面以禁止输入表情和特殊字符为例。

    let findEle = (parent, type) => {
      return parent.tagName.toLowerCase() === type ? parent : parent.querySelector(type)
    }
    
    let trigger = (el, type) => {
      let e = document.createEvent('HTMLEvents')
      e.initEvent(type, true, true)
      el.dispatchEvent(e)
    }
    
    let emoji = {
      bind: function (el, binding, vnode) {
        // 正则规则可根据自定义
        var regRule = /[^\u4E00-\u9FA5|\d|\a-zA-Z|\r\n\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: function (el) {
        el.$inp.removeEventListener('keyup', el.$inp.handle)
      },
    }
    
    export default emoji
    

    使用:将需要校验的输入框加上 v-emoji 即可

    <template>
      <input type="text" v-model="note" v-emoji />
    </template>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    v-permission权限

    自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。

    1. 自定义一个权限数组
    2. 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 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: function (el, binding) {
        let permission = binding.value // 获取到 v-permission的值
        if (permission) {
          let hasPermission = checkArray(permission)
          if (!hasPermission) {
            // 没有权限 移除Dom元素
            el.parentNode && el.parentNode.removeChild(el)
          }
        }
      },
    }
    
    export default permission
    

    使用:给 v-permission 赋值判断即可

    <div class="btns">
      <!-- 显示 -->
      <button v-permission="'1'">权限按钮1</button>
      <!-- 不显示 -->
      <button v-permission="'10'">权限按钮2</button>
    </div>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted



    vue-waterMarker添加水印

    给页面添加背景水印

    1. 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
    2. 将其设置为背景图片,从而实现页面或组件水印效果
    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: function (el, binding) {
        addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
      },
    }
    
    export default waterMarker
    

    使用,设置水印文案,颜色,字体大小即可

    <template>
      <div v-waterMarker="{text:'lzg版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
    </template>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted



    v-draggable拖拽

    实现一个拖拽指令,可在页面可视区域任意拖拽元素。

    1. 设置需要拖拽的元素为相对定位,其父元素为绝对定位。
    2. 鼠标按下(onmousedown)时记录目标元素当前的 lefttop 值。
    3. 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 lefttop
    4. 鼠标松开(onmouseup)时完成一次拖拽
    const draggable = {
      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
          }
        }
      },
    }
    export default draggable
    

    使用: 在 Dom 上加上 v-draggable 即可

    <template>
      <div class="el-dialog" v-draggable></div>
    </template>
    

    vue3写法

    大体变动不大,只需将相关生命周期钩子函数名替换一下。

    • bind => beforeMount
    • inserted => mounted

    小结

    好了,vue2和vue3中的指令大概就介绍到这,导入时只需像下面这样以插件的方式导入即可。

    /// main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    import directives  from './util/directives'
    
    let app = createApp(App)
    // 导入自定义指令
    app.use(directives)
    app.use(router).mount('#app')
    

    之后会争取出一篇vue3工程中的自定义指令库的构建方法,并在github上放上代码

    展开全文
  • Vue 自定义指令有全局注册和局部注册两种方式。先来看看注册全局指令的方式,通过 Vue.directive( id, [definition] ) 方式注册全局指令。然后在入口文件中进行 Vue.use() 调用。 批量注册指令,新建 directives/...
  • 最近看了一篇用 Angular4+ 写的添加水印功能,觉得挺好玩,就自己试着用 vue 写一个相同功能 效果 LIVE ☀️???????????? 在线例子 ????????☃️❄️ 代码 ✈️✈️✈️✈️✈️✈️ 直达完整代码 ????✈️????????...
  • 给项目的整个背景加上自定义水印,可以改变水印的文案和字体颜色等 实现思路 这里使用的技术主要是canvas,在实现水印的过程中,通过canvas 特性生成 base64 格式的图片文件 使用 canvas 特性生成 base64 格式的图片...
  • vue水印

    2020-11-12 09:20:26
    vue项目中通过自定义指令,使用canvas特 性生成base64格式的图片文件,并将其设置为背景图片,从而实现页面或组件局部水印效果 1.新建reg.js文件 Vue.directive('watermark',(el,binding)=>{ function ...
  • 因此,在很多的pc端的开发页面上,经常能看见水印,这种水印的实现,在html、vue等技术上对于开发过的人来说十分的简单。当然,App端同业也有很多的敏感信息,避免暴露出去因此需要加上水印。 自己这次的开发需求上...
  • VUE常用的自定义指令

    2020-12-15 11:16:27
    基于 vue自定义指令集合,包含 复制粘贴指令 v-copy 长按指令 v-longpress 输入框防抖指令 v-debounce 禁止表情及特殊字符 v-emoji 图片懒加载 v-LazyLoad 权限校验指令 v-premission 实现页面水印 v-...
  • vue 项目通过vue指令添加水印

    千次阅读 2019-10-05 09:41:44
    vue项目中通过自定义指令,使用canvas特性生成base64格式的图片文件,并将其设置为背景图片,从而实现页面或组件局部水印效果 1、新建directives.js import Vue from 'vue' Vue.directive('watermark',(el,...
  • 指令包含复制、指定显示行数、外部点击、防抖节流、element dialog改变大小,拖拽、只可以输入数字、长点击、权限、水印
  • vue 项目中添加水印效果

    千次阅读 2021-02-25 10:51:50
    在项目中有给部分区域或者是整个页面添加水印的需求。...一、自定义指令: 1、在 directives 文件夹下面 创建一个 watermark.js 文件 import Vue from 'vue'; export default () => { Vue.directive('watermark'
  • 文章目录一、新建 watermark.js二、min.js 引入 watermark.js三、调用指令 ... function addWaterMarker(str, parentNode, font, textColor) {// 水印文字,父元素,字体,文字颜色 var can = document.create
  • Vue 中集成 Mozilla/PDF.js ,实现自定义的 PDF 预览器,以及给被预览的 PDF 添加水印 实现效果 可用插件介绍 Mozilla 提供了 PDF.js 和 pdfjs-dist ,两者的区别如下: * PDF.js ,一个完整的 PDF 查看器,...
  • 需求:在项目中为了防止截图或者下载导致内部图片泄露,或者图片传播溯源,需要将项目中展示的图片添加上属于自己公司的水印标识。 解决思路: 1、 先将img图片绘制为canvas 2、 使用canva 绘制所要自定义添加的内容...
  • demo18:在ElementUI中插入自定义标签 demo19:动态读取图片 demo20:Mixin demo21:组件上使用v-model demo22:队列和promise的一个实际问题 demo23:算法相关 demo24:Vue的过滤器filter demo25:富文本编辑器 ...
  • 给页面添加水印

    2020-06-03 16:00:01
    这里使用了vue中的自定义指令 main.js // vue 全局指令 --- 模块/组件式添加水印 Vue.directive('watermark',(el,binding)=>{ function addWaterMarker(str,parentNode,font,textColor){// 水印文字,父...
  • 使用canvas生成PDF,并含有水印 在项目中有一个功能是将获得的数据以PDF的方式展示,并且有水印 生成水印 这里使用自定义指令来实现操作 生成watermark.js来设置这个自定义指令 import Vue from 'vue' Vue....
  • watermark.js

    2019-09-13 19:36:42
    该插件支持vue单独区域绘制用户自定义水印,可以直接在项目中使用。
  • WePY Plugin For IntelliJ Platform - 让PhpStorm/WebStorm全面支持WePY的开发,包括API(原生/WePY)和组件(官方/自定义)的自动完成/错误检查/高亮/不依赖Vue/... wxml - vscode插件--微信小程序格式化以及高亮组件...
  • 2、IAM 身份认证基础组件 及 配套VUE前端框架(diboot-antd-admin、diboot-element-admin) 开箱即用的RBAC角色权限模型与预置组织人员岗位模型 基于JWT的认证授权,支持申请token、刷新token 简化的BindPermission...
  • ✅ 数字水印:肉眼不可见的水印 (严格地说来是肉眼不容易分辨的水印,所以比较难以识别出来并去除,减少破坏图片的完整性) 2021年1月5号添加 阿树(上海) - Github ✅ 早晨计划:帮你早起一小时,规划生活,...

空空如也

空空如也

1 2
收藏数 29
精华内容 11
关键字:

vue自定义水印

vue 订阅