基于Vue3+TypeScript内置指令和封装6个常用的自定义指令

 内置指令

Vue3逐渐的成熟并且渐渐的被大众选择,开发过程中如果想要提高效率以及技术要点,可以尝试封装一些公共的方法或者指令,内置的核心功能默认提供了一些内置指令例如:

 <!-- v-text -->
  <p v-text="text"></p>

  <!-- v-html -->
  <p v-html="html"></p>

  <!-- v-show -->
  <p v-show="show"></p>

  <!-- v-if组合 -->
  <p v-if="showIf"></p>
  <p v-else-if="!showIf"></p>
  <p v-else></p>

  <!-- v-for -->
  <p v-for="item in array" :key="item">{{ item }}</p>

  <!-- v-on缩写@ -->
  <el-button type="primary" @click="onClick"> 按钮 </el-button>

  <!-- v-bind缩写: -->
  <img v-bind:src="imageSrc" />

  <!-- v-model -->
  <el-input v-model="value" />

  <!-- v-slot缩写# -->
  <base-layout>
    <template v-slot:header></template>
  </base-layout>

  <!-- v-pre -->
  <p v-pre></p>

  <!-- v-cloak -->
  <div v-cloak>{{ cloak }}</div>

  <!-- v-once -->
  <p v-once></p>

 自定义指令

在开发过程中,有的情况下需要对普通DOM元素进行底层操作,这时候就会用到自定义指令。Vue自定义指令分为全局注册和局部注册两种方式,注册全局指令的方式是通过App.directive(key,directive),然后在main.js文件引入并调用。 新建directives文件夹下新建modules文件夹和index.ts文件,批量注册指令,index.ts文件。

// import directives
import { App } from "vue";
import copy from "./modules/copy";
import waterMarker from "./modules/waterMarker";
import draggable from "./modules/draggable";
import debounce from "./modules/debounce";
import throttle from "./modules/throttle";
import longpress from "./modules/longpress";

const directivesList: any = {
    // Custom directives
    copy,
    waterMarker,
    draggable,
    debounce,
    throttle,
    longpress
};

const directives = {
    install: function (app: App<Element>) {
        Object.keys(directivesList).forEach(key => {
            // 注册自定义指令
            app.directive(key, directivesList[key]);
        });
    }
};

export default directives;

 在main.js引入并调用

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import directives from '@/directives/index.ts'
import router from './routers/index.js'

const app = createApp(App)
app.use(ElementPlus, {
    size: 'small',
    zIndex: 3000,
    locale: zhCn
})

指令定义函数提供了几个钩子函数(可选):

  • created:在绑定元素的attribute或事件监听器被应用之前调用。
  • beforeMount: 当指令第一次绑定到元素并且在挂载父组件之前调用。
  • mounted: 在绑定元素的父组件被挂载后调用。
  • beforeUpdate: 在更新包含组件的VNode之前调用。
  • updated: 在包含组件的VNode及其子组件的VNode更新后调用。
  • beforeUnmount: 在卸载绑定元素的父组件之前调用。
  • unmounted: 当指令与元素解除绑定且父组件已卸载时,只调用一次。

v-copy复制指令 

     功能:通过鼠标点击复制绑定的文本内容,可以绑定按钮一起使用。

  1. 定义封装点击函数handleClick手动创建input标签且赋值,并让标签隐藏。
  2. 将input标签插入到body内,并复制其内容后将input框从body中移除。
  3. 在第一次调用时绑定事件,和解绑时移除事件。
