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
}