常见的移动端标题栏切换

559 阅读1分钟

今天要实现的效果是标题栏切换,使用JS写的。效果就是下面图片显示的,当点击某一个标题的时候,标题下面有一个线条会高亮,然后标题还会往中间移动,有效的防止栏后面标题被遮挡的问题。

包括掘金首页标题栏也是这种效果。

上代码!!!主要的JS有80几行,比较复杂的就是让标题往中间移动这个过度的动画效果。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    ul,
    li {
      list-style: none;
    }

    html,
    body {
      width: 100%;
      height: 100%;
    }

    ::-webkit-scrollbar {
      display: none;
      /* Chrome Safari */
    }

    .wrapper {
      position: relative;
      width: 100%;
      overflow-y: hidden;
      overflow-x: scroll;
    }

    .list {
      display: flex;
      flex-wrap: nowrap;
    }

    .item {
      text-align: center;
      line-height: 36px;
      padding: 0 12px;
      white-space: nowrap;
    }

    .line {
      position: absolute;
      text-align: center;
      left: 0;
      bottom: 0;
      width: 0;
      transform: translate3d(0, 0, 0);
      transition: transform 0.3s ease-out;
      display: none;
    }

    .inner {
      width: 20px;
      height: 2px;
      background: red;
      display: inline-block;
      vertical-align: bottom;
    }
  </style>
</head>

<body>
  <div class="wrapper" id="wrapper">
    <ul class="list" id="list">
      <li class="item" data-index="0">标题1</li>
      <li class="item" data-index="1">标题2</li>
      <li class="item" data-index="2">标题3</li>
      <li class="item" data-index="3">标题4</li>
      <li class="item" data-index="4">标题5</li>
      <li class="item" data-index="5">标题6</li>
      <li class="item" data-index="6">标题7</li>
      <li class="item" data-index="7">标题8</li>
      <li class="item" data-index="8">标题9</li>
      <li class="item" data-index="9">标题10</li>
      <li class="item" data-index="10">标题11</li>
    </ul>
    <div class="line" id="line">
      <span class="inner"></span>
    </div>
  </div>
  <script>
    const list = document.getElementsByClassName('item')
    const line = document.getElementById('line')
    const listWrapper = document.getElementById('list')
    const wrapper = document.getElementById('wrapper')
    const resut = []
    let allWidth = 0
    for (let i = 0; i < list.length; i++) {
      const element = list[i]
      allWidth += element.offsetWidth
      resut.push({
        width: element.offsetWidth,
        left: element.offsetLeft
      })
    }
    line.style.width = `${resut[0].width}px`
    line.style.display = 'block'
    let current = 0
    let timer = null

    listWrapper.onclick = function (e) {
      // 快速点击需要立即停止动画
      clearInterval(timer)
      const index = e.target.dataset.index
      const width = resut[index].width
      line.style.width = `${width}px`
      const left = resut[index].left
      line.style.transform = `translate3d(${left}px, 0, 0)`
      list[current].style.color = ''
      list[index].style.color = 'red'
      current = index
    }

    line.addEventListener('transitionend', function () {
      const elementLeft = Math.floor(list[current].getBoundingClientRect().left)
      const scrollLeft = wrapper.scrollLeft
      const middle = Math.floor(window.innerWidth / 2) - Math.floor(resut[current].width / 2)
      const minScroll = 0
      const maxScroll = allWidth - window.innerWidth
      let targetScrollLeft = scrollLeft + (elementLeft - middle)
      if (targetScrollLeft < minScroll) {
        targetScrollLeft = minScroll
      }
      if (targetScrollLeft > maxScroll) {
        targetScrollLeft = maxScroll
      }
      if (allWidth > window.innerWidth && scrollLeft !== targetScrollLeft) {
        animationScroll(targetScrollLeft)
      }
    }, false)

    function animationScroll(distance) {
      const minScroll = 0
      const maxScroll = allWidth - window.innerWidth
      const scrollLeft = wrapper.scrollLeft
      const step = Math.floor(Math.abs((scrollLeft - distance) / 200 * 20))
      let temp = 0
      timer = setInterval(() => {
        // 手动触发的滚动需要立即停止动画
        if (temp && wrapper.scrollLeft !== temp) {
          return clearInterval(timer)
        }
        if (distance > scrollLeft) {
          const current = wrapper.scrollLeft + step
          // 左移动
          if (current > distance || current >= maxScroll) {
            clearInterval(timer)
            wrapper.scrollLeft = distance
          } else {
            wrapper.scrollLeft = current
            temp = current
          }
        } else {
          const current = wrapper.scrollLeft - step
          // 右移动
          if (current < distance || current <= minScroll) {
            clearInterval(timer)
            wrapper.scrollLeft = distance
          } else {
            wrapper.scrollLeft = current
            temp = current
          }
        }
      }, 20);
    }

  </script>
</body>

</html>