/**
 * 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 copy: 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: any) {
    const input = document.createElement("input");
    input.style.opacity = '0'
    input.value = this.copyData.toLocaleString();
    document.body.appendChild(input);
    input.select();
    document.execCommand("Copy");
    document.body.removeChild(input);
    ElMessage({
        type: "success",
        message: "复制成功"
    });
  }
 
 export default copy;

 使用:只需在DOM节点上绑定v-copy的文本内容,通过鼠标点击就可以赋值其内容到粘贴板上。

<template>
    <div class="content-box">
        <span class="text">复制指令</span>
        <div class="box-content">
            <el-input placeholder="请输入内容" v-model="data">
                <template #append>
                    <el-button v-copy="data">复制</el-button>
                </template>
            </el-input>
        </div>
    </div>
</template>

<script setup lang="ts" name="copyDirect">
import { ref } from "vue";

const data = ref<string>("我是被复制的内容!!!");

</script>

v-debounce防抖指令

功能:多次点击按钮会多次触发事件,如果是在表单提交就会多次调用后端接口,就会达不到需求,防止多次触发。

  1. 通过监听点击事件设置定时器,设置几秒内才触发点击事件,通过绑定和解绑移除事件。
/**
 * v-debounce
 * 按钮防抖指令,可自行扩展至input
 * 接收参数:function类型
 */
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
  __handleClick__: () => any;
}
const debounce: Directive = {
  mounted(el: ElType, binding: DirectiveBinding) {
    if (typeof binding.value !== "function") {
      throw "callback must be a function";
    }
    let timer: NodeJS.Timeout | null = null;
    el.__handleClick__ = function () {
      if (timer) {
        clearInterval(timer);
      }
      timer = setTimeout(() => {
        binding.value();
      }, 500);
    };
    el.addEventListener("click", el.__handleClick__);
  },
  beforeUnmount(el: ElType) {
    el.removeEventListener("click", el.__handleClick__);
  }
};

export default debounce;

使用:只需在DOM节点上绑定v-debounce及回调函数即可。

<template>
  <div class="content-box">
    <span class="text">防抖指令</span>
    <el-button type="primary" v-debounce="debounceClick">防抖按钮 (0.5秒后执行)</el-button>
  </div>
</template>

<script setup lang="ts" name="debounceDirect">
import { ElMessage } from 'element-plus'
const debounceClick = () => {
  ElMessage.success('我是防抖按钮触发的事件 🍍🍓🍌')
}
</script>

v-draggable拖拽指令

功能:通过鼠标按住在页面可视区域内任意拖拽元素。

  1. 设置需要拖拽的元素为absolute,其父元素为relative。
  2. 鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
  3. 鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值。
  4. 鼠标松开(onmouseup)时完成一次拖拽。
import type { Directive } from "vue";
interface ElType extends HTMLElement {
    parentNode: any;
}
const draggable: Directive = {
    mounted: function (el: ElType) {
        el.style.cursor = "move";
        el.style.position = "absolute";
        el.onmousedown = function (e) {
            let disX = e.pageX - el.offsetLeft;
            let disY = e.pageY - el.offsetTop;
            document.onmousemove = function (e) {
                let x = e.pageX - disX;
                let y = e.pageY - disY;
                let maxX = parseInt(window.getComputedStyle(el.parentNode).width) - parseInt(window.getComputedStyle(el).width);
                let maxY = parseInt(window.getComputedStyle(el.parentNode).height) - parseInt(window.getComputedStyle(el).height);
                if (x < 0) {
                        x = 0;
                } else if (x > maxX) {
                        x = maxX;
                }

                if (y < 0) {
                        y = 0;
                } else if (y > maxY) {
                        y = maxY;
                }
                el.style.left = x + "px";
                el.style.top = y + "px";
            };
            document.onmouseup = function () {
                document.onmousemove = document.onmouseup = null;
            };
        };
    }
};

使用:只需在DOM节点上绑定v-draggable即可。

<template>
    <div class="content-box">
        <span class="text">拖拽指令</span>
        <div v-draggable class="drag-box flx-center text">我可以拖拽哦~</div>
    </div>
</template>

<style scoped lang="scss">
.content-box {
   height: 500px;
   width: 1200px;
   position: relative;
    .drag-box {
        position: absolute;
        top: 110px;
        height: 300px;
        width: 300px;
        background: #dfe6e9;
        color: #b2bec3;
        border-radius: 50%;
    }
}
</style>

