模拟美团简易版左右联动

401 阅读2分钟

模拟美团简易版左右联动

第一种实现方式

tsx部分

import React, { Component, createRef } from 'react'
import './index.scss'
export default class index extends Component {
  state = {
    arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
    start: 0,
    rightStart: 0,
    leftTop: 0,
    rightTop: 0,
    leftArr: [],
    leftIndexArr: [],
    animate: 0,
    active: 0,
  }
  componentDidMount() {
    const { arr } = this.state
    let leftArr = arr.map((e) => e * 200 - 200)
    let leftIndexArr = arr.map((e) => e * 50)
    this.setState({ leftArr, leftIndexArr })
  }
  changeIndex = (i: number) => {
    const { leftIndexArr, leftArr } = this.state
    let y = -(leftIndexArr[i] - 300)
    y = y > 0 ? 0 : y < -83 ? -83 : y
    this.setState({ rightTop: -leftArr[i], animate: 1, active: i })
    this.setState({ leftTop: y })
  }
  render() {
    const { arr, start, leftTop, rightTop, rightStart, leftArr, animate, active } = this.state
    return (
      <div
        className='app'
        onTouchStart={(e) => {
          this.setState({ start: e.changedTouches[0].pageY })
        }}
        onTouchMove={(e) => {
          let move = rightStart + e.changedTouches[0].pageY - start
          move = move < -2800 ? -2800 : move > 0 ? 0 : move
          let active = leftArr.findIndex((e, i) => {
            return e + 200 > -move
          })
          this.changeIndex(active)
          this.setState({ rightTop: move, animate: 0 })
        }}
        onTouchEnd={(e) => this.setState({ rightStart: rightTop })}
      >
        <div className='tabs'>
          <div className='left'>
            <div className='leftBox' style={{ top: leftTop }}>
              {arr.map((tim, i) => {
                return (
                  <div
                    className={active === i ? 'leftItem active' : 'leftItem'}
                    onTouchStart={(e) => {
                      this.changeIndex(i)
                    }}
                  >
                    {tim}
                  </div>
                )
              })}
            </div>
          </div>
          {/* 右侧部分 */}
          <div className='right'>
            <div className='rightBox' style={{ top: rightTop, transition: animate ? '.3s' : '' }}>
              {arr.map((e) => {
                return <div className='rightItem'>{e}</div>
              })}
            </div>
          </div>
        </div>
      </div>
    )
  }
}

css部分

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.app {
  position: absolute;
  width: 100%;
  height: 100;
  .swiper {
    width: 100%;
    height: 300px;
    background-color: pink;
  }
  .tabs {
    position: relative;
    width: 100%;
    height: 667px;
    display: flex;
  }
  .left {
    width: 100px;
    height: 100%;
    background-color: blue;
    font-size: 15px;
    position: relative;
    overflow: hidden;
    .leftBox {
      position: absolute;
      width: 100px;
      transition: 0.3s;
      .leftItem {
        font-size: 15px;
        list-style: none;
        width: 100%;
        height: 50px;
        border: 1px solid #000;
      }
      .active {
        background-color: pink;
      }
    }
  }
  .right {
    flex: 1;
    height: 100%;
    background-color: yellow;
    overflow: hidden;
    position: relative;
    .rightBox {
      position: absolute;
      width: 100%;
      .rightItem {
        font-size: 30px;
        list-style: none;
        width: 100%;
        height: 200px;
        border: 1px solid #000;
      }
    }
  }
}

第二种实现方式

tsx部分

import React, { Component } from 'react'
import './index.scss'
export default class index extends Component {
  //定义状态
  state = {
    //定义当前选中索引
    y: 0,
    //生成列表的依赖数据
    arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    //存放右侧每一个模块的高度值
    topArr: [],
    //节流阀
    flag: 0,
  }
  //组件加载调用
  componentDidMount() {
    let topArr: number[] = []
    //获取右侧每一个模块的高度值
    ;(this.refs.right as HTMLDivElement).childNodes.forEach((e, i) => {
      topArr[i] = (e as HTMLDivElement).offsetTop
    })
    //设置到状态里
    this.setState({ topArr })
  }
  render() {
    //解构状态
    const { y, arr, topArr, flag } = this.state
    return (
      <div className='app'>
        {/* ref标记 */}
        <div className='left' ref={'left'}>
          {/* 渲染列表 */}
          {arr.map((tim, i) => {
            return (
              <div
                // 判断当前是否选中
                className={y === i ? 'active' : ''}
                //点击触发
                onTouchStart={(e) => {
                  //设置当前点击的索引,关闭节流阀
                  this.setState({ y: i, flag: 1 })
                  //设置右侧滚动高度
                  ;(this.refs.right as HTMLDivElement).scrollTop = topArr[i]
                  //计算左侧滚动距离
                  let max = (i - 5) * 60
                  //设置左侧滚动距离
                  ;(this.refs.left as HTMLDivElement).scrollTop = max
                }}
              >
                {tim}
              </div>
            )
          })}
        </div>
        <div
          className='right'
          ref={'right'}
          onScroll={(e) => {
            //节流阀判断是否执行滚动逻辑
            if (flag) return
            //拿到目前滚动高度
            let top = (this.refs.right as HTMLDivElement).scrollTop
            //计算当前滚动到的索引
            let i = topArr.findIndex((e) => e > top) - 1
            //如果发生变化则触发左侧选项变化和左侧滚动值变化
            if (i !== y) {
              //设置索引
              this.setState({ y: i < 0 ? arr.length - 1 : i })
              //计算左侧滚动的位置
              let max = (i - 5) * 60
              ;(this.refs.left as HTMLDivElement).scrollTop = max
            }
          }}
          //触摸右侧的时候关闭节流阀,打开右侧滚动逻辑
          onTouchMove={(e) => {
            this.setState({ flag: 0 })
          }}
        >
          {arr.map((tim, i) => {
            return <div>{tim}</div>
          })}
        </div>
      </div>
    )
  }
}


scss部分

.app {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  .left {
    width: 20vw;
    height: 100%;
    background-color: pink;
    overflow-y: scroll;
    scroll-behavior: smooth;
    div {
      height: 60px;
      text-align: center;
      line-height: 60px;
    }
    .active {
      background-color: green;
    }
  }
  .right {
    flex: 1;
    height: 100%;
    background-color: yellow;
    overflow-y: scroll;
    scroll-behavior: smooth;
    div {
      height: 150vh;
      text-align: center;
      border: 1px solid #000;
    }
  }
}