Panzoom 图片缩放拖拽组件使用文档
基于 Vue 3 + @panzoom/panzoom 的图片缩放拖拽组件,支持滚轮缩放和鼠标拖拽,大图自动缩小完整显示,小图 1:1 居中。
目录
- 依赖安装
- [Props 说明](#Props 说明)
- 完整代码
- 使用步骤
- 功能说明
1. 依赖安装
npm install @panzoom/panzoom
2. 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 步:安装依赖
npm install @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