前端实现svg图片居中放大缩小与拖动,使用viewbox属性(vue3+typescript+vue自定义命令)

效果

请添加图片描述

demo

  1. 基于svg viewbox属性居中进行放大缩小
  2. 基于svg viewbox属性进行平滑拖动

demo预览地址

https://zqy233.github.io/svg-zoom-drag-vue-demo/#/

demo完整代码地址

https://github.com/zqy233/svg-zoom-drag-vue-demo

定义自定义命令v-svgWheelv-svgDrag

import { Vue2, App } from "vue-demi";

/** create a `v-svgWheel` directive for a Vue app, allows the user to control the zoom in and out of an SVG image using the mouse wheel. */
export function svgWheel(app: typeof Vue2): void;
/** create a `v-svgWheel` directive for a Vue app, allows the user to control the zoom in and out of an SVG image using the mouse wheel. */
export function svgWheel(app: App<Element>): void;
// Control the zoom in and out of an SVG image by setting the viewbox attribute of the SVG element. The third parameter of the viewbox controls the horizontal size, while the fourth parameter sets the vertical size.
export function svgWheel(app: typeof Vue2 | App<Element>) {
  app.directive("svgWheel", (el: HTMLElement) => {
    if (el) {
      el.onwheel = (e: WheelEvent) => {
        // Because the SVG is rendered using v-html, the child element of the bound DOM is the SVG element.
        const svgDom = el.firstChild as SVGSVGElement;
        const viewBox = svgDom.getAttribute("viewBox") as string;
        const [x, y, width, height] = viewBox.split(/\s+/).map(parseFloat);
        // event.wheelDelta has been deprecated, use event.deltaY instead.
        // event.deltaY returns a positive value when scrolling downwards and a negative value when scrolling upwards. Otherwise, it is 0. event.wheelDelta is the opposite.
        const scaleDelta = e.deltaY > 0 ? 0.9 : 1.1; // Zoom scale.
        const newWidth = width * scaleDelta;
        const newHeight = height * scaleDelta;
        // Calculate the centering offset.
        const dx = (width - newWidth) / 2;
        const dy = (height - newHeight) / 2;
        const newViewBox = `${x + dx} ${y + dy} ${newWidth} ${newHeight}`;
        svgDom.setAttribute("viewBox", newViewBox);
      };
    }
  });
}

/** create a `v-svgDrag` directive for a Vue app, allows the user to drag the SVG image by holding down the mouse and moving the cursor.
 */
export function svgDrag(app: typeof Vue2): void;
/** create a `v-svgDrag` directive for a Vue app, allows the user to drag the SVG image by holding down the mouse and moving the cursor.
 */
export function svgDrag(app: App<Element>): void;
// Control the drag of an SVG image by setting the "viewBox" attribute of the SVG element. The first parameter of "viewBox" controls the left-right position, and the second parameter sets the up-down position.
export function svgDrag(app: typeof Vue2 | App<Element>) {
  app.directive("svgDrag", (el: HTMLElement) => {
    let clientX = 0; // The last x-axis position of the mouse
    let clientY = 0; // The last y-axis position of the mouse
    let debounce = true; // Throttling is necessary, otherwise the dragging effect will appear jerky
    let isStartMoveSvg = false; // Whether to start dragging
    let ratio = 1; // The ratio of drag speed to size
    let sgvDom: SVGAElement; // The SVG element
    let viewBox: string; // The "viewBox" attribute of the SVG element
    let arrPoint: number[]; // The value of the "viewBox" attribute of the SVG element
    // Mouse down means start moving
    el.onmousedown = () => {
      isStartMoveSvg = true;
      const width = el.getBoundingClientRect().width;
      // Because the SVG is rendered using "v-html", the child element of the bound command DOM is the SVG element
      sgvDom = el.firstChild as SVGAElement;
      viewBox = sgvDom.getAttribute("viewBox") as string;
      arrPoint = viewBox.split(/\s+/).map(parseFloat);
      // Dynamically adjust the drag speed based on the size, otherwise it becomes harder to drag as the SVG becomes smaller
      ratio = arrPoint[2] / width;
      if (ratio < 1) ratio = 1;
    };
    // Mouse up means end moving
    el.onmouseup = () => {
      isStartMoveSvg = false;
      clientX = 0;
      clientY = 0;
    };
    // Dynamically set "viewBox" while moving
    el.onmousemove = (e: MouseEvent) => {
      if (debounce) {
        debounce = false;
        if (isStartMoveSvg) {
          if (clientX !== 0 && clientY !== 0) {
            arrPoint[0] = arrPoint[0] - (e.clientX - clientX) * ratio;
            arrPoint[1] = arrPoint[1] - (e.clientY - clientY) * ratio;
            sgvDom.setAttribute("viewBox", arrPoint.join(" "));
          }
          clientX = e.clientX;
          clientY = e.clientY;
        }
        setTimeout(() => {
          debounce = true;
        }, 50);
      }
    };
  });
}

上方两个自定义指令已经发布为npm包,可以直接安装使用

npm i -s svg-zoom-drag-vue-directives

Vue2

import Vue from "vue";
import App from "./App.vue";

import { svgWheel, svgDrag } from "svg-zoom-drag-vue-directives";
svgWheel(Vue);
svgDrag(Vue);

new Vue({
  render: (h) => h(App),
}).$mount("#app");

Vue3

import { createApp } from "vue";
import App from "./App.vue";

import { svgWheel, svgDrag } from "svg-zoom-drag-vue-directives";
const app = createApp(App);
svgWheel(app);
svgDrag(app);

app.mount("#app");

绑定两个命令到dom上

<template>
  <div id="svg" v-html="svgString" v-svgWheel v-svgDrag></div>
</template>
<script setup lang="ts">
import { svgText } from "./svgText";
const svgString = ref("");
onMounted(() => {
  svgString.value = svgText;
});
</script>
<style>
* {
  margin: 0;
  padding: 0;
}
#svg {
  width: 100%;
  height: 100vh;
  overflow: hidden;
}
svg {
  width: 100% !important;
  height: 100% !important;
  user-select: none;
}
</style>

注意点

指令将绑定元素的第一个子元素当做 svg,所以请注意绑定的元素

const svgDom = el.firstChild as SVGSVGElement;

这样设计是为了搭配v-html

<div id="svg" v-html="svgString" v-svgWheel v-svgDrag></div>
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值