panzoom拖动放缩状态下,拖动dom节点有残影,且不跟手是什么原因?

项目中用到了panzoom组件(https://github.com/anvaka/panzoom),上面有一些绝对定位的dom元素,因为点击这些元素时,不能拖动画布,所以我需要模拟点击的当前dom节点,在没有缩放情况下,拖动是正常的,没有残影,也跟手,等按下alt键,滚动鼠标滑轮缩放后,在快速拖动节点,就会出现残影,且模拟的dom节点不跟手,请问怎么回事?

已处理:(偶尔还会有残影)

<template>
  <div class="page-wrapper">
    <div class="lft">
      <div>left</div>
    </div>
    <div class="rht">
      <div class="nodes-content-wrapper">
        <div class="nodes-wrapper" id="nodes-wrapper">
          <div
            v-for="(node, idx) in nodes"
            :key="`node-${idx}`"
            class="node"
            :style="{
              width: `${node.width}px`,
              height: `${node.height}px`,
              left: `${node.x}px`,
              top: `${node.y}px`,
            }"
            @mousedown="(e) => startDrag(e, node)"
          >
            <div class="node-inner-wrapper not-allowed-drag" :id="node.Id">
              {{ node.Id }}
            </div>
          </div>

          <!-- 影子节点 -->
          <div
            v-if="moveNode"
            style="border: 1px solid blue"
            class="node moni-node"
            :style="{
              width: `${moveNode.width}px`,
              height: `${moveNode.height}px`,
              left: `${moveNode.x}px`,
              top: `${moveNode.y}px`,
              zIndex: `${moveNodeZIndex}`,
            }"
          >
            <div>{{ moveNode.Id }}</div>
            <div>{{ moveNode.x }}</div>
            <div>{{ moveNode.y }}</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import panzoom from "panzoom";

export default {
  data() {
    return {
      pan: null,
      nodes: [
        {
          Id: "Q",
          x: 100,
          y: 660,
          width: 180,
          height: 60,
        },
        {
          Id: "G0",
          x: 380,
          y: 20,
          width: 180,
          height: 60,
        },
        {
          Id: "G",
          x: 380,
          y: 227.5,
          width: 180,
          height: 60,
        },
      ],

      moveNodeZIndex: 300, //影子节点

      moveNode: null, // 当前移动的节点
      isDragging: false, // 是否正在拖动
      offsetX: 0, // 鼠标点击node时,鼠标距离node左上角的偏移量
      offsetY: 0, // 

    };
  },

  mounted() {
    this.initPanZoom();
  },
  methods: {
    initPanZoom(scale = 1, x = 0, y = 0) {
      let that = this;

      if (that.pan) {
        that.pan.dispose();
      }

      const mainContainer = document.getElementById("nodes-wrapper");

      let params = {
        smoothScroll: false,
        bounds: false, //可以移动出父容器
        // autocenter: true,
        zoomDoubleClickSpeed: 1,
        minZoom: 0.5,
        maxZoom: 3,
        // overflow: 'auto',
        // contain: 'outside',
        beforeWheel: function (e) {
          // 仅当 altKey 按下时才允许滚轮缩放。否则 - 忽略
          var shouldIgnore = !e.altKey;
          return shouldIgnore;
        },
        beforeMouseDown: function (e) {
          // if(e.target.className == "not-allowed-drag") {
          //   console.log(e.offsetY, e.offsetX)
          // }

          if (e.target.className.indexOf("not-allowed-drag") == -1) {
            return false;
          }
          return true;
        },
      };

      that.pan = panzoom(mainContainer, params);

      // //移动到目标位置(使用初始化参数initialX、initialY会基于 scale 改变——加上拖动到,top: 100; left: 200 的位置,如果 scale = 0.5,会给你还原到 50, 100 的位置。)
      // that.pan.moveTo(x, y);

      // 缩放时设置jsPlumb的缩放比率
      that.pan.on("zoom", (e) => {

      });

      that.pan.on("panend", (e) => {});
    },

    /**
     * 在节点上按下鼠标,准备拖拽
     * @param {*} event
     * @param {*} node
     */
    startDrag(event, node) {
      let that = this;
      that.isDragging = true;
      that.moveNode = JSON.parse(JSON.stringify(node));

      that.moveNode.Id = `${that.moveNode.Id}_bak`;

      //记住点击节点的位置(点击节点dom对象的坐标)
      that.offsetX = event.offsetX
      that.offsetY = event.offsetY

      // 添加鼠标移动和松开的监听
      document.addEventListener("mousemove", that.onDrag);
      document.addEventListener("mouseup", that.stopDrag);
    },

    // 鼠标移动时更新 div 位置
    onDrag(event) {
      let that = this;
      if (that.isDragging) {

        // // 更新影子节点的位置,使其跟随鼠标
        const transform = this.pan.getTransform(); // 获取当前的缩放和位移
        const scale = transform.scale;

        const mainContainer = document.getElementById("nodes-wrapper");
        const rect = mainContainer.getBoundingClientRect()
        const offsetX = event.clientX - rect.left;
        const offsetY = event.clientY - rect.top;

        const realOffsetX = offsetX / scale;
        const realOffsetY = offsetY / scale;

        this.moveNode.x = realOffsetX - that.offsetX
        this.moveNode.y = realOffsetY - that.offsetY

      }
    },

    // 鼠标松开时停止拖动
    stopDrag() {
      this.isDragging = false;
      this.moveNode = null;
      document.removeEventListener("mousemove", this.onDrag);
      document.removeEventListener("mouseup", this.stopDrag);
    },
  },
};
</script>

