如何在移动端的Chromium浏览器中防止意外滚动
我的代码创建了一个浮动框。这个框有一个顶部工具栏,里面放了几个按钮(下面的示例中为了简化仅放了两个按钮)。当用户点击或触摸顶部工具栏来拖拽时,应该把浮动框一起拖动,移动到用户想要的位置。
然而,在基于Chromium的移动端浏览器上(桌面端的Chromium浏览器也会出现这个问题,但较少见),我遇到了似乎是浏览器的问题。拖动浮动框时,浏览器并不是在拖动浮动框,而是在尝试滚动页面。也就是说,用户触摸浮动框的顶部工具栏并试图拖动,但实际发生的是他们在滚动浮动框后面的页面。
我已经尝试设置 touch-action: none,这确实有所改善,但并未解决问题。多个 ev.preventDefault 和 ev.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();
我的问题:
- 这是浏览器或设备的bug,还是我的代码有问题?用不同的手机测试这段代码,但同一个浏览器(Brave)时,结果却不一致(有时甚至无法拖动顶栏)。
- 有没有办法避免基于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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。