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

求实现带边缘渐隐效果的垂直循环滚动数字选择组件(类行李箱密码锁)

垂直循环滚动数字密码锁组件实现方案

核心需求

  • 6位垂直排列的独立数字选择列,每列包含0-9完整数字
  • 支持鼠标/触摸上下滑动切换数字,实现无缝循环滚动效果
  • 滚动时列的上下边缘添加渐隐遮罩,强化循环视觉错觉
  • 适配应用内选票流程的交互场景

实现思路

  1. 循环滚动基础:每列内部重复渲染3组0-9数字(中间组为可视核心,上下组用于衔接循环),当滚动至顶部/底部组时,自动重置滚动位置到中间组对应数字,实现无感知循环
  2. 滚动校准:监听滚动/触摸事件,计算滚动偏移量,自动将列定位到最近的数字位置,保证数字始终居中显示
  3. 视觉遮罩:通过CSS渐变遮罩实现列上下边缘的渐隐效果,隐藏滚动衔接的边界
  4. 交互优化:添加平滑滚动过渡,选中数字高亮,提升操作体验

代码示例

HTML结构

<div class="lock-container">
  <div class="digit-column">
    <div class="digit-list">
      <div class="digit">0</div>
      <div class="digit">1</div>
      <div class="digit">2</div>
      <div class="digit">3</div>
      <div class="digit">4</div>
      <div class="digit">5</div>
      <div class="digit">6</div>
      <div class="digit">7</div>
      <div class="digit">8</div>
      <div class="digit">9</div>
      <div class="digit">0</div>
      <div class="digit">1</div>
      <div class="digit">2</div>
      <div class="digit">3</div>
      <div class="digit">4</div>
      <div class="digit">5</div>
      <div class="digit">6</div>
      <div class="digit">7</div>
      <div class="digit">8</div>
      <div class="digit">9</div>
      <div class="digit">0</div>
      <div class="digit">1</div>
      <div class="digit">2</div>
      <div class="digit">3</div>
      <div class="digit">4</div>
      <div class="digit">5</div>
      <div class="digit">6</div>
      <div class="digit">7</div>
      <div class="digit">8</div>
      <div class="digit">9</div>
    </div>
  </div>
  <!-- 重复上述digit-column结构5次,共6列 -->
</div>

CSS样式

.lock-container {
  display: flex;
  gap: 12px;
  padding: 20px;
  background: #f5f5f5;
  border-radius: 8px;
}

.digit-column {
  width: 48px;
  height: 160px;
  overflow: hidden;
  position: relative;
  border-radius: 8px;
  background: #fff;
  /* 上下渐隐遮罩 */
  mask-image: linear-gradient(to bottom, transparent 0%, black 20%, black 80%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 20%, black 80%, transparent 100%);
}

.digit-list {
  position: relative;
  transition: transform 0.2s ease-out;
}

.digit {
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: bold;
  color: #333;
}

/* 选中数字高亮 */
.digit-column::after {
  content: '';
  position: absolute;
  top: 60px;
  left: 0;
  width: 100%;
  height: 40px;
  border-top: 2px solid #2196F3;
  border-bottom: 2px solid #2196F3;
  pointer-events: none;
}

JavaScript逻辑

const digitColumns = document.querySelectorAll('.digit-column');

digitColumns.forEach(column => {
  const list = column.querySelector('.digit-list');
  const digitHeight = 40;
  const totalDigits = list.children.length;
  // 初始定位到中间组的0位置
  let currentOffset = digitHeight * 10;
  list.style.transform = `translateY(-${currentOffset}px)`;

  let startY = 0;
  let startOffset = currentOffset;

  // 触摸事件处理
  column.addEventListener('touchstart', (e) => {
    startY = e.touches[0].clientY;
    startOffset = currentOffset;
    list.style.transition = 'none';
  });

  column.addEventListener('touchmove', (e) => {
    const deltaY = e.touches[0].clientY - startY;
    currentOffset = startOffset - deltaY;
    list.style.transform = `translateY(-${currentOffset}px)`;
  });

  column.addEventListener('touchend', () => {
    // 校准到最近的数字位置
    currentOffset = Math.round(currentOffset / digitHeight) * digitHeight;
    // 循环逻辑:超出范围时重置到中间组对应位置
    if (currentOffset < digitHeight * 10) {
      currentOffset += digitHeight * 10;
    } else if (currentOffset > digitHeight * 20) {
      currentOffset -= digitHeight * 10;
    }
    list.style.transition = 'transform 0.2s ease-out';
    list.style.transform = `translateY(-${currentOffset}px)`;
    // 获取当前选中的数字(0-9)
    const selectedDigit = (currentOffset / digitHeight) % 10;
    console.log('选中数字:', selectedDigit);
  });

  // 鼠标滚动事件处理(适配桌面端)
  column.addEventListener('wheel', (e) => {
    e.preventDefault();
    list.style.transition = 'none';
    currentOffset += e.deltaY > 0 ? digitHeight : -digitHeight;
    // 循环校准
    if (currentOffset < digitHeight * 10) {
      currentOffset += digitHeight * 10;
    } else if (currentOffset > digitHeight * 20) {
      currentOffset -= digitHeight * 10;
    }
    list.style.transform = `translateY(-${currentOffset}px)`;
    const selectedDigit = (currentOffset / digitHeight) % 10;
    console.log('选中数字:', selectedDigit);
  });
});

关键细节说明

  • 重复渲染3组数字是为了避免滚动到边界时出现空白,通过滚动位置重置实现循环
  • CSS遮罩使用mask-image实现跨浏览器兼容(需添加-webkit前缀)
  • 滚动校准确保数字始终居中对齐选中区域,提升交互精准度
  • 触摸和鼠标事件分别处理,适配移动端和桌面端场景

内容的提问来源于stack exchange,提问作者ADARSH K VINOD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 14:00:33