写一个给组件添加拖拽缩放功能的组件

12/22/2022 vue.jsjs拖拽组件

# 一. 前言

之前有写过一个低代码工具, 即通过拖拽组件生成一个页面. 现在产品又多了一个需求, 就是在预览时, 用户可以拖拽缩放来查看这个预览页面

此时再去修改低代码的渲染区显示太费功夫, 因为我想到直接写一个给组件添加拖拽缩放功能的组件, 然后包裹预览组件.

# 二. 实现

# 1. 缩放

缩放功能很简单, 监听鼠标滚轮事件即可

<template>
  <div
    class="drag-wrap"
    @mousewheel="handleMousewheel"
    :style="{
      transform: `scale(${transform.scale})`,
    }"
  >
    <slot></slot>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        transform: {
          scale: 1,
        },
      };
    },
    methods: {
      handleMousewheel(ev) {
        const down = ev.wheelDelta ? ev.wheelDelta < 0 : ev.detail > 0;
        let scale;
        if (down) {
          // 滚轮向下
          scale = this.transform.scale - 0.1;
        } else {
          // 滚轮向上
          scale = this.transform.scale + 0.1;
        }
        if (scale > 0.5 && scale < 4) {
          this.transform.scale = scale;
        }
        if (ev.preventDefault) {
          ev.preventDefault();
        }
        return false;
      },
    },
  };
</script>

<style lang="less" scoped>
  .drag-wrap {
    height: 100%;
  }
</style>

# 2. 拖拽

拖拽功能分为 3 部分:

  • 按下鼠标, 开始准备拖拽

  • 拖动鼠标, 组件跟随移动

  • 松开鼠标, 拖拽结束, 所有状态重新初始化

<template>
  <div
    class="drag-wrap"
    @mousewheel="handleMousewheel"
    @mousedown="handleMouseDown"
    @mousemove="handleMouseMove"
    @mouseup="handleMouseUp"
    @mouseleave="handleMouseleave"
    :style="{
      transform: `scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`,
    }"
  >
    <slot></slot>
  </div>
</template>

<script>
  /**
   * 鼠标按下的初始位置
   */
  let startPosition = {};
  // 是否开始拖动
  let isStart = false;
  export default {
    data() {
      return {
        transform: {
          x: 0,
          y: 0,
          scale: 1,
        },
      };
    },
    methods: {
      handleMousewheel(ev) {
        const down = ev.wheelDelta ? ev.wheelDelta < 0 : ev.detail > 0;
        let scale;
        if (down) {
          // 滚轮向下
          scale = this.transform.scale - 0.1;
        } else {
          // 滚轮向上
          scale = this.transform.scale + 0.1;
        }
        if (scale > 0.5 && scale < 4) {
          this.transform.scale = scale;
        }
        if (ev.preventDefault) {
          ev.preventDefault();
        }
        return false;
      },
      handleMouseDown(event) {
        event.preventDefault();
        // 并记录当前位置
        startPosition = {
          clientX: event.clientX,
          clientY: event.clientY,
        };
        isStart = true;
      },
      handleMouseMove(event) {
        if (isStart) {
          const { clientX, clientY } = startPosition;
          // 获取鼠标的偏移量
          const offsetX = event.clientX - clientX;
          const offsetY = event.clientY - clientY;
          const { x, y, scale } = this.transform;
          this.transform = {
            x: x + offsetX,
            y: y + offsetY,
            scale,
          };
          startPosition = {
            clientX: event.clientX,
            clientY: event.clientY,
          };
        }
      },
      handleMouseUp(event) {
        isStart = false;
      },
      /**
       * 当鼠标先离开.drag-wrap然后再放开鼠标时, 依然可以恢复状态
       */
      handleMouseleave(event) {
        isStart = false;
      },
    },
  };
</script>

<style lang="less" scoped>
  .drag-wrap {
    height: 100%;
  }
</style>

# 三. 需要注意的点

# 1. 为什么使用 translate 而不是 position

由于相对定位是依赖上一层定位元素, 使用定位可能会导致其子元素依赖的定位元素变化, 而导致一些奇怪的问题

# 2. 为什么使用 scale 而不是 zoom

  • zoom 会引起整个页面的重绘而 scale 缩放所占据的原始尺寸不变,只在当前元素进行重绘, 因此 scale 性能更好

  • zoom 缩放中心是左上角而 scale 缩放中心可调整

  • zoom 会受到最小字体大小为 12px 等限制