vue3 自定义指令 Directive, 小demo盒子拖拽、图片懒加载、hook

97 阅读1分钟
自定义指令
使用场景: 鉴权 像v-if v-model这些都是内置的指令 自己也可以定义 
命名规则:第一个字母为v
自定义指令内部也有生命周期钩子
使用方法:
<A v-move:aaa.xiaoman="{background: 'red'}"></A>
const vMove: Directive = {
  created() {
    console.log('======>created')
  },
  beforeMount() {
    console.log('=====>beforeMount')
  },
  mounted(...args: any[]) {
    console.log('=====>mounted', args)
  },
  unmounted() {
  }
}
括号里接收的第一个值为绑定的元素本身,第二个值为冒号后面的值dir,也能通过value获取background red

鉴权场景
如果当前用户没有添加编辑删除命令,对应按钮就隐藏
<template>
  <div class="btns">
    <button v-has-show="'shop:create'">创建</button>
    <button v-has-show="'shop:edit'">编辑</button>
    <button v-has-show="'shop:delete'">删除</button>
  </div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
import type { Directive } from 'vue'
localStorage.setItem('userId', 'xm')
const permission = ['xm:shop:edit', 'xm:shop:create', 'xm:shop:delete']
const userId = localStorage.getItem('userId') as string
const vHasShow: Directive = (el, binding) => {
  console.log(el, binding)
  if (!permission.includes(userId + ':' + binding.value)) {
    el.style.display = 'none'
  }
}
</script>

盒子拖拽
<template>
  <div v-move class="box">
    <div class="header"></div>
    <div>内容</div>
  </div>
</template>
<script setup lang='ts'>
import { ref, Directive, DirectiveBinding } from 'vue'
const vMove = (el, binding) => {
  let moveElement = el.firstElementChild

  const mouseDown = e => {
    let X = e.clientX - el.offsetLeft
    let Y = e.clientY - el.offsetTop
    const move = e => {
      el.style.left = e.clientX - X + 'px'
      el.style.top = e.clientY - Y + 'px'
    }
    document.addEventListener('mousemove', move)
    document.addEventListener('mouseup', () => {
      document.removeEventListener('mousemove', move)
    })
  }

  // 给header 加鼠标摁下时监听事件
  moveElement.addEventListener('mousedown', mouseDown)
}
</script>
<style lang='scss' scoped>
.box {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 200px;
  height: 200px;
  border: 2px solid black;
  .header {
    height: 20px;
    background-color: black;
  }
}
</style>

图片懒加载
<template>
  <div>
    <div>
      <img v-lazy="item" v-for="item in arr" alt="img" width="300" height="300">
    </div>
  </div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
import type { Directive } from 'vue'
let imageList: Reacord<string> = import.meta.glob('../public/*.*', { eager: true })
let arr = Object.values(imageList).map(v => v.default)
console.log(arr)

let VLazy: Directive<HTMLImageElement, string> = async (el, bingding) => {
  const def = await import('../public/vite.svg')
  el.src = def.default
  console.log('el', el)
  // 判断元素是否在可视区
  const observer = new IntersectionObserver(enr => {
    console.log('===>', enr[0])
    console.log('bingding', bingding.value)
    if (enr[0].intersectionRatio > 0) {
      setTimeout(() => {
        el.src = bingding.value
        // 取消监听
        observer.unobserve(el)
      }, 1000)
    }
  })
  传入要监听的元素
  observer.observe(el)
}
</script>

一个将图片生成base64的hook
hook ts
import { onMounted } from 'vue'
type Options = {
	el: string
}
export default function (options: Options):Promise<{BaseUrl:string}> {
	return new Promise(res => {
		onMounted(() => {
			let img: HTMLImageElement = document.querySelector(options.el) as HTMLImageElement
			img.onload = () => {
				res({
					baseUrl: base64(img)
				})
			}
		})
		const base64 = (el:HTMLImageElement) => {
			const canvas = document.createElement('canvas')
			const ctx = canvas.getContext('2d')
			canvas.width = el.width
			canvas.height = el.height
			ctx?.drawImage(el, 0, 0, canvas.width, canvas.height)
			return canvas.toDataURL('image/png')
		}
	})
}
组件
<template>
  <div>
    <img src="../public/1.png" alt="1">
  </div>
</template>
<script setup lang='ts'>
import useBase64 from './hooks/index'
useBase64({
  el: 'img'
}).then(res => {
  console.log(res.baseUrl)
})
</script>