vue3 常用指令 更新中

本文介绍了Vue3中的四个实用指令:v-waterMarker实现文字水印效果,v-dragable提供自定义拖拽功能,v-copy用于复制文本到剪贴板,v-longpress实现长按时触发事件,以及v-debounce防止频繁点击带来的性能问题。
摘要由CSDN通过智能技术生成

vue3 功能大全

v-waterMarker 水印指令

directive/waterMarker/index .ts

/**
 * v-waterMarker可接收参数,均为非必填
 * { text: 'vue-admin-box', font: '16px Microsoft JhengHei', textColor: '#000' }
 */
import { Color, FontFamilyProperty, FontProperty } from 'csstype'
import type { Directive, DirectiveBinding } from 'vue'

const directive: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    binding.value ? binding.value : binding.value = {}
    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor, )
  },
}

function addWaterMarker(str: string, parentNode: HTMLElement, font: FontProperty, textColor: Color) {
  // 水印文字,父元素,字体,文字颜色
  var can = document.createElement('canvas') as HTMLCanvasElement
  parentNode.appendChild(can)
  can.width = 200
  can.height = 150
  can.style.display = 'none'
  var cans = can.getContext('2d') as CanvasRenderingContext2D
  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 ||'He word' , can.width / 10, can.height / 2)
  parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}

export default directive
<template>
  <div class="layout-container">
    <div class="layout-container-table" v-waterMarker>
      我是一个水印页面
    </div>
  </div>
</template>
<script setup lang="ts">
import WaterMarker from '@/directive/waterMarker'
</script>
<style lang="scss" scoped>
</style>

v-dragable 拖拽指令

directive/dragable/index .ts

/**
 * 支持父级,自定义父级,以及window作为父级
 * 使用示例:
 * 1. v-dragable
 * 2. v-dragable="'father'" // 使用父级作为父级
 * 3. v-dragable="'body'" // 使用body对象作为父级
 * 4. v-dragable="'#app'" // 使用id作为父级
 * 5. v-dragable="'.list'" // 使用class名作为父级
 * 3-5代表所有可被document.querySelector()解析的参数值
 **/