v-longpress长按指令

功能:通过鼠标长按页DOM节点触发回调执行所需的功能。

  1. 通过监听鼠标的点击事件和移动事件设置定时器,设置几秒内才触发回调函数,如果鼠标长按在时间内则取消事件监听。
**
 * 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秒后执行函数 )
         const start = (e: any) => {
             if (e.button) {
                 if (e.type === "click" && e.button !== 0) {
                     return;
                 }
             }
             if (pressTimer === null) {
                 pressTimer = setTimeout(() => {
                     handler(e);
                 }, 1000);
             }
         };
         // 取消计时器
         const cancel = () => {
             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;

使用:只需在DOM节点上绑定v-longpress及回调函数即可

<template>
  <div class="content-box">
    <span class="text">长按指令</span>
    <el-button type="primary" v-longpress="longpress" >长按2秒触发事件</el-button>
  </div>
</template>

<script setup lang="ts" name="longpressDirect">
import { ElMessage } from 'element-plus'
const longpress = () => {
  ElMessage.success('长按事件触发成功')
}
</script>

v-throttle节流指令

功能:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。

  1. 第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮。
  2. 将需要触发的方法绑定在指令上。
import type { Directive, DirectiveBinding } from "vue";
interface ElType extends HTMLElement {
    __handleClick__: () => any;
    disabled: boolean;
}
const throttle: Directive = {
    mounted(el: ElType, binding: DirectiveBinding) {
        if (typeof binding.value !== "function") {
            throw "callback must be a function";
        }
        let timer: NodeJS.Timeout | null = null;
        el.__handleClick__ = function () {
            if (timer) {
                clearTimeout(timer);
            }
            if (!el.disabled) {
                el.disabled = true;
                binding.value();
                timer = setTimeout(() => {
                        el.disabled = false;
                }, 1000);
            }
        };
        el.addEventListener("click", el.__handleClick__);
    },
    beforeUnmount(el: ElType) {
        el.removeEventListener("click", el.__handleClick__);
    }
};

export default throttle;

使用:给 Dom 加上 v-throttle 及回调函数即可。

<template>
  <div class="content-box">
    <span class="text">节流指令 🍇🍇🍇🍓🍓🍓</span>
    <el-button type="primary" v-throttle="throttleClick">节流按钮 (每隔1S秒后执行)</el-button>
  </div>
</template>

<script setup lang="ts" name="throttleDirect">
import { ElMessage } from 'element-plus'
const throttleClick = () => {
  ElMessage.success('我是节流按钮触发的事件 🍍🍓🍌')
}
</script>

v-waterMarker水印指令

功能:给页面添加背景水印。

  1. 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
  2. 将其设置为背景图片,从而实现页面或组件水印效果
import type { Directive, DirectiveBinding } from "vue";
const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => {
    // 水印文字,父元素,字体,文字颜色
    let can: HTMLCanvasElement = document.createElement("canvas");
    parentNode.appendChild(can);
    can.width = 200;
    can.height = 150;
    can.style.display = "none";
    let 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" as CanvasTextBaseline;
    cans.fillText(str, can.width / 10, can.height / 2);
    parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
};

const waterMarker = {
    mounted(el: DirectiveBinding, binding: DirectiveBinding) {
        addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor);
    }
};

export default waterMarker;

使用:设置水印文案,颜色,字体大小即可。

<template>
  <div
    class="content-box"
    v-waterMarker="{
      text: 'Watermark Direct',
      textColor: 'rgba(180, 180, 180, 0.6)'
    }"
  >
    <span class="text">水印指令</span>
  </div>
</template>

<script setup lang="ts" name="watermarkDirect"></script>

<style scoped lang="scss">
.content-box {
  width: 100%;
  height: 600px;
}
</style>

通过这些常用的指令结合需求可以独立封装符合自己功能的自定义指令,所有代码都不是万能,唯有结合实际才能解决万万不能的需求,希望此文章对浏览者有所启发!!!

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值