各位开发者朋友们!经过两天的技术攻坚,Vue3 在线 PDF 编辑项目的缩放、拖拽功能终于落地,同时针对 PDF 渲染性能做了深度优化。今天带大家直击核心代码,看看如何让 PDF 编辑体验更上一层楼!
一、PDF 渲染优化:离屏 Canvas 重构渲染逻辑
本次优化彻底重构了 PDF 渲染层,通过 离屏 Canvas(Offscreen Canvas) 将 PDF 内容渲染为背景图片,解决了多 Canvas 同步问题,大幅提升缩放 / 拖拽时的性能表现。
核心原理:单 Canvas 集成渲染
原方案采用双 Canvas 分离渲染(PDF 层 + 批注层),缩放时需同步处理两层的尺寸和位置,不仅逻辑复杂,还容易因重绘不同步导致画面撕裂。优化后采用单 Canvas 架构,将 PDF 内容通过离屏 Canvas 渲染为背景图,批注层直接绘制在同一 Canvas 上,实现了背景与批注的无缝集成。
核心优化点对比
优化前 | 优化后 |
---|---|
双 Canvas 分离渲染(PDF 层 + 批注层) | 单 Canvas 集成(PDF 背景 + 批注层) |
缩放时需同步双 Canvas 尺寸 | 背景图随 Canvas 自动适配缩放比例 |
内存占用较高,渲染延迟明显 | 离屏渲染减少重绘压力,流畅度提升 |
关键代码实现
arduino
// 创建离屏Canvas并渲染PDF内容
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
offscreenCanvas.width = viewport.width; // PDF页面原始宽度
offscreenCanvas.height = viewport.height; // PDF页面原始高度
// 调用PDF.js渲染页面到离屏Canvas
await page.render({
canvasContext: offscreenCtx,
viewport: { ...viewport, scale: 1 } // 初始缩放比例1:1渲染
}).promise;
// 将离屏Canvas转换为fabric背景图
const bgImage = new fabric.Image(offscreenCanvas, {
left: 0,
top: 0,
originX: 'left',
originY: 'top'
});
// 设置背景图并自动适配当前Canvas尺寸
fabricCanvas.setBackgroundImage(bgImage, () => {
fabricCanvas.renderAll(); // 确保背景图加载完成后再渲染批注层
}, {
scaleX: fabricCanvas.width / viewport.width, // 水平缩放比例(当前Canvas宽度/原始宽度)
scaleY: fabricCanvas.height / viewport.height, // 垂直缩放比例
originX: 'left',
originY: 'top'
});
二、PDF 缩放功能:基于 fabric 的精准缩放控制
借助 fabric-Canvas 原生缩放 API,实现滚轮缩放 + 焦点定位 + 缩放边界限制的完整交互逻辑,用户体验更接近专业 PDF 阅读器。
核心功能点
- 缩放区间限制:通过minZoom和maxZoom参数限制缩放范围(1-5 倍),避免因过度缩放导致内容模糊或性能下降。
- 焦点定位缩放:以鼠标当前位置为中心缩放,避免缩放时内容偏移,实现 "所见即所得" 的操作体验。
- 浏览器事件屏蔽:阻止滚轮事件冒泡至浏览器,避免与页面滚动冲突,确保缩放操作仅作用于 PDF 区域。
代码实现细节
ini
const scaleCanvas = (event: { page: number; canvas: fabric.Canvas }, opt: WheelEvent) => {
// 非手势模式或无有效Canvas时直接返回
if (drawConfig.value.type !== 'gesture' || !event.canvas) return;
const MIN_ZOOM = 0.5; // 最小缩放比例(支持缩小至50%)
const MAX_ZOOM = 5; // 最大缩放比例(放大至5倍)
const ZOOM_STEP = 0.1; // 每次缩放步进值(可根据需求调整)
// 计算缩放方向(滚轮向上为缩小,向下为放大)
const delta = opt.deltaY > 0 ? ZOOM_STEP : -ZOOM_STEP;
let currentZoom = event.canvas.getZoom();
currentZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, currentZoom + delta)); // 限制缩放范围
// 获取鼠标在Canvas上的相对坐标(以Canvas左上角为原点)
const pointer = event.canvas.getPointer(opt);
if (!pointer) return;
// 执行缩放并以当前指针位置为焦点
event.canvas.zoomToPoint({ x: pointer.x, y: pointer.y }, currentZoom);
// 阻止浏览器默认行为(如页面滚动)
opt.preventDefault();
opt.stopPropagation();
// 触发Canvas重绘(确保缩放立即生效)
event.canvas.requestRenderAll();
};
三、PDF 拖拽功能:基于视图变换的平滑平移
通过监听鼠标按下、移动、抬起事件,实时修改 Canvas 的视图变换矩阵(viewportTransform),实现任意方向平滑拖拽,解决了传统定位偏移问题。
交互逻辑拆解
- 鼠标按下( mousedown ) :记录鼠标初始位置,开启拖拽状态。
- 鼠标移动( mousemove ) :计算位移差值,更新视图变换矩阵,实时平移画布。
- 鼠标抬起( mouseup ) :清除拖拽状态,避免误操作。
关键代码实现
ini
let isDragging = false; // 拖拽状态标记
let dragStartX = 0; // 拖拽起点X坐标
let dragStartY = 0; // 拖拽起点Y坐标
let initialVpt = [1, 0, 0, 1, 0, 0]; // 初始视图变换矩阵
// 初始化拖拽事件监听
const initDrag = (canvas: fabric.Canvas) => {
canvas.on('mousedown', (e) => {
if (e.target) return; // 点击到批注对象时不触发拖拽
isDragging = true;
dragStartX = e.e.clientX;
dragStartY = e.e.clientY;
initialVpt = canvas.viewportTransform.slice(); // 保存初始变换矩阵
});
canvas.on('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.e.clientX - dragStartX;
const deltaY = e.e.clientY - dragStartY;
// 更新变换矩阵(平移部分)
initialVpt[4] += deltaX; // x = vpt[4] + deltaX
initialVpt[5] += deltaY; // y = vpt[5] + deltaY
canvas.viewportTransform = initialVpt; // 应用新的变换矩阵
canvas.requestRenderAll(); // 重绘画布
});
canvas.on('mouseup', () => {
isDragging = false;
});
};
// 在PDF渲染完成后初始化拖拽
for (const canvas of Object.values(fabricCanvasObj)) {
initDrag(canvas); // 为每个页面的Canvas绑定拖拽事件
}
四、一键复位功能:快速恢复初始视图
新增一键复位按钮,通过重置视图变换矩阵和缩放比例,解决用户因频繁缩放 / 拖拽导致的视图混乱问题,操作体验更友好。
实现原理
- 重置变换矩阵:将viewportTransform恢复为[1, 0, 0, 1, 0, 0],即无平移、无旋转、无缩放的初始状态。
- 恢复默认缩放:调用setZoom(1)将缩放比例强制设置为 100%。
- 全页面重绘:通过requestRenderAll()确保所有页面同步复位。
代码实现
ini
const resetAllCanvases = () => {
const INITIAL_ZOOM = 1;
const INITIAL_VPT = [1, 0, 0, 1, 0, 0]; // 初始变换矩阵(单位矩阵)
Object.values(fabricCanvasObj).forEach((canvas) => {
canvas.setZoom(INITIAL_ZOOM); // 恢复默认缩放
canvas.viewportTransform = INITIAL_VPT.slice(); // 重置变换矩阵
canvas.clear(); // 清空临时绘制状态(如有)
canvas.requestRenderAll(); // 触发全量重绘
});
};
五、效果图展示

总结
本次更新围绕性能优化 和交互体验 展开,通过离屏 Canvas 重构渲染逻辑,结合 fabric-Canvas 的原生能力,实现了流畅的缩放、拖拽及复位功能。本次功能优化涉及范围较广,更改内容较大,已经拉取代码的小伙伴,建议再拉取一份新代码,没有拉取代码的小伙伴,欢迎前往 项目仓库 或者 gitee 仓库拉取代码, 体验完整功能。同时,也期待大家在评论区分享对功能扩展的想法,你的建议很可能会成为下一个版本的核心特性! 🚀