在工作中经常遇到需要预览一张尺寸可能非常大的图片,初始化显示的时候,希望它自适应显示区域后,还可以缩放并可以在显示区域中拖拽。
在这里,手把手展示一下如何实现一个简单的组件,以实现上述的需求。
效果展示
先看看效果
可以直达👇
别忘了,可以带话,给我一个 Star 哟!
实现 Hook
在实现组件之前,可以先实现一个 hook,以包含核心逻辑,后面实现 组件 和 指令 的时候需要依赖它。
使用方式
<template>
<div ref="board" class="demo">
<div>Somethings</div>
</div>
</template>
// 页面
import {
ref } from 'vue'
import {
useZoomDrag } from 'vue3-zoom-drag'
const board = ref<HTMLElement>()
useZoomDrag({
board,
zoomMin: 0.1,
})
这里的 hook 就是 useZoomDrag
配置项
先设计一下,接下来要实现那些功能配置
export type useZoomDragOptions = {
/**
* 容器区域
*/
board: Ref<HTMLElement | undefined> | ComputedRef<HTMLElement | undefined>
/**
* 目标区域
*/
target?: Ref<HTMLElement | undefined> | ComputedRef<HTMLElement | undefined>
/**
* 目标变化事件
*/
onTargetChange?: (info: ZoomDragSize & {
zoom: number }, methods: ZoomDragMethods) => void
/**
* 容器大小变化事件
*/
onBoardChange?: (info: ZoomDragSize, methods: ZoomDragMethods) => void
/**
* 初始化完成事件
*/
onReady?: () => void
/**
* 放大缩小速率(默认 0.1)
*/
zoomSpeed?: number
/**
* 最高放大倍速(默认 3)
*/
zoomMax?: number
/**
* 最低缩小倍速(默认 0.2)
*/
zoomMin?: number
/**
* 内边距
*/
padding?: [number, number, number, number]
}
- board - 作为可视区域的 HTML 节点
- target - 可以缩放、拖拽的目标 HTML 节点
- zoomSpeed - 缩放速度可配置,即 dom 每次触发 wheel 事件时,增加/减少多少缩放比例,例如,0.1、0.3…
假如当前目标缩放比例为 1.2,缩放速度为 0.2,则 wheel 放大一次后,最新缩放比例就是 1.2 + 0.2 = 1.4;反之缩小一次,就是 1.2 - 0.2 = 1。
- zoomMax - 最大缩放比例可配置,例如,2倍、3倍…
- zoomMin - 最小缩放比例可配置,例如,0.5倍、0.2倍…
- onReady - 初始化完成事件,默认会执行一次“自适应显示区域”操作(也就是后面会提到的 fitSize 方法)。
值得一提的是,如果目标本身并非 img 节点,但是内部却包含 img 节点,此时,需要在 img 节点的 onload 事件中手动执行一次 fitSize 方法。
原因就是目标节点(例如一个 div),它的大小取决于一个需要异步加载的节点(例如 img),初始化的时候执行的 fitSize,无法获得真实的大小尺寸。 - onTargetChange - 目标变化事件,会返回关于目标的大小信息和缩放比例,并暴露一个 fitSize 方法。
- onBoardChange - 容器大小变化事件,会返回关于显示区域的大小信息(例如浏览器窗口大小发生了改变,导致显示区域发生了改变),并暴露一个 fitSize 方法。
PS: 关于大小信息的定义:
export interface ZoomDragSize {
width: number
height: number
left: number
top: number
}
- padding - 内边距,主要是为了预留一些空间,可以放置一些自定义的操作按钮。
假如设置为
padding: [0, 100, 0, 0]
代码实现
基础结构
先忽略具体细节,看看主要这个 hook 的代码结构:
import {
ref, type Ref, reactive, watch } from 'vue'
// 类型定义
import type {
useZoomDragOptions, ZoomDragMethods, ZoomDragSize } from '../types'
// 配置的默认值
const DefaultOptions: Partial<useZoomDragOptions> = {
zoomSpeed: 0.1,
zoomMax: 10,
zoomMin: 0.2,
padding: [0, 0, 0, 0],
}
// hook 主体
export default function useZoomDrag(opts: useZoomDragOptions): {
// hook 返回的内容
// 目标、显示区域的相关信息是 Ref 类型的
target: Ref<ZoomDragSize & {
zoom: number }>
board: Ref<ZoomDragSize>
// 暴露一些方法,目前只有 fitSize(自适应显示区域)
methods: ZoomDragMethods
} {
// 传入配置的默认值合并
const options = {
...DefaultOptions, ...opts }
// 略
// 返回的内容(目标、显示区域的相关信息)
const targetInfoRef: Ref<ZoomDragSize & {
zoom: number }> = ref({
width: 0,
height: 0,
left: 0,
top: 0,
zoom: 1,
})
const boardInfoRef: Ref<ZoomDragSize> = ref({
width: 0,
height: 0,
left: 0,
top: 0,
})
// 获得目标节点(如果没有通过配置传入,则获取显示区域的第一个子节点)
function getTarget() {
// 略
}
// 略
// 自适应大小(需要暴露的方法)
// animate 表示缩放时是否显示缩放动画
async function fitSize(animate = false) {
// 略
}
// 略
// 涉及到的 Dom 事件处理
const eventHandlers = {
// 缩放
zoom: (e: WheelEvent) => {
// 略
},
// 排除右键菜单的影响
contextmenu: (e: MouseEvent) => {
e.preventDefault()
},
// 拖拽相关逻辑
dragStart: (e: MouseEvent) => {
// 略
},
dragMove: (e: MouseEvent) => {
// 略
},
dragEnd: () => {