手把手实现一个图片可缩放可拖拽的 Vue3 组件

在工作中经常遇到需要预览一张尺寸可能非常大的图片,初始化显示的时候,希望它自适应显示区域后,还可以缩放并可以在显示区域中拖拽。

在这里,手把手展示一下如何实现一个简单的组件,以实现上述的需求。

效果展示

先看看效果

在这里插入图片描述

可以直达👇

示例仓库 | 示例文档 | 在线示例

别忘了,可以带话,给我一个 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: () => {
   
      
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值