以鼠标为中心缩放与拖拽页面功能实现

276 阅读3分钟

1. 引言

在 Web 应用中,支持DOM元素缩放与拖拽是一项常见的需求,尤其在图片查看器、流程图操作中。本文将详细解析一段用于实现缩放与拖拽交互的 JavaScript 代码。

2.预览

2025-02-14.gif

3. 代码解析

3.1 代码概述

这段代码定义了一系列函数,用于实现对目标元素的鼠标滚轮缩放和拖拽平移功能。其核心逻辑包括:

  • 监听鼠标滚轮事件,实现缩放功能。
  • 监听鼠标按下、移动和释放事件,实现拖拽功能。
  • 使用 transform 进行位移和缩放变换。

3.2 代码核心变量

let scale = 1; // 当前缩放比例
let callback = null; // 缩放回调函数
let translateX = 0, translateY = 0; // 平移偏移量
let isDragging = false; // 是否处于拖拽状态
let startX, startY; // 记录鼠标拖拽起始位置
let target = null; // 目标元素
let wrapper = null; // 容器元素

这些变量用于存储当前缩放比例、拖拽状态、鼠标起始位置等信息,以便在交互过程中进行更新。

4. 事件绑定

4.1 初始化函数 zoomInit

该函数用于初始化缩放和拖拽功能,绑定鼠标滚轮、拖拽等事件。

export function zoomInit(dom1, dom2, cb) {
    callback = cb;
    scale = 1;
    wrapper = dom1.value;
    target = dom2.value;

wrapper(容器元素)上监听滚轮事件,实现缩放:

    wrapper.addEventListener("wheel", function (event) {
        event.preventDefault();
        const rect = target.getBoundingClientRect();
        const offsetX = event.clientX - rect.left;
        const offsetY = event.clientY - rect.top;
        const originX = offsetX / rect.width;
        const originY = offsetY / rect.height;
        wheelZoomFunc({scaleFactor: event.deltaY, originX, originY});
    });

wrapper 上监听 mousedownmousemovemouseup 事件,实现拖拽功能:

    wrapper.addEventListener("mousedown", function (event) {
        isDragging = true;
        startX = event.clientX - translateX;
        startY = event.clientY - translateY;
        target.style.cursor = "grabbing";
    });

    wrapper.addEventListener("mousemove", function (event) {
        if (!isDragging) return;
        translateX = event.clientX - startX;
        translateY = event.clientY - startY;
        updateTransform();
    });

    wrapper.addEventListener("mouseup", function () {
        isDragging = false;
        target.style.cursor = "grab";
    });
}

5. 变换更新

5.1 更新 transform 样式

function updateTransform() {
    target.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
}

该函数用于更新 targettransform 样式,实现缩放和平移。

5.2 处理缩放 wheelZoomFunc

export function wheelZoomFunc({scaleFactor, originX = 0.5, originY = 0.5, isExternalCall = false}) {
    let ratio = 1.1;
    if (scaleFactor && scaleFactor > 0) {
        ratio = 1 / ratio;
    }

    const rect = target.getBoundingClientRect();
    let newScale = isExternalCall ? scaleFactor : scale * ratio;
    newScale = Math.max(0.5, Math.min(newScale, 5));
    
    const deltaScale = newScale / scale;
    translateX -= (originX - 0.5) * rect.width * (deltaScale - 1);
    translateY -= (originY - 0.5) * rect.height * (deltaScale - 1);
    
    scale = newScale;
    callback && callback((scale * 100).toFixed(0));
    updateTransform();
}

该函数通过 scaleFactor 控制缩放,并调整 translateXtranslateY 以确保缩放围绕鼠标焦点进行。

6. 重置功能

6.1 resetImage 函数

export function resetImage() {
    scale = 1;
    translateX = 0;
    translateY = 0;
    isDragging = false;
    startX = null;
    startY = null;
    target.style.transform = "translate3d(0px, 0px, 0px) scale(1)";
}

该函数用于将 target 归位,恢复初始缩放比例和位移。

7. 使用教程

7.1 初始化

zoomInit(boxRef, contentRef, (val) => {
  console.log(`缩放百分比${val}%`)
})

7.2 放大缩小按钮外部使用

7.2.1 放大

wheelZoomFunc({scaleFactor: num.value / 100 + 0.1, isExternalCall: true})

7.2.2 缩小

  wheelZoomFunc({scaleFactor: num.value / 100 - 0.1, isExternalCall: true})

8. 示例代码

源码地址GitHub:github.com/SpanManX/vu…

源码地址Gitee:gitee.com/testcjw/vue…

9. 源码

let scale = 1; // 当前缩放比例
let callback = null; // 缩放回调函数
let translateX = 0, translateY = 0; // 平移偏移量
let isDragging = false; // 是否处于拖拽状态
let startX, startY; // 记录鼠标拖拽起始位置
let target = null; // 目标元素
let wrapper = null; // 容器元素

export function zoomInit(dom1, dom2, cb) {
    callback = cb;
    scale = 1;
    wrapper = dom1.value
    target = dom2.value

    // 在 wrapper 内部监听鼠标滚轮缩放
    wrapper.addEventListener("wheel", function (event) {
        event.preventDefault(); // 阻止默认滚动行为

        const rect = target.getBoundingClientRect();
        const offsetX = event.clientX - rect.left;
        const offsetY = event.clientY - rect.top;

        const originX = offsetX / rect.width;
        const originY = offsetY / rect.height;

        wheelZoomFunc({scaleFactor: event.deltaY, originX, originY});
    });

    // 鼠标按下,开始拖拽
    wrapper.addEventListener("mousedown", function (event) {
        isDragging = true;
        startX = event.clientX - translateX;
        startY = event.clientY - translateY;
        target.style.cursor = "grabbing";
        // setTimeout(() => {
        //     target.setPointerCapture(event.pointerId);
        // },100)
    });

    // 鼠标移动,执行拖拽
    wrapper.addEventListener("mousemove", function (event) {
        if (!isDragging) return;

        translateX = event.clientX - startX;
        translateY = event.clientY - startY;
        updateTransform();
    });

    // 鼠标松开,停止拖拽
    wrapper.addEventListener("mouseup", function () {
        isDragging = false;
        target.style.cursor = "grab";
    });
}

/**
 * 重置大小和位置
 **/
export function resetImage() {
    scale = 1;
    translateX = 0
    translateY = 0;
    isDragging = false;
    startX = null
    startY = null;
    target.style.transform = "translate3d(0px, 0px, 0px) scale(1)";
}

function updateTransform() {
    target.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
}

export function wheelZoomFunc({scaleFactor, originX = 0.5, originY = 0.5, isExternalCall = false}) {
    let ratio = 1.1;

    if (scaleFactor && scaleFactor > 0) {
        ratio = 1 / ratio;
    }

    const rect = target.getBoundingClientRect();
    let newScale = isExternalCall ? scaleFactor : scale * ratio;

    newScale = Math.max(0.5, Math.min(newScale, 5));

    const deltaScale = newScale / scale;
    translateX -= (originX - 0.5) * rect.width * (deltaScale - 1);
    translateY -= (originY - 0.5) * rect.height * (deltaScale - 1);

    scale = newScale;
    callback && callback((scale * 100).toFixed(0));
    updateTransform();
}

10. 结论

这段代码提供了一个完整的DOM缩放与拖拽交互方案,主要通过监听鼠标事件并动态修改 transform 来实现。通过 wheelZoomFunc 处理缩放,并结合 mousedownmousemovemouseup 事件实现拖拽,使得交互体验更加自然。