纯JavaScript自适应滑块移动端拖动异常问题求助
移动端滑块拖动异常问题修复
问题描述
基于纯JS实现的滑块组件,PC端拖动正常,但在手机端或自适应布局下滑块移动异常。已尝试多种移动端点击初始化方式,均未解决问题。
原实现代码
JavaScript
const containerMass = 5; const mouseMass = 10; let imageHasLoaded = false; let mouseX = 0; let prevMouseX = 0; let mouseXOnDown = null; let isMouseDown = false; let containerPosition = 0; let mouseVelocity = 0; let containerVelocity = 0; let imagesElement = null; const createBeltScroller = (container, images) => { imageHasLoaded = true; const beltDOMItems = images.map((image, index) => { const element = document.createElement("div"); element.classList.add("item"); const elementDiv = document.createElement("div"); elementDiv.style.height = "200px"; elementDiv.style.width = "200px"; elementDiv.style.backgroundColor = 'green' elementDiv.innerHTML = image element.appendChild(elementDiv); return element; }); imagesElement = beltDOMItems.map((element) => element); beltDOMItems.forEach((beltDOMItem) => { container.appendChild(beltDOMItem); }); }; const container = document.querySelector(".container"); createBeltScroller(container, ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]); // Mouse event handlers const onMouseUpdate = (event) => { mouseX = event.pageX; }; // Mouse movement const onMouseDown = () => { isMouseDown = true; }; // Mouse button press const onMouseUp = () => { isMouseDown = false; }; // Mouse button release // Add pointer event listeners document.addEventListener("pointermove", onMouseUpdate, false); document.getElementById("container").addEventListener("pointerdown", onMouseDown); document.addEventListener("pointerup", onMouseUp); const calculateMouseMomentum = () => { if (isMouseDown) { if (mouseXOnDown == null) { mouseXOnDown = mouseX; containerVelocity = 0; } const distance = mouseX - mouseXOnDown; mouseVelocity = mouseX - prevMouseX; } else { if (mouseXOnDown != null) { containerVelocity = ((2 * mouseMass) / (mouseMass + containerMass)) * mouseVelocity + ((containerMass - mouseMass) / (mouseMass + containerMass)) * containerVelocity; const maxVelocity = 60; if (containerVelocity > maxVelocity) { containerVelocity = maxVelocity; } else if (containerVelocity < -maxVelocity) { containerVelocity = -maxVelocity; } mouseXOnDown = null; mouseVelocity = 0; } } prevMouseX = mouseX; }; const updateContainer = () => { const boundRight = -container.offsetWidth + window.innerWidth - 85; const isOutBound = containerPosition > 0 || containerPosition < boundRight; if (!isMouseDown) { const mu = 0.04; const g = 10; const flictionForce = containerMass * g * mu; const flictionA = flictionForce / containerMass; if (containerVelocity > 0) { containerVelocity -= flictionA; if (containerVelocity < 0) { containerVelocity = 0; } } else if (containerVelocity < 0) { containerVelocity += flictionA; if (containerVelocity > 0) { containerVelocity = 0; } } if (isOutBound) { const k = 0.01; const restLength = containerPosition > 0 ? 0 : boundRight; const currentLength = containerPosition; const dragForce = 1 * k * (restLength - currentLength); const dragForceA = dragForce / containerMass; containerVelocity += dragForce; const nextPosition = containerPosition + containerVelocity; if (containerPosition < boundRight && nextPosition > boundRight) { containerVelocity = 0; containerPosition = boundRight; } else if (containerPosition > 0 && nextPosition < 0) { containerVelocity = 0; containerPosition = 0; } } } containerPosition = containerPosition + containerVelocity + (isOutBound ? mouseVelocity / 2 : mouseVelocity); container.style.transform = `translate(${containerPosition}px)`; }; const loop = () => { calculateMouseMomentum(); updateContainer(); window.requestAnimationFrame(() => { loop(); }); }; loop();
CSS
.container { height: fit-content; display: inline-flex; flex-wrap: nowrap; align-items: center; touch-action: pan-y; user-select: none; cursor: grab; margin: 2% 0; font-size: 150px; } .item { width: fit-content; height: fit-content; padding-left: 4.43vw; opacity: 1; transition: opacity 2s; }
HTML
<div id="container" class="container"></div>
问题原因及修复方案
核心问题
- 移动端坐标获取偏差:使用
event.pageX受页面滚动影响,导致触摸位置计算错误 - 未处理多点触摸:pointer事件未锁定活跃触摸点,多点操作时触发异常
- 边界值未实时更新:窗口尺寸变化时,滑块边界未重新计算,自适应布局下边界失效
- 拖动逻辑误差:原速度叠加逻辑在移动端触摸采样率下累积位移偏差
修复后的代码
JavaScript
const containerMass = 5; const mouseMass = 10; let imageHasLoaded = false; let mouseX = 0; let prevMouseX = 0; let mouseXOnDown = null; let isMouseDown = false; let activePointerId = null; // 跟踪当前活跃触摸点 let containerPosition = 0; let mouseVelocity = 0; let containerVelocity = 0; let imagesElement = null; const createBeltScroller = (container, images) => { imageHasLoaded = true; const beltDOMItems = images.map((image, index) => { const element = document.createElement("div"); element.classList.add("item"); const elementDiv = document.createElement("div"); elementDiv.style.height = "200px"; elementDiv.style.width = "200px"; elementDiv.style.backgroundColor = 'green' elementDiv.innerHTML = image element.appendChild(elementDiv); return element; }); imagesElement = beltDOMItems.map((element) => element); beltDOMItems.forEach((beltDOMItem) => { container.appendChild(beltDOMItem); }); }; const container = document.querySelector(".container"); createBeltScroller(container, ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]); // 实时计算滑块边界 const calculateBoundRight = () => { return -container.offsetWidth + window.innerWidth - 85; }; // 指针事件处理 const onPointerDown = (event) => { isMouseDown = true; activePointerId = event.pointerId; mouseX = event.clientX; // 使用clientX避免滚动影响 mouseXOnDown = mouseX; prevMouseX = mouseX; containerVelocity = 0; }; const onPointerMove = (event) => { if (!isMouseDown || event.pointerId !== activePointerId) return; mouseX = event.clientX; }; const onPointerUp = (event) => { if (event.pointerId !== activePointerId) return; isMouseDown = false; activePointerId = null; }; // 绑定事件 container.addEventListener("pointerdown", onPointerDown); document.addEventListener("pointermove", onPointerMove); document.addEventListener("pointerup", onPointerUp); document.addEventListener("pointercancel", onPointerUp); // 处理触摸中断(如来电、弹窗) // 窗口 resize 时校正滑块位置 window.addEventListener("resize", () => { const boundRight = calculateBoundRight(); containerPosition = Math.max(boundRight, Math.min(0, containerPosition)); container.style.transform = `translate(${containerPosition}px)`; }); const calculateMouseMomentum = () => { if (isMouseDown) { mouseVelocity = mouseX - prevMouseX; } else { if (mouseXOnDown != null) { containerVelocity = ((2 * mouseMass) / (mouseMass + containerMass)) * mouseVelocity + ((containerMass - mouseMass) / (mouseMass + containerMass)) * containerVelocity; const maxVelocity = 60; containerVelocity = Math.max(-maxVelocity, Math.min(maxVelocity, containerVelocity)); mouseXOnDown = null; mouseVelocity = 0; } } prevMouseX = mouseX; }; const updateContainer = () => { const boundRight = calculateBoundRight(); const isOutBound = containerPosition > 0 || containerPosition < boundRight; if (!isMouseDown) { const mu = 0.04; const g = 10; const frictionForce = containerMass * g * mu; const frictionA = frictionForce / containerMass; if (containerVelocity > 0) { containerVelocity -= frictionA; containerVelocity = Math.max(0, containerVelocity); } else if (containerVelocity < 0) { containerVelocity += frictionA; containerVelocity = Math.min(0, containerVelocity); } if (isOutBound) { const k = 0.01; const restLength = containerPosition > 0 ? 0 : boundRight; const dragForce = k * (restLength - containerPosition); containerVelocity += dragForce; const nextPosition = containerPosition + containerVelocity; // 边界校正 if (containerPosition < boundRight && nextPosition > boundRight) { containerVelocity = 0; containerPosition = boundRight; } else if (containerPosition > 0 && nextPosition < 0) { containerVelocity = 0; containerPosition = 0; } } } // 更新滑块位置 if (isMouseDown) { const deltaX = mouseX - mouseXOnDown; containerPosition += deltaX; mouseXOnDown = mouseX; // 更新起点避免累积误差 } else { containerPosition += containerVelocity; } // 强制限制在有效边界内 containerPosition = Math.max(boundRight, Math.min(0, containerPosition)); container.style.transform = `translate(${containerPosition}px)`; }; const loop = () => { calculateMouseMomentum(); updateContainer(); window.requestAnimationFrame(loop); }; loop();
关键修复点
- 替换
pageX为clientX,消除页面滚动对触摸坐标的影响 - 添加
activePointerId锁定活跃触摸点,避免多点触摸干扰 - 每次更新滑块时实时计算边界值,适配窗口尺寸变化
- 拖动时直接计算触摸位移更新位置,消除速度叠加带来的偏差
- 绑定
pointercancel事件处理触摸中断场景 - 窗口resize时自动校正滑块位置
内容的提问来源于stack exchange,提问作者Chew
相关产品推荐
相关产品推荐

