如何在移动端的Chromium浏览器中防止意外滚动

移动开发 2026-07-09

我的代码创建了一个浮动框。这个框有一个顶部工具栏,里面放了几个按钮(下面的示例中为了简化仅放了两个按钮)。当用户点击或触摸顶部工具栏来拖拽时,应该把浮动框一起拖动,移动到用户想要的位置。

然而,在基于Chromium的移动端浏览器上(桌面端的Chromium浏览器也会出现这个问题,但较少见),我遇到了似乎是浏览器的问题。拖动浮动框时,浏览器并不是在拖动浮动框,而是在尝试滚动页面。也就是说,用户触摸浮动框的顶部工具栏并试图拖动,但实际发生的是他们在滚动浮动框后面的页面。

我已经尝试设置 touch-action: none,这确实有所改善,但并未解决问题。多个 ev.preventDefaultev.stopPropagation 似乎没有效果,z-index: 1000 也一样。

下面是一个代码示例:

const createfloatingbox = () => {
    const floatingbox = document.createElement("div");
    floatingbox.classList.add("floatingbox");
    const topbar = document.createElement("div");
    const floatingboxContent = document.createElement("textarea");
    floatingboxContent.classList.add("floatingbox-content");

    floatingboxContent.value = `some text`;

    topbar.innerHTML = `
        <button class="floatingbox-copy" title="Copiar texto" style="height: 1.5rem; width: 1.5rem; top: 2px">svg</button>
        <button onclick="
        const floatingboxContent = this.closest('.floatingbox').querySelector('.floatingbox-content');
        floatingboxContent.style.display = floatingboxContent.style.display ? '' : 'none'; 
        ">—</button>
        <button onclick="this.closest('.floatingbox').remove();">X</button>
    `;

    floatingbox.style.cssText = `
        position: fixed; 
        top: 3rem;
        left: 2rem;
        z-index: 10;
        border: 1px solid #ccc;
        box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    `;
    topbar.style.cssText = `
        cursor: move;
        user-select: none;
        display: flex;
        justify-content: flex-end;
        width: 100%;
        min-width: 5rem;
        background-color: #DDD;
        /* 
        * "touch-action" is critical for mobile Chromium,
        * because it privileges touch for scrolling, dragging,
        * etc., so these take priority over my code
        */
        touch-action: none;
    `;
    // some stylesheet is changing the buttons padding... bad quick fix:
    topbar
        .querySelectorAll("button")
        .forEach((btn) => (btn.style.padding = "0 5px"));
    floatingboxContent.style.cssText = `
        width: 500px;
        height: 500px;
        max-width: 90vw;
        max-height: 90vh;
    `;

    floatingbox.append(topbar, floatingboxContent);
    document.body.prepend(floatingbox);

    // drag box around
    let offset = { x: 0, y: 0 };

    topbar.addEventListener("pointerdown", (e) => {
        offset.x = e.clientX - floatingbox.offsetLeft;
        offset.y = e.clientY - floatingbox.offsetTop;

        // set pointer capture, because floatingbox is moving on top of different iframes
        topbar.setPointerCapture(e.pointerId);

        e.preventDefault();
        e.stopImmediatePropagation();

        const drag = (e) => {
            floatingbox.style.left = e.clientX - offset.x + "px";
            floatingbox.style.top = e.clientY - offset.y + "px";

            /* these seemed to make things a little worse instead of improving:
            e.preventDefault();
            e.stopPropagation();
            */
        };

        window.addEventListener("pointermove", drag);
        window.addEventListener("pointerup", () => {
            window.removeEventListener("pointermove", drag);
        }, { once: true });
    });
};
createfloatingbox();

我的问题:

  1. 这是浏览器或设备的bug,还是我的代码有问题?用不同的手机测试这段代码,但同一个浏览器(Brave)时,结果却不一致(有时甚至无法拖动顶栏)。
  2. 有没有办法避免基于Chromium的浏览器出现的“先滚动再拖动”的行为,让我的代码在不同浏览器/设备上都能正常工作?

解决方案

window.addEventListener("pointermove", drag);

在移动端的Chromium上,这里使用 window 可能会引发手势路由不一致的问题。

我打算这样做:

const createfloatingbox = () => {
    const floatingbox = document.createElement("div");
    const topbar = document.createElement("div");
    const textarea = document.createElement("textarea");

    floatingbox.style.cssText = `
        position: fixed;
        top: 3rem;
        left: 2rem;
        z-index: 999999;
    `;

    topbar.style.cssText = `
        background: #ddd;
        padding: 4px;
        cursor: move;

        touch-action: none;
        user-select: none;
        -webkit-user-select: none;
        -webkit-touch-callout: none;
    `;

    textarea.style.cssText = `
        width: 300px;
        height: 200px;
    `;

    topbar.innerHTML = `
        <button>—</button>
        <button>X</button>
    `;

    floatingbox.append(topbar, textarea);
    document.body.append(floatingbox);

    let startX = 0;
    let startY = 0;

    let initialLeft = 0;
    let initialTop = 0;

    const move = (e) => {
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;

        floatingbox.style.left = initialLeft + dx + "px";
        floatingbox.style.top = initialTop + dy + "px";

        e.preventDefault();
    };

    const up = (e) => {
        topbar.releasePointerCapture(e.pointerId);

        topbar.removeEventListener("pointermove", move);
        topbar.removeEventListener("pointerup", up);

        document.body.style.overflow = "";
    };

    topbar.addEventListener("pointerdown", (e) => {

        // Ignore button clicks
        if (e.target.closest("button")) return;

        startX = e.clientX;
        startY = e.clientY;

        initialLeft = floatingbox.offsetLeft;
        initialTop = floatingbox.offsetTop;

        topbar.setPointerCapture(e.pointerId);

        topbar.addEventListener("pointermove", move);
        topbar.addEventListener("pointerup", up);

        // Optional but VERY effective on Android Chromium:
        document.body.style.overflow = "hidden";

        e.preventDefault();
    });
};

createfloatingbox();
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章