移动端滑动(touch)选项并实现多选效果

  • 移动端滑动选项实现多选效果
  • 通过 touchstarttouchmovetouchendtouchcancel 事件实现
  • 通过父元素代理事件的方式实现子组件点击选中选项
  • 如果选项添加 disabled 属性将不会被选中
  • 移动端拖拽 .box.options 元素时,是有拖拽效果的,去除拖拽效果有两种方式:
    ① 用 css pointer-events: none; 阻止拖拽,.box 元素使用的是这种方式
    ② 用 js event.preventDefault() 阻止元素的事件, .options 元素使用的是这种方式
  • 如果需要通过鼠标滚轮来切换选项,可以使用 wheel 来实现,本案例没有实现
  • touchcancel 事件一直没有办法触发,所以没有验证效果

移动端滑动(touch)选项并实现多选效果_第1张图片

  • 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
    )
    

你可能感兴趣的:(css,javascript,html)