<style scoped lang="scss">
.page-wrapper {
  height: 100%;
  display: flex;
  .lft {
    width: 300px;
  }
  .rht {
    flex: 1;
  }
}
.nodes-content-wrapper {
  background: #f1f1f1;
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  .nodes-wrapper {
    background: #f5e4e4;
    backface-visibility: hidden; //去掉残影
    position: relative;
    height: 100%;
    width: 100%;
    .node {
      position: absolute;
      border: 1px solid red;
      user-select: none;
      box-sizing: border-box;
      .node-inner-wrapper {
        position: relative;
        height: 100%;
      }
    }
  }
}

.plus-sub,
.plus-sib {
  position: absolute;
  font-size: 10px;
  right: 0;
}
.plus-sub {
  bottom: 0;
}

.plus-sib {
  bottom: 20px;
}

.moni-node {
  backface-visibility: hidden; //去掉残影
}
</style>

阅读 2.1k
avatarAI BotBETA

回答

这个问题通常是由于在缩放状态下,CSS 变换(transform)和事件坐标处理不当导致的。在 panzoom 组件中,缩放实际上是通过 CSS 的 transform: scale() 属性来实现的。这会导致以下几个问题:

  1. 事件坐标未正确转换
    在缩放状态下,鼠标事件(如 mousemove)的坐标相对于缩放前的画布是不同的。你需要将这些坐标根据当前的缩放比例进行转换,以确保拖动操作正确无误。
  2. 浏览器渲染延迟
    在高缩放级别下,浏览器可能需要更多时间来渲染变换后的元素,这可能导致视觉上的残影和不跟手的感觉。

解决方案

  1. 事件坐标转换
    监听 panzoom 的缩放事件,获取当前的缩放比例。在拖动操作中,根据这个比例调整鼠标事件的坐标。

    const panzoom = Panzoom(element, {
        // 配置项
    });
    
    let currentScale = 1;
    
    panzoom.on('zoom', (e) => {
        currentScale = e.transform.k[0]; // 获取当前的缩放比例
    });
    
    function onMouseMove(event) {
        const rect = element.getBoundingClientRect();
        const offsetX = event.clientX - rect.left;
        const offsetY = event.clientY - rect.top;
    
        // 根据缩放比例调整坐标
        const realOffsetX = offsetX / currentScale;
        const realOffsetY = offsetY / currentScale;
    
        // 使用调整后的坐标进行拖动操作
        // ...
    }
    
    element.addEventListener('mousemove', onMouseMove);
  2. 优化渲染性能

    • 尽量减少 DOM 操作,特别是在拖动和缩放过程中。
    • 使用 CSS 硬件加速(如 will-change: transform)来提示浏览器优化渲染。
    • 如果可能,考虑使用更高效的数据结构和算法来处理大量的 DOM 元素。
  3. 使用 requestAnimationFrame
    在拖动操作中,使用 requestAnimationFrame 来平滑动画,这有助于减少残影现象。

    let dragging = false;
    
    function onMouseDown(event) {
        dragging = true;
        requestAnimationFrame(drag);
    
        function drag(timestamp) {
            if (!dragging) return;
    
            // 处理拖动逻辑
            // ...
    
            requestAnimationFrame(drag);
        }
    }
    
    element.addEventListener('mousedown', onMouseDown);
    
    function onMouseUp() {
        dragging = false;
    }
    
    element.addEventListener('mouseup', onMouseUp);
    element.addEventListener('mouseleave', onMouseUp);

通过上述方法,你应该能够解决拖动 DOM 节点时的残影和不跟手问题。如果问题依然存在,可能需要进一步检查你的代码和 panzoom 组件的具体实现。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题