import type { Directive, DirectiveBinding } from 'vue'
interface Position {
  x: number,
  y: number
}
interface Mouse {
  down: Position,
  move: Position
}
interface ElType extends HTMLElement {
  __mouseDown__: any,
  __mouseUp__: any,
  __mouseMove__: any,
  __parentDom__: HTMLElement,
  __position__: Position
}
const directive: Directive = {
  mounted: (el: ElType, binding: DirectiveBinding) => {
    setParentDom(el, binding, false)
    // 子级元素位置处理
    // 1. 获取父子元素当前位置
    let parentDomRect: DOMRect
    let elDomRect: DOMRect
    let mouseData: Mouse = {
      down: { x: 0, y: 0},
      move: { x: 0, y: 0 }
    }
    let mouseDown: boolean = false
    el.__position__ = {
      x: 0,
      y: 0
    }
    let bodyUserSelect: string = 'text'
    
    function handleMouseDown(e: MouseEvent) {
      if (e.button !== 0) {
        return
      }
      mouseData.down = {
        x: e.clientX,
        y: e.clientY
      }
      mouseDown = true
      parentDomRect = el.__parentDom__.getBoundingClientRect()
      elDomRect = el.getBoundingClientRect()
      bodyUserSelect = document.querySelector('body')!.style.userSelect
      document.querySelector('body')!.style.userSelect = "none"
    }
    function handleMouseMove(e: MouseEvent) {
      if (!mouseDown) {
        return
      }
      mouseData.move = {
        x: e.clientX,
        y: e.clientY
      }
      setPosition()
    }
    function handleMouseUp(e: MouseEvent) {
      if (mouseDown) {
        mouseDown = false
        document.querySelector('body')!.style.userSelect = bodyUserSelect
      }
    }
    // 用于设置el元素的Position位置
    function setPosition() {
      // 通过几何图形计算更佳,我就是通过几何画图计算出来的当前数据,使用者可以自行计算,得到这两个值
      const x = mouseData.move.x + elDomRect.x - parentDomRect.x - mouseData.down.x
      const y = mouseData.move.y + elDomRect.y - parentDomRect.y - mouseData.down.y
      // 进行x,y坐标边界处理判断
      if (x < 0) {
        el.__position__.x = 0
      } else if (x > parentDomRect.width - elDomRect.width) {
        el.__position__.x = parentDomRect.width - elDomRect.width
      } else {
        el.__position__.x = x
      }
      if (y < 0) {
        el.__position__.y = 0
      } else if (y > parentDomRect.height - elDomRect.height) {
        el.__position__.y = parentDomRect.height - elDomRect.height
      } else {
        el.__position__.y = y
      }
      // 渲染到真实dom属性上
      el.style.cssText += `
        position: absolute;
        z-index: 100;
        left: ${ el.__position__.x }px;
        top: ${ el.__position__.y }px;
      `
    }
    el.__mouseDown__ = handleMouseDown
    el.__mouseMove__ = handleMouseMove
    el.__mouseUp__ = handleMouseUp
    // 2. 监听拖拽事件
    el.addEventListener('mousedown', el.__mouseDown__)
    document.addEventListener('mousemove', el.__mouseMove__)
    document.addEventListener('mouseup', el.__mouseUp__)
  },
  updated(el, binding) {
    setParentDom(el, binding, true)
  },
  beforeUnmount(el: ElType) {
    // 避免重复开销,卸载所有的监听
    // 解决问题:多次创建新的实例 =》 监听不取消 =》 同时存在多个无用的监听,导致页面性能变差
    document.removeEventListener('mousedown', el.__mouseDown__)
    document.removeEventListener('mousemove', el.__mouseMove__)
    document.removeEventListener('mouseup', el.__mouseUp__)
  }
}
// 设置parentDom,供mounted和update使用
function setParentDom(el: ElType, binding: DirectiveBinding, updated: boolean) {
  
  const array = [
    { name: 'father', dom: el.parentElement }
  ]
  
  // 获取父级元素
  let parentDom: HTMLElement | HTMLBodyElement
  // 以下if操作用于确保一定有一个parentDom
  if (binding.value) {
    const findArr = array.find((arr) => {
      return arr.name === binding.value
    })
    if (findArr && findArr.dom) {
      parentDom = findArr.dom
    } else {
      parentDom = document.querySelector(binding.value) || array[0].dom as HTMLElement || array[1].dom
    }
  } else {
    parentDom = array[0].dom as HTMLElement || array[1].dom
  }
  const parentDomRect = parentDom.getBoundingClientRect()
  const elDomRect = el.getBoundingClientRect()
  // 把以前的样式重置一下
  if (el.__parentDom__) {
    el.__parentDom__.style.position = 'static'
  }
  el.__parentDom__ = parentDom
  el.__parentDom__.style.position = 'relative'
  
  if (updated) {
    el.__position__ = {
      x: elDomRect.x - parentDomRect.x,
      y: elDomRect.y - parentDomRect.y
    }
    // return
    el.style.cssText += `
      position: absolute;
      z-index: 100;
      left: ${ el.__position__.x }px;
      top: ${ el.__position__.y }px;
    `
  }
}
export default directive
<template>
  <div class="layout-container">
    <div class="layout-container-table">
      <p>自由切换父级</p>
      <el-button @click="changeBox('father')">father</el-button>
      <el-button @click="changeBox('body')">body</el-button>
      <el-button @click="changeBox('.layout-container-table')">自定义父级类名:.layout-container-table</el-button>
      <p>父级:{{ dragableFather }}</p>
      <div class="box">
        <div class="row" v-dragable="dragableFather"></div>
        <div class="row" v-dragable="dragableFather"></div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import dragable from '@/directive/dragable'
	let dragableFather = ref('body')
    const changeBox = (str: string)=> {
      dragableFather.value = str
    }
</script>

<style lang="scss" scoped>
  .box {
    border: 1px solid #eee;
    width: 100%;
    height: 200px;
    .row {
      width: 50px;
      height: 50px;
      background: red;
    }
  }
</style>

v-copy 复制指令

directive/copy/index .ts

/**
 * v-copy
 * 复制某个值至剪贴板
 * 接收参数:string类型/Ref<string>类型/Reactive<string>类型
 */
