- 移动端滑动选项实现多选效果
- 通过 touchstart、touchmove、 touchend、touchcancel 事件实现
- 通过父元素代理事件的方式实现子组件点击选中选项
- 如果选项添加 disabled 属性将不会被选中
- 移动端拖拽 .box 和 .options 元素时,是有拖拽效果的,去除拖拽效果有两种方式:
① 用 csspointer-events: none;
阻止拖拽,.box 元素使用的是这种方式
② 用 jsevent.preventDefault()
阻止元素的事件, .options 元素使用的是这种方式 - 如果需要通过鼠标滚轮来切换选项,可以使用 wheel 来实现,本案例没有实现
- touchcancel 事件一直没有办法触发,所以没有验证效果
-
css
* { margin: 0; padding: 0; } ul, li { list-style: none; } .box { height: 300px; width: 400px; margin: 50px auto; border: 1px solid #000; border-radius: 12px; position: relative; background-color: #f5f5f5; touch-action: none; /* 元素不能滑动 */ } .box .columns { overflow: hidden; height: 100%; } .box .columns .options { transition-timing-function: cubic-bezier(0.23, 1, 0.68, 1); transition-duration: 0ms; transition-property: all; height: 100%; } .box .columns .options li { height: 60px; line-height: 60px; text-align: center; display: flex; justify-content: space-between; padding: 0 15px; } .box .columns .options li[disabled] { cursor: not-allowed; opacity: 0.3; } .box .columns .options li.selected { font-weight: bold; } .box .columns .options li.selected::after { content: '√'; font-size: 16px; color: red; }
-
html
<div class="box"> <div class="columns"> <ul class="options"> <li data-index="0">选项一</li> <li disabled data-index="1">选项2</li> <li data-index="2">选项3</li> <li data-index="3">选项4</li> <li data-index="4">选项5</li> <li data-index="5">选项6</li> <li data-index="6">选项7</li> <li data-index="7">选项8</li> <li data-index="8">选项9</li> </ul> </div> </div>
-
javascript
const boxEl = document.querySelector('.box') const optionsEl = document.querySelector('.options') const columnsEl = document.querySelector('.columns') const liFirstEl = document.querySelector('.options li:first-child') const boxHeight = boxEl.clientHeight const liHeight = liFirstEl.offsetHeight const liAllEl = optionsEl.querySelectorAll('li') const liCount = liAllEl.length const boxContainsLi = Math.floor(boxHeight / liHeight) let startY = 0 let endY = 0 let currentY = 0 // 当前Y轴移动的位置 let isTouch = false // 是否是滑动事件 const initPositionY = 0 // 进入页面时Y轴开始的位置 let beginPositionY = initPositionY // 每次滚动后Y轴开始的位置 const maxTranslateY = liHeight / 2 // Y轴最大移动距离(向下移动) const minTranslateY = liCount * liHeight > boxHeight ? -(liCount * liHeight - boxHeight + liHeight / 2) : -liHeight / 2 // Y轴最小移动距离(向上移动) ;(function () { // 初始化页面信息 optionsEl.setAttribute( 'style', `transition-duration: 0ms; transition-property: all;` ) setTransform(beginPositionY, optionsEl) })() // 设置元素在Y轴的移动距离 function setTransform(value, el) { el.style.transform = `translate3d(0px, ${value}px, 0px)` } // 获取元素在Y轴的移动距离 function getTranslateY(el) { const translateStr = el.style.transform const valueStr = translateStr.match(/\(([^)]*)\)/) const valueArr = valueStr[1].split(',') return Number(valueArr[1].replace('px', '')) } // 给元素设置动画 function setTransitionDuration(value, el) { el.style.transitionDuration = `${value}ms` el.style.transitionProperty = value === 0 ? 'none' : 'all' } // 重置动画 function setTransitionNone(el) { setTimeout(() => { setTransitionDuration(0, el) }, 300) } // 设置 class function setClassName(index, elList) { elList[index].classList.toggle('selected') } // 阻止父元素的滑动 columnsEl.addEventListener( 'touchmove', function (e) { e.preventDefault() }, false ) // 父元素代理子元素点击事件 columnsEl.addEventListener( 'click', function (e) { console.log('click 事件触发') const target = e.target let index = 0 // 方案一:遍历所有 li 得到 index // for (let i = 0; i < liCount; i++) { // if (liAllEl[i] === target) { // index = i // break // } // } // 方案二: 根据 data-index 获取当前 li 的index index = target.dataset.index if (liAllEl[index].hasAttribute('disabled')) { return false } setClassName(index, liAllEl) }, false ) // 开始滑动 optionsEl.addEventListener( 'touchstart', function (e) { console.log('touchstart 事件触发') startY = event.targetTouches[0].pageY }, false ) // 滑动中 optionsEl.addEventListener( 'touchmove', function (e) { endY = event.targetTouches[0].pageY let moveY = endY - startY if (moveY !== 0) { isTouch = true } console.log('touchmove 事件触发') currentY = beginPositionY + moveY // Y 轴位置大于最大位置 if (currentY > maxTranslateY) { currentY = maxTranslateY } else if (currentY < minTranslateY) { currentY = minTranslateY } setTransform(currentY, optionsEl) }, false ) function touchend(e) { if (!isTouch) { return false } console.log('touchend 事件触发') setTransitionDuration(200, optionsEl) // 超过头部与底部位置 if (currentY >= maxTranslateY) { currentY = initPositionY } else if (currentY <= minTranslateY) { currentY = currentY + liHeight / 2 } else { let index // 根据不同的方向,回正所要选中的选项 if (endY - startY > 0) { // 向下滚动 index = Math.floor((initPositionY - currentY) / liHeight) } else { // 向上滚动 index = Math.ceil((initPositionY - currentY) / liHeight) } index = index <= 0 ? 0 : index >= liCount - boxContainsLi ? liCount - boxContainsLi : index currentY = initPositionY - liHeight * index isTouch = false } setTransform(currentY, optionsEl) beginPositionY = currentY setTransitionNone(optionsEl) } // 滑动结束 optionsEl.addEventListener('touchend', touchend, false) // 滑动取消 optionsEl.addEventListener( 'touchcancel', (evt) => { evt.preventDefault() console.log('touchcancel 事件触发') touchend(evt) }, false )