vue2使用use注册自定义指令实现输入控制与快捷复制

使用场景

在一些form表单填写内容的时候,要限制输入的内容必须是数值、浮点型,本来el-input-number就可以实现,但是它本身带那个数值控制操作,等一系列感觉不舒服的地方。如果只是使用el-input该多好,只要监听一下输入的内容就好了。于是就可以考虑使用注册指令的方式,限制输入的内容。

因为只是限制数值和浮点型,数值挺好的,限制浮点类型最好是可以传入参数,限制具体多少位数。

版本环境

vue的版本是^2.6.12,elementui的版本是^2.15.6,使用到Vue.directive()方法,浏览器的requestAnimationFrame方法、cancelAnimationFrame方法,文档对象document的execCommand方法。

文件位置

与index.js同级的文件夹中。src/directive/input.js

引入方式

在入口文件内引用,

import App from './App'

import "@/directive/input.js"


// 省略其他

new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App)
})

复制功能实现

Vue.directive("copy", {
  bind(el) {
    el.$value = el.innerText
    el.handler = () => {
      const textarea = document.createElement("textarea")
      textarea.readOnly = "readonly"
      textarea.style.position = "absolute"
      textarea.style.left = "-9999px"
      textarea.value = el.innerText.trim()
      document.body.appendChild(textarea)
      textarea.select()
      const result = document.execCommand("Copy")
      if (result) {
        Message.success("复制成功!")
      }
      document.body.removeChild(textarea)
    }
    el.addEventListener("click", el.handler)
  },
  unbind(el) {
    el.removeEventListener("click", el.handler)
  }
})

注册指令过程中有几个钩子函数

实现原理就是为指令绑定的组件设置一个点击事件,在销毁的时候注销掉那个点击事件。

在dom文档流中添加一个textarea文本域,然后设置一系列的样式使其不会出现在视图窗口中,为它赋值,将其添加到文档流中,然后选中整个文本域的内容。然后调用文档流的document.execCommand("Copy")方法,将文本内容复制出来。

这个复制内容功能做的比较简单,因为复制出来的内容是取的元素的innerText的,所以最好是在那种span标签使用这个指令。

使用方式

<div class="page-head-text">
  <span>订单编号:</span>
  <el-tooltip content="复制单号" type="light">
     <span v-copy class="pointer" style="color: #1890ff">
        {{ form.orderSn }}
     </span>
  </el-tooltip>
</div>

限制整数,浮点数的输入

因为是限制输入类型,因此对于绑定的元素需要进行判断,判断是否是一个Input类型。如果不是的话,就直接通过querySelector去寻找input标签。

需要给指令传递参数,用来区分是想进行整数限制,还是浮点数限制。需要注意的是,这个参数和赋值是两个概念。

v-filter:int  其中的int是一个参数,让我们知道这个输入框限制的是整数。

v-filter:decimals  其中的decimals也是一个参数,让我们知道这个输入框限制的是浮点数。如果我们想设置这个浮点数最多两位呢?在组件里面要怎么用?

这就是参数和赋值的区别。以限制两位浮点数为例

v-filter:decimals="2"

在钩子函数的四个参数之一binding对象中,value对应的是2,arg对应的是decimals

Vue.directive("filter", {
  bind(el, binding) {
    if (el.tagName.toLowerCase() !== "input") {
      el = el instanceof HTMLInputElement ? el : el.querySelector("input")
    }
    switch (binding.arg) {
      case "int":
        intFilter(el)
        break
      case "decimals":
        let decimalsFilterO = new DecimalsBinding()
        decimalsFilterO.decimalsFilter(el, binding)
        break
    }
  },
  unbind(el, binding) {
    // 解除事件监听
    switch (binding.arg) {
      case "int":
        el.removeEventListener("input", intFilterEx, true)
        break
      case "decimals":
        el.removeEventListener("input", decimalsFilterEx, true)
        break
      default:
        break
    }
  }
})

整数限制

其中intFilter实现

const intFilter = function (el) {
  el.addEventListener("input", intFilterEx, true)
}
const intFilterEx = e => {
  e.target.removeEventListener("input", intFilterEx, true)
  let stop = requestAnimationFrame(() => {
    let num = e.target.value.replace(/\D/g, "").replace(".", "")
    e.target.value = ""
    e.target.value = num.replace(/^0([0-9])/g, "$1") || 0
    e.target.dispatchEvent(new Event("input"))
    e.target.addEventListener("input", intFilterEx, true)
    window.cancelAnimationFrame(stop)
  }, 1)
}

requestAnimationFrame是浏览器的一个事件,在mdn中有详细的解释

https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame

 浮点数限制

