模拟美团简易版左右联动
第一种实现方式
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;
}
}
}