import type { Directive, DirectiveBinding } from 'vue'
import { ElMessage } from 'element-plus'
interface ElType extends HTMLElement {
  copyData: string|number,
  __handleClick__: any
}
const directive: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    el.copyData = binding.value
    el.addEventListener('click', handleClick)
  },
  updated(el: ElType, binding: DirectiveBinding) {
    el.copyData = binding.value
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener('click', el.__handleClick__)
  }
}

function handleClick(this: ElType, ev: MouseEvent) {
  let input = document.createElement('input')
  input.value = this.copyData.toLocaleString()
  document.body.appendChild(input)
  input.select()
  document.execCommand('Copy')
  document.body.removeChild(input)
  ElMessage({
    type: 'success',
    message: '复制成功'
  })
}

export default directive
<template>
  <div class="layout-container">
    <div class="layout-container-table">
      <div class="box">
        <el-input v-model="input" placeholder="输入需要粘贴的值"></el-input>
        <el-button v-copy="input" type="primary">复制到剪切板</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import Copy from '@/directive/copy'
let input = ref('')
</script>

<style lang="scss" scoped>
  .box {
    display: flex;
    .el-input {
      margin-right: 10px;
    }
  }
</style>

v-longpress 长按指令

directive/longpress/index .ts

/**
 * v-longpress
 * 长按指令,长按时触发事件
 */
import type { Directive, DirectiveBinding } from 'vue'

const directive: Directive = {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
    if (typeof binding.value !== 'function') {
      throw 'callback must be a function'
    }
    // 定义变量
    let pressTimer: any = null
    // 创建计时器( 2秒后执行函数 )
    let start = (e: MouseEvent|TouchEvent) => {
      if (e.button) {
        if (e.type === 'click' && e.button !== 0) {
          return
        }
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler(e)
        }, 1000)
      }
    }
    // 取消计时器
    let cancel = (e) => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer)
        pressTimer = null
      }
    }
    // 运行函数
    const handler = (e: MouseEvent | TouchEvent) => {
      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)
  },
}

export default directive
<template>
  <div class="layout-container">
    <div class="layout-container-table">
      <el-button v-longpress="setData">长按指令</el-button>
      <p>{{ data }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import Longpress from '@/directive/longpress'
 let data = ref('')
    function setData() {
      data.value = '执行长按指令'
    }
</script>

<style lang="scss" scoped>
  
</style>

v-debounce 按钮防抖指令

directive/v-debounce/index .ts

/**
 * v-debounce
 * 按钮防抖指令,可自行扩展至input
 * 接收参数:function类型
 */
import type { Directive, DirectiveBinding } from 'vue'
interface ElType extends HTMLElement {
  __handleClick__: any
}
const directive: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    if (typeof binding.value !== 'function') {
      console.error('callback must be a function')
      return;
    }
    let timer: NodeJS.Timeout|null = null
    el.__handleClick__ = function(e: ElType) {
      if (timer) {
        clearInterval(timer)
      }
      timer = setTimeout(() => {
        binding.value()
      }, 200)
    }
    el.addEventListener('click', el.__handleClick__)
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener('click', el.__handleClick__)
  }
}

export default directive
<template>
  <div class="layout-container">
    <div class="layout-container-table">
      <div class="box">
        <el-button v-debounce="getData(123)" type="primary">防抖按钮</el-button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ElMessage } from 'element-plus'
import Debounce from '@/directive/debounce'
const getData = (str)=> {
      return function() {
        console.log(str)
        ElMessage({
          type: 'success',
          message: '正在拉取数据'
        })
      }
    }
    onUnmounted(() => {
      ElMessage.closeAll()
    })
</script>

<style lang="scss" scoped>
  .box {
    display: flex;
    .el-input {
      margin-right: 10px;
    }
  }
</style>

v-throttling 节流指令

//节流 // 节流指令 一段时间内只执行一次
const directives = {
    beforeMount(el: HTMLElement, binding: any) {
        let lastExecutedTime: number = 0;
        const HookTime: number = binding.arg || 1000
        el.addEventListener('click', () => {
            const currentTime = Date.now() //获取当前时间
            if (currentTime - lastExecutedTime > HookTime) {
                lastExecutedTime = currentTime;
                binding.value(binding.arg);
            }
        })
    }
}
export default {
    name: "throttling",
    directives
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vue1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值