求实现带边缘渐隐效果的垂直循环滚动数字选择组件(类行李箱密码锁)
垂直循环滚动数字密码锁组件实现方案
核心需求
- 6位垂直排列的独立数字选择列,每列包含0-9完整数字
- 支持鼠标/触摸上下滑动切换数字,实现无缝循环滚动效果
- 滚动时列的上下边缘添加渐隐遮罩,强化循环视觉错觉
- 适配应用内选票流程的交互场景
实现思路
- 循环滚动基础:每列内部重复渲染3组0-9数字(中间组为可视核心,上下组用于衔接循环),当滚动至顶部/底部组时,自动重置滚动位置到中间组对应数字,实现无感知循环
- 滚动校准:监听滚动/触摸事件,计算滚动偏移量,自动将列定位到最近的数字位置,保证数字始终居中显示
- 视觉遮罩:通过CSS渐变遮罩实现列上下边缘的渐隐效果,隐藏滚动衔接的边界
- 交互优化:添加平滑滚动过渡,选中数字高亮,提升操作体验
代码示例
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
相关产品推荐
相关产品推荐

