You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用JavaScript实现触摸滑块滚动位置的吸附效果?

电商触摸滑块自动吸附问题修复

问题说明

我正在制作电商落地页,想要创建一款支持触摸操作的产品滑块/轮播组件(类似Nike移动端官网效果)。已创建带有overflow:scroll属性的.slider-container容器,内部包含6个.slide产品插槽。需求是:用户触摸滑动(touchmove)后,松开手指(touchend)时滑块自动吸附到对应产品位置。但当前代码存在问题——每次滚动都会回到初始位置(slider.scrollLeft = 0)。

原问题代码

HTML

<div class="slider-container">
    <div class="slide">
        <img src="https://picsum.photos/id/237/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/25/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/9/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/237/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/13/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/10/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
</div>

CSS

*{
  margin: 0;
  padding: 0;
  font-family: "Roboto Condensed", sans-serif;
}

html{
   scroll-behavior:smooth;
   overflow-x: hidden;
}

body {
   overflow-x: hidden;
}

.slider-container{
  display: flex;
  overflow: scroll;
}

.slide{
  min-width: 275px;
  margin: 20px 0 40px 0;
  padding-left: 10px;
}

.preview-product-title{
font-size: 17px;
padding: 2px 0 2px 0;
}

.preview-color{
  font-size: 17px;
  color: gray;
  padding-bottom: 2px;
}

.preview-price{
  font-size: 17px;
  font-weight: bold;
}

.slide:first-child{
  margin: 20px 0 40px 15px;
}

.slide:last-child{
  margin: 20px 20px 40px 0;
}

JavaScript

let startPos = 0
let sliderScroll = 0
let prevPos = 0
let currentPos = 0

const slider = document.querySelector('.slider-container')
const slideWidth = document.querySelector('.slide').getBoundingClientRect().width

function getPositionX(event) {
    return
    event.touches[0].clientX
}

function touchStart(event) {
    startPos = getPositionX(event)
    sliderScroll = slider.scrollLeft
}

function touchMove(event) {
    currentPos = getPositionX(event)
}

function touchEnd() {
    let calcNumber = Math.round((sliderScroll + Math.abs(startPos - currentPos)) / slideWidth)
    let setScrollPos = calcNumber * slideWidth
    
    
    slider.scrollTo({
        left: setScrollPos,
        behaviour: 'smooth'
    })

}

slider.addEventListener('touchstart', touchStart)
slider.addEventListener('touchmove', touchMove)
slider.addEventListener('touchend', touchEnd)

错误原因分析

  1. getPositionX函数返回逻辑错误:return单独占一行,导致后续获取触摸位置的代码永远不会执行,startPos和currentPos始终为undefined,计算时触发异常。
  2. 滑动方向与实时反馈缺失:仅计算滑动绝对值,未区分左右滑动方向;且touchmove阶段未更新滑块位置,用户滑动时无跟随感,结束时计算逻辑完全失效。
  3. scrollTo参数拼写错误:behaviour应为美式拼写behavior,导致平滑滚动不生效。
  4. 边界未限制:未处理滑块滚动到内容边缘的情况,可能出现空白区域。

修复后的完整代码

HTML(无改动)

<div class="slider-container">
    <div class="slide">
        <img src="https://picsum.photos/id/237/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/25/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/9/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/237/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/13/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
    <div class="slide">
        <img src="https://picsum.photos/id/10/200/300" style="width: 275px;">
        <p class="preview-product-title">Cool Shorts</p>
        <p class="preview-color">Blue Navy</p>
        <p class="preview-price">€29</p>
    </div>
</div>

CSS(新增隐藏滚动条样式)

*{
  margin: 0;
  padding: 0;
  font-family: "Roboto Condensed", sans-serif;
}

html{
   scroll-behavior:smooth;
   overflow-x: hidden;
}

body {
   overflow-x: hidden;
}

.slider-container{
  display: flex;
  overflow: scroll;
  /* 隐藏滚动条,贴近Nike官网视觉效果 */
  scrollbar-width: none;
  cursor: grab;
}

