Vue3实现一个类似于PDF的批注控件

2,503 阅读1分钟

前言

接到公司一个内部需求,实现在网页任意位置添加一个标注框,用户可以自由输入文字,自由拖拽文本框,类似于PDF的标注功能。需求很简单,主要是通过这个需求掌握VUE3创建动态节点、Vue3的自定义指令,也可以扩展实现自己的对话框

创建动态节点

// createNode.ts
import { createApp, Component, onUnmounted } from 'vue'

function create(selector: string ,component: Component, props?: Object) {
  var divEle: HTMLElement,
    ele: HTMLElement | null;
  const app = createApp(component, { ...props })
  divEle = document.createElement("div")  
  ele = document.querySelector(selector)
  ele?.appendChild(divEle)

  app.mount(divEle)

  onUnmounted(()=>{
    app.unmount()
    ele?.removeChild(divEle)
  })
}

export default create

标注控件(简单对话框)

// postilModal.uve
<template>
  <div class="postil" v-drag>
    <textarea placeholder="请添加批注" />
  </div>
</template>

<script lang="ts" setup>
const vDrag = {
  mounted(el: HTMLDivElement) {
    el.onmousedown = (ev) => {
      // ev.stopPropagation()
      // 鼠标按下的位置
      const mouseXStart = ev.clientX;
      const mouseYStart = ev.clientY;
      // 当前滑块位置
      const rectLeft = el.offsetLeft;
      const rectTop = el.offsetTop;
      el.onmousemove = (e) => {
        // 鼠标移动的位置
        // e.stopPropagation()
        const mouseXEnd = e.clientX;
        const mouseYEnd = e.clientY;
        const moveX = mouseXEnd - mouseXStart + rectLeft;
        const moveY = mouseYEnd - mouseYStart + rectTop;
        el.style["top"] = moveY + "px";
        el.style["left"] = moveX + "px";
      };
      el.onmouseup = (e) => {
        // e.stopPropagation()
        // 取消事件
        el.onmousemove = null;
      };
    };
  }
}
</script>

<style lang="scss" scoped>
.postil {
  position: absolute;
  top: 0;
  left: 0;
  textarea {
    background-color: #D9EDFF;
    border: 1px solid #40A9FF;
    min-width: 160px;
    width: auto;
    padding: 8px 24px;
    box-shadow: 0px 0px 0px 2px rgba(24, 144, 255, 0.2);
    border-radius: 0px 20px 20px 20px;
    border: none;
    outline: none;
    resize: none;
  }
}
</style>

使用

// index.vue
import postilModal from './components/postilModal.vue';
import create from '@/utils/createNode'

<template>
    <a-button @click="createPostil">添加批注</a-button>
    
    <!-- 标注的位置 -->
    <div id="preview-pdf">
      ...
    </div>
</template>

<script setup lang="ts">
 createPostil = () => {
    create('#preview-pdf', postilModal)
  }
<script>