Vue3图片缩放拖拽组件全攻略
📅 2026/7/30 19:22:23
👁️ 阅读次数
📝 编程学习
Panzoom 图片缩放拖拽组件使用文档
基于Vue 3 + @panzoom/panzoom的图片缩放拖拽组件,支持滚轮缩放和鼠标拖拽,大图自动缩小完整显示,小图 1:1 居中。
目录
- 依赖安装
- Props 说明
- 完整代码
- 使用步骤
- 功能说明
1. 依赖安装
npminstall@panzoom/panzoom2. Props 说明
| Prop | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
imageSrc | String | 否 | "" | 图片地址(支持远程 URL 或URL.createObjectURL的 Blob URL) |
3. 完整代码
ProcessDialog.vue
<template> <!-- 固定高度容器,作为 panzoom 的视口 --> <div ref="containerRef" class="panzoom-viewport"> <!-- panzoom 控制的目标元素:transform 在这里生效 --> <div ref="panzoomRef" class="panzoom-layer"> <img v-if="imageSrc" :src="imageSrc" alt="图片" class="draggable-image" @load="onImageLoad" /> <el-empty v-else description="暂无图片" /> </div> </div> </template> <script setup> import { ref, watch, nextTick, onBeforeUnmount } from "vue" import Panzoom from "@panzoom/panzoom" /** * Props * - imageSrc: 图片地址(ObjectURL 或远程 URL) */ const props = defineProps({ imageSrc: { type: String, default: "" } }) // ==================== DOM 引用 ==================== const containerRef = ref(null) // 固定高度视口容器 const panzoomRef = ref(null) // panzoom 控制的内容层 let panzoomInstance = null // ==================== Panzoom 实例创建 ==================== /** 创建并配置 panzoom 实例 * @param {HTMLElement} element - 被控制的内容层 DOM * @returns {Panzoom} panzoom 实例 */ function createPanzoom(element) { return Panzoom(element, { maxScale: 5, // 最大放大 5 倍 minScale: 0.05, // 最小缩小到 5% step: 0.15, // 滚轮每格缩放步长 contain: false, // 不限制内容边界,允许自由拖拽 /* 鼠标/触摸事件处理:避免拖拽时误选文本 */ handleStartEvent: (e) => { // 多指触摸 → 允许缩放 if (e.touches && e.touches.length > 1) return true // 非左键 / 非 touch / 非滚轮 → 不拦截 if (e.button !== 0 && e.type !== "touchstart" && e.type !== "wheel") return true e.preventDefault() e.stopPropagation() } }) } // ==================== 自适应缩放 ==================== /** * 内容自适应容器:大图缩小、小图居中,保持完整显示 * 计算公式: * scale = min(容器宽 / 内容宽, 容器高 / 内容高, 1) * panX = (容器宽 - 内容宽 × scale) / 2 * panY = (容器高 - 内容高 × scale) / 2 * * @param {Panzoom} instance - panzoom 实例 * @param {HTMLElement} layerEl - 内容层 DOM */ function autoFit(instance, layerEl) { if (!instance || !layerEl || !containerRef.value) return const pw = containerRef.value.clientWidth const ph = containerRef.value.clientHeight const cw = layerEl.scrollWidth const ch = layerEl.scrollHeight if (cw <= 0 || ch <= 0) return // 刚好完整显示的比例,最大不超过 1(小图不放大) const scale = Math.min(pw / cw, ph / ch, 1) // 先归零 → 设置缩放 → 居中(避免位移累积误差) instance.pan({ x: 0, y: 0 }) instance.setScale(scale) instance.pan({ x: (pw - cw * scale) / 2, y: (ph - ch * scale) / 2 }) } // ==================== 图片加载回调 ==================== /** 图片加载完成后初始化 panzoom + 自适应居中 */ function onImageLoad() { if (!panzoomRef.value || !containerRef.value) return // 销毁旧实例,避免重复创建 if (panzoomInstance) { panzoomInstance.destroy() panzoomInstance = null } panzoomInstance = createPanzoom(panzoomRef.value) // 滚轮事件绑定到外层容器,确保鼠标在容器内任意位置都能缩放 containerRef.value.addEventListener("wheel", panzoomInstance.zoomWithWheel, { passive: false }) // 等浏览器完成布局后自适应 nextTick(() => { requestAnimationFrame(() => autoFit(panzoomInstance, panzoomRef.value)) }) } // ==================== 监听 imageSrc 变化 ==================== watch( () => props.imageSrc, (src) => { if (src) { nextTick(() => { // 如果图片已缓存(loaded),直接初始化 if (panzoomRef.value?.querySelector("img")?.complete) { onImageLoad() } }) } } ) // ==================== 组件销毁时清理 ==================== onBeforeUnmount(() => { if (containerRef.value && panzoomInstance) { containerRef.value.removeEventListener("wheel", panzoomInstance.zoomWithWheel) } if (panzoomInstance) { panzoomInstance.destroy() panzoomInstance = null } }) </script> <style scoped lang="scss"> /* ======== 视口容器:固定高度,裁剪溢出 ======== */ .panzoom-viewport { width: 100%; height: 65vh; overflow: hidden; position: relative; background: #f5f6f8; border: 1px solid #e8eaed; border-radius: 4px; user-select: none; /* 拖拽时不选中文本 */ } /* ======== panzoom 控制的内容层:transform 在这里生效 ======== */ .panzoom-layer { display: inline-block; transform-origin: 0 0; /* 以左上角为缩放原点,配合 autoFit 的 pan 居中 */ } /* ======== 图片样式 ======== */ .draggable-image { display: block; max-width: none; /* 允许图片超出容器,由 panzoom 控制缩放 */ } </style>4. 使用步骤
第 1 步:安装依赖
npminstall@panzoom/panzoom第 2 步:在父组件中使用
<template> <div style="padding: 20px;"> <ProcessDialog :image-src="imageUrl" /> </div> </template> <script setup> import { ref } from "vue" import ProcessDialog from "@/views/mytodolist/components/dialogs/ProcessDialog.vue" // 可以是远程 URL const imageUrl = ref("https://example.com/flow-diagram.png") // 或者通过后端 Blob 接口获取 import { diagram } from "@/api/mytodolist/index" async function loadImage(processInstanceId) { const res = await diagram({ processInstanceId }) imageUrl.value = URL.createObjectURL(res) // Blob → ObjectURL } </script>5. 功能说明
5.1 滚轮缩放
- 鼠标悬停在图片上,滚动滚轮即可缩放
- 以鼠标位置为中心点缩放,缩放步长 15%
- 最大放大 5 倍,最小缩小到 5%
5.2 鼠标拖拽
- 按住鼠标左键拖动图片
- 可拖出容器边界(
contain: false) - 支持触摸屏手势拖拽
5.3 自适应显示
| 图片大小 | 效果 |
|---|---|
| 图片 > 容器 | 自动缩小到完整显示,居中 |
| 图片 < 容器 | 保持 1:1 原始大小,居中不放大 |
5.4 生命周期
- 图片加载完成后自动初始化 panzoom + 自适应居中
imageSrc变化时重新初始化- 组件销毁时自动清理 panzoom 实例和事件监听
5.5 autoFit 缩放计算公式
scale = min(容器宽 / 图片宽, 容器高 / 图片高, 1) ↑ ↑ 取宽高比中较小的一个 不超过 1(不大) panX = (容器宽 - 图片宽 × scale) / 2 panY = (容器高 - 图片高 × scale) / 2
编程学习
技术分享
实战经验