Vue3 在线 PDF 编辑 1.0 画线批注

家人们!上回带大家看了 PDF 预览怎么玩,这回直接放大招 ------ 拆解 基于 Vue3 的在线 PDF 编辑 1.0 项目诞生记 里超实用的 画线批注 功能!话不多说,快跟上,一起钻进代码世界 "挖宝"!

初始化 fabric-Canvas:从 "硬刚" 到 "躺平" 的技术自救

一开始咱也是 "技术猛男",想着手写 Canvas 挑战批注功能,结果写着写着发现 ------ 这简直是在给自己挖坑!各种功能需求 "狂轰滥炸",写到怀疑人生。果断 "认怂",搬出老伙计 fabric 救场!虽然它平时都在搞图片编辑,但这次跨界到 PDF 批注,居然完美适配,只能说 "专业的事还得专业的工具来干"!

要实现画线批注,先得在页面上 "召唤" 两个 canvas 兄弟:

ini 复制代码
<canvas
    :data-index="index"
    class="pdf-box"
    :ref="(el:any) => (canvasRefs['canvas' + index] = el)"
></canvas>
<canvas
    class="annotation-canvas"
    :id="`annotation-canvas_${index}`"
></canvas>

第二个 canvas 记得给它加上 z-index: 2 的 "buff",不然在同一画布上操作,容易把 PDF 内容 "误伤"。当然,你也可以把 PDF 转成图片当背景,这样画布刷新时内容就不会 "人间蒸发",怎么玩就看各位的脑洞啦!

在渲染 PDF 时,咱得让第二个 canvas 和第一个 canvas "保持同款身材",再用 new fabric.Canvas 初始化,最后把鼠标事件绑定好,"万事俱备,只欠东风":

ini 复制代码
if (!pdfUrl.value) return;
const loadingTask = pdfjsLib.getDocument(pdfUrl.value);
const pdf = await loadingTask.promise;
// 省略部分代码...
for (let i = 1; i <= pagesCount.value; i++) {
    const page = await pdf.getPage(i);
    const viewport = page.getViewport({ scale });
    const canvas = canvasRefs["canvas" + (i - 1)];
    if (!canvas) break;
    const context = canvas.getContext("2d");
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    const fabricCanvas = new fabric.Canvas(`annotation-canvas_${i - 1}`, {
        width: viewport.width,
        height: viewport.height,
        isDrawingMode: false,
    });
    fabricCanvas.selectionColor = 'transparent';
    fabricCanvas.selectionBorderColor = 'transparent';
    fabricCanvas.on('mouse:down', startLine.bind(fabricCanvas, {
        page: i - 1,
        canvas: fabricCanvas,
    }));
    fabricCanvas.on('mouse:move', drawLine.bind(fabricCanvas, {
        page: i - 1,
        canvas: fabricCanvas,
    }));
    fabricCanvas.on('mouse:up', stopDrwa.bind(fabricCanvas, {
        page: i - 1,
        canvas: fabricCanvas,
    }));
    fabricCanvasObj.value[`annotation-canvas_${i - 1}`] = fabricCanvas;
    const wrapper = canvas.parentElement;
    wrapper.style.width = `${viewport.width}px`;
    wrapper.style.height = `${viewport.height}px`;
    const renderContext = {
        canvasContext: context,
        viewport: viewport,
    };
    await page.render(renderContext).promise;
}

我这里用瀑布流展示 PDF,看着超带感!不过要是追求性能,换成翻页模式也是个 "真香" 选择!

fabric 画线批注:代码里的 "魔法画笔"

绑定好事件后,fabric 就要开始 "表演" 了!三个核心函数,让你的鼠标化身 "神笔马良":

  1. 鼠标按下:记录起点,创建线条
ini 复制代码
const startLine = async (event: { page: string, canvas: any }, e: any) => {
    if (!e || !event.canvas) return;
    const fabricCanvas = event.canvas;
    const pointer = fabricCanvas.getPointer(e.e);
    if (e.target) {
        fabricCanvas.setActiveObject(e.target);
        fabricCanvas.renderAll();
    } else if (drawConfig.value.type === "draw") {
        const currentObjet = new fabric.Line([pointer.x, pointer.y, pointer.x, pointer.y], {
            stroke: drawConfig.value.lineColor,
            strokeWidth: drawConfig.value.lineWidth,
            selectable: true
        });
        fabricCanvas.add(currentObjet);
    }
};
  1. 鼠标移动:实时更新线条,"指哪画哪"
ini 复制代码
const drawLine = (event: { page: string, canvas: any }, e: any) => {
    if (!e) return;
    const fabricCanvas = event.canvas;
    const pointer = fabricCanvas.getPointer(e.e);
    if (drawConfig.value.type === 'draw') {
        const currentObjet = fabricCanvas.getActiveObject();
        if (currentObjet) {
            currentObjet.set({ x2: pointer.x, y2: pointer.y });
            fabricCanvas.requestRenderAll();
        }
    }
};
  1. 鼠标松开:"收笔" 完成,完美 ending
ini 复制代码
const stopDrwa = (event: { page: string, canvas: any }, e: any) => {
    const fabricCanvas = event.canvas;
    fabricCanvas.discardActiveObject();
    fabricCanvas.renderAll();
};

有了这一套 "组合拳",画线批注直接拿捏!

以上就是画线批注功能的核心代码解析!后续还有更多宝藏功能等着解锁,想亲自上手的小伙伴,速去 项目仓库 拉取源码!也欢迎各位大佬、小伙伴来唠嗑,咱们一起把这个项目卷成 "六边形战士"!

相关推荐
LRH2 分钟前
JS基础 - instanceof 理解及手写
前端·javascript
leefirm2 分钟前
node 切换版本,每次打开都是切换前的版本怎么办?Node.js 版本管理神器 NVM 完全使用指南
前端
kangyouwei4 分钟前
鸿蒙开发:18-hilogtool命令的使用
前端·harmonyos
小小神仙4 分钟前
JSCommon系列 - 为什么前端没有 Apache Commons?
前端·javascript·设计模式
WildBlue5 分钟前
🚀 React组件化实战:用TodoList项目搭乐高式开发!🎉
前端·react.js
Nano6 分钟前
ES6中的Proxy和Reflect:深入解析与Vue3响应式原理的完美结合
前端·vue.js
Nano6 分钟前
TypeScript 基础入门指南:从 JavaScript 进阶到类型安全开发
前端
Sun_light6 分钟前
深入理解 JavaScript 对象:从入门到精通
前端·javascript
中微子6 分钟前
从零构建电影展示页面:原生js Web开发技术解析
前端·javascript
Mintopia12 分钟前
计算机图形学中的几何体布尔运算:一场形状的奇幻冒险
前端·javascript·计算机图形学