vue 自定义事件、自定义指令

1,460 阅读2分钟

一个vue 工程,通常都会有对应的全局方法,通过全局引入的方式,更省事。

引用官方:在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

  • 新建一个directives 目录来放置自定义指令
  • 新建一个comPlugin.js 来实现对应全局方法 ufo案例:
import WaterMarker from './directives/waterMarker'
import Copy from './directives/copy'
import Throttle from './directives/throttle'

let ComPlugin = {}
ComPlugin.install = function (Vue, options) {
  // 全局方法或属性
  Vue.globalMethod = function () {}

  // 全局自定义指令
  Vue.directive('WaterMarker', { ...WaterMarker })
  Vue.directive('Copy', { ...Copy })
  Vue.directive('Throttle', { ...Throttle })

  //   全局注入mixins
  Vue.mixin({
    methods: {
      aa() {},
    },
  })
  // 实例方法
  Vue.prototype.$myMethod = function () {}
}

export default ComPlugin

waterMarker.js 代码

let toolMarker = (function () {
  let _can = document.createElement('canvas')
  let _div = document.createElement('div')
  _div.id = 'waterMarker'
  document.body.appendChild(_can)
  _can.width = 300
  _can.height = 360
  _can.style.display = 'none'
  let _cans = _can.getContext('2d')
  _cans.font = '26px Microsoft Yahei'
  _cans.rotate((-20 * Math.PI) / 180)

  function drawWaterMarker(str, textColor) {
    _cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.2)'
    //   清空画板
    _cans.clearRect(0, 0, _can.width, _can.height)
    _cans.fillText(str, _can.width / 10, _can.height / 2)
    _div.style = `
              pointer-events: none;
              top: 70px;
              left: 0px;
              position: fixed;
              z-index: 998;
              width: 100%;
              height: 100%;
              background-image: url(${_can.toDataURL('image/png')})
          `
    document.body.appendChild(_div)
  }
  function updatedWaterMarker(textVal, textColor) {
    if (_cans && _div) {
      _cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.2)'
      //   清空画板
      _cans.clearRect(0, 0, _can.width, _can.height)
      _cans.fillText(textVal, _can.width / 10, _can.height / 6)
      _div.style.backgroundImage = `url(${_can.toDataURL('image/png')})`
    }
  }
  function cleanWaterMarker() {
    _div && _div.parentNode.removeChild(_div)
    _can && _can.parentNode.removeChild(_can)
  }
  return {
    drawWaterMarker,
    updatedWaterMarker,
    cleanWaterMarker,
  }
})()

const lkWaterMarker = {
  bind: function (el, { value }) {
    toolMarker.drawWaterMarker(value.text, value.textColor || '', el)
  },
  update: function (el, binding) {
    let { text } = binding.oldValue
    if (binding.oldValue && text && text !== binding.value.text) {
      //   更新waterMarker 文字
      toolMarker.updatedWaterMarker(binding.value.text, binding.value.textColor || '', el)
    }
  },
  unbind: function () {
    toolMarker.cleanWaterMarker && toolMarker.cleanWaterMarker()
  },
}

export default lkWaterMarker

copy.js 利用两个已有的 Web API(前者属于 HTML5,后者属于 HTML Editing API):

HTMLInputElement.select()
document.execCommand()

用的最多的clipboard.js 库,也是基于上述两年api。 Async Clipboard API来了

const Copy = {
  bind(el, { value }) {
    el.$value = value
    el.handler = () => {
      if (!el.$value) {
        console.warn('内容为空')
        return
      }
      const _textareaDom = document.createElement('textarea')
      // 将该 _textareaDom 设为 readonly 防止 iOS 下自动唤起键盘
      _textareaDom.readOnly = 'readonly'
      _textareaDom.style.position = 'absolute'
      _textareaDom.style.left = '-999px'
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      _textareaDom.value = el.$value
      // 将 _textareaDom 插入到 body 中
      document.body.appendChild(_textareaDom)
      // 选中值并复制
      _textareaDom.select()
      const result = document.execCommand('Copy')
      if (result) {
        console.log('复制成功') // 可根据项目UI仔细设计
      }
      document.body.removeChild(_textareaDom)
    }
    el.addEventListener('click', el.handler)
  },
  componentUpdated(el, { value }) {
    el.$value = value
  },
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}

export default Copy

throttle.js

const lkThrottle = {
  inserted(el, binding) {
    let _inpParams = binding.value
    if (!el) {
      return false
    }
    el.time = 1500
    if (Object.prototype.toString.call(_inpParams) === '[object Function]') {
      el.callback = _inpParams
    } else {
      const { fn, _time } = binding.value
      el.callback = fn
      el.time = _time
    }
    el.addEventListener('click', () => {
      const _nowTime = new Date().getTime()
      if (!el.preTime || _nowTime - el.preTime > el.time) {
        el.preTime = _nowTime
        el.callback && el.callback(el)
      }
    })
  },
  unbind() {},
}

export default lkThrottle

app.js 处使用

import ComPlugin from 'xxx/comPlugin.js'
Vue.use(ComPlugin)

自定义指令参数详解:

指令定义函数的几个钩子函数:

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

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

input自动聚焦也可用自定义实现,或者设置focus="true", 真正开发,老是想不到还有这个额。

Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

其他自定义指令