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

199 阅读1分钟

一. 前言

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

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

二. 实现

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 等限制