家人们!上回带大家看了 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 就要开始 "表演" 了!三个核心函数,让你的鼠标化身 "神笔马良":
- 鼠标按下:记录起点,创建线条
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);
}
};
- 鼠标移动:实时更新线条,"指哪画哪"
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();
}
}
};
- 鼠标松开:"收笔" 完成,完美 ending
ini
const stopDrwa = (event: { page: string, canvas: any }, e: any) => {
const fabricCanvas = event.canvas;
fabricCanvas.discardActiveObject();
fabricCanvas.renderAll();
};
有了这一套 "组合拳",画线批注直接拿捏!
以上就是画线批注功能的核心代码解析!后续还有更多宝藏功能等着解锁,想亲自上手的小伙伴,速去 项目仓库 拉取源码!也欢迎各位大佬、小伙伴来唠嗑,咱们一起把这个项目卷成 "六边形战士"!