function decimalsFilterEx(e) {
  e.target.removeEventListener("input", this.decimalsFilterEx, true)
  let re = new RegExp(`^\\D*([0-9]\\d*\\.?\\d{0,${this.decimalsBinding.value}})?.*$`)
  let stop = requestAnimationFrame(() => {
    e.target.value = e.target.value.replace(re, "$1").replace(/^0([0-9])/g, "$1")
    e.target.dispatchEvent(new Event("input"))
    e.target.addEventListener("input", this.decimalsFilterEx, true)
    window.cancelAnimationFrame(stop)
  }, 1)
}
class DecimalsBinding {
  decimalsBinding = {}
  decimalsFilter = function (el, binding) {
    this.decimalsBinding = binding
    el.addEventListener("input", this.decimalsFilterEx, true)
  }
  decimalsFilterEx = decimalsFilterEx.bind(this)
}

实现方式 

 第一步,创建一个对象,DecimalsBinding对象有三个属性。decimalsBinding用来存储钩子函数中的binding值信息;decimalsFilter用来设置对象存储的binging值,以及为el添加input事件监听;decimalsFilterEx是事件的执行内容。

这里有一些绕,因为在整个input.js文件中,定义了一个decimalsFilterEx函数方法。而且DecimalsBinding里面也有一个同名的属性。

在new DecimalsBinding()创建实例的时候,对象的decimalsFilterEx与input.js文件中的decimalsFilterEx是同一个,因为bind方法改变了外围的decimalsFilterEx的this指向。此时decimalsFilterEx里面的this指向的是实体类

第二步  decimalsFilter为输入框绑定input事件,在这个事件中,先移除原先绑定的监听事件,设定正则校验,拿到输入框的值,使用replace替换里面的值。

然后重新绑定input事件

requestAnimationFrame是一个异步方法,像setTimeOut一样,会返回一个ID值,用来清除这个执行。

requestAnimationFrame的刷新频率和浏览器的渲染频率一样。因此几乎是无感限制。其他监听方法会有几帧显示输入的内容,然后立马消失。这种方法会让用户体验更好一点。

最后就是cancelAnimationFrame方法,清除这个渲染。因为是嵌套设置,如果不清除的话,就像是在setInterval里面再次调用setInterval一样。

 完整文件代码

import { Message } from "element-ui"
import Vue from "vue"

const intFilter = function (el) {
  el.addEventListener("input", intFilterEx, true)
}
const intFilterEx = e => {
  e.target.removeEventListener("input", intFilterEx, true)
  let stop = requestAnimationFrame(() => {
    let num = e.target.value.replace(/\D/g, "").replace(".", "")
    e.target.value = ""
    e.target.value = num.replace(/^0([0-9])/g, "$1") || 0
    e.target.dispatchEvent(new Event("input"))
    e.target.addEventListener("input", intFilterEx, true)
    window.cancelAnimationFrame(stop)
  }, 1)
}

function decimalsFilterEx(e) {
  e.target.removeEventListener("input", this.decimalsFilterEx, true)
  let re = new RegExp(`^\\D*([0-9]\\d*\\.?\\d{0,${this.decimalsBinding.value}})?.*$`)
  let stop = requestAnimationFrame(() => {
    e.target.value = e.target.value.replace(re, "$1").replace(/^0([0-9])/g, "$1")
    e.target.dispatchEvent(new Event("input"))
    e.target.addEventListener("input", this.decimalsFilterEx, true)
    window.cancelAnimationFrame(stop)
  }, 1)
}
class DecimalsBinding {
  decimalsBinding = {}
  decimalsFilter = function (el, binding) {
    this.decimalsBinding = binding
    el.addEventListener("input", this.decimalsFilterEx, true)
  }
  decimalsFilterEx = decimalsFilterEx.bind(this)
}

Vue.directive("copy", {
  bind(el) {
    el.$value = el.innerText
    el.handler = () => {
      const textarea = document.createElement("textarea")
      textarea.readOnly = "readonly"
      textarea.style.position = "absolute"
      textarea.style.left = "-9999px"
      textarea.value = el.innerText.trim()
      document.body.appendChild(textarea)
      textarea.select()
      const result = document.execCommand("Copy")
      if (result) {
        Message.success("复制成功!")
      }
      document.body.removeChild(textarea)
    }
    el.addEventListener("click", el.handler)
  },
  unbind(el) {
    el.removeEventListener("click", el.handler)
  }
})

Vue.directive("filter", {
  bind(el, binding) {
    if (el.tagName.toLowerCase() !== "input") {
      el = el instanceof HTMLInputElement ? el : el.querySelector("input")
    }
    switch (binding.arg) {
      case "int":
        intFilter(el)
        break
      case "decimals":
        let decimalsFilterO = new DecimalsBinding()
        decimalsFilterO.decimalsFilter(el, binding)
        break
    }
  },
  unbind(el, binding) {
    // 解除事件监听
    switch (binding.arg) {
      case "int":
        el.removeEventListener("input", intFilterEx, true)
        break
      case "decimals":
        el.removeEventListener("input", decimalsFilterEx, true)
        break
      default:
        break
    }
  }
})

export default Vue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值