/* 针对webkit内核浏览器隐藏滚动条 */
.slider-container::-webkit-scrollbar {
  display: none;
}

.slide{
  min-width: 275px;
  margin: 20px 0 40px 0;
  padding-left: 10px;
}

.preview-product-title{
font-size: 17px;
padding: 2px 0 2px 0;
}

.preview-color{
  font-size: 17px;
  color: gray;
  padding-bottom: 2px;
}

.preview-price{
  font-size: 17px;
  font-weight: bold;
}

.slide:first-child{
  margin: 20px 0 40px 15px;
}

.slide:last-child{
  margin: 20px 20px 40px 0;
}

JavaScript(核心逻辑修复)

let startPos = 0;
let sliderScroll = 0;
let currentPos = 0;

const slider = document.querySelector('.slider-container');
// 计算完整的slide宽度,包含内边距和外边距
const slide = document.querySelector('.slide');
const slideWidth = slide.offsetWidth + (parseInt(getComputedStyle(slide).marginRight) || 0);

function getPositionX(event) {
    // 修复return位置错误,正确返回触摸X坐标
    return event.touches[0].clientX;
}

function touchStart(event) {
    // 阻止默认触摸行为,避免页面整体滚动干扰
    event.preventDefault();
    startPos = getPositionX(event);
    sliderScroll = slider.scrollLeft;
}

function touchMove(event) {
    event.preventDefault();
    currentPos = getPositionX(event);
    // 实时计算滑动距离,更新滑块位置,实现跟随滑动
    const moveDistance = startPos - currentPos;
    slider.scrollLeft = sliderScroll + moveDistance;
}

function touchEnd() {
    // 根据当前滚动位置计算吸附目标
    const targetPos = Math.round(slider.scrollLeft / slideWidth) * slideWidth;
    // 限制滚动边界,避免超出内容范围
    const maxScrollPos = slider.scrollWidth - slider.clientWidth;
    const finalPos = Math.min(Math.max(targetPos, 0), maxScrollPos);
    
    slider.scrollTo({
        left: finalPos,
        // 修复拼写错误,启用平滑滚动
        behavior: 'smooth'
    });
}

// 绑定触摸事件
slider.addEventListener('touchstart', touchStart);
slider.addEventListener('touchmove', touchMove);
slider.addEventListener('touchend', touchEnd);

// 额外添加鼠标拖拽支持,兼容桌面端
let isDragging = false;
let startMousePos = 0;

slider.addEventListener('mousedown', (e) => {
    isDragging = true;
    startMousePos = e.clientX;
    sliderScroll = slider.scrollLeft;
    slider.style.cursor = 'grabbing';
});

slider.addEventListener('mousemove', (e) => {
    if (!isDragging) return;
    const moveDistance = startMousePos - e.clientX;
    slider.scrollLeft = sliderScroll + moveDistance;
});

slider.addEventListener('mouseup', () => {
    isDragging = false;
    slider.style.cursor = 'grab';
    // 复用触摸结束的吸附逻辑
    touchEnd();
});

slider.addEventListener('mouseleave', () => {
    isDragging = false;
    slider.style.cursor = 'grab';
});

主要修复点

  1. 修复触摸位置获取函数:将return与返回值放在同一行,确保能正确获取触摸坐标。
  2. 添加实时滑动反馈:在touchmove阶段根据滑动距离实时更新滑块位置,让用户操作有跟随感。
  3. 优化吸附逻辑:直接基于当前滚动位置计算目标吸附点,逻辑更准确,且添加边界限制避免滚动溢出。
  4. 修复平滑滚动参数:修正scrollTo的参数拼写错误,确保平滑滚动生效。
  5. 兼容桌面端鼠标拖拽:添加鼠标事件监听,让滑块在桌面端也能正常拖拽吸附。
  6. 视觉优化:隐藏滚动条,添加拖拽光标样式,贴近目标网站的交互体验。

内容的提问来源于stack exchange,提问作者Mr. G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:07:32