在移动端的滚动屏效果里面,肯德基的效果确实不错。原生的是用vue写的,这里我用react强行撸了个效果细节几乎一致的。
技术栈
react + antd + better-scroll + react-lazyload
better-scroll:也许你并不陌生,是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,来自于黄轶老师。滚动场景在开发中经常遇见,有必要去掌握它。
我的项目效果展示:
左右联动,效果贼棒。滚动也是如丝般顺滑。

关于实现:
不知道原作是怎么实现的,这里我用react去实现它走了不少弯路。 这里简单的分析这个滚动屏效果的需求吧:
1. 点击左侧导航栏:
- 点击的块有active指示(即有背景图)
- 右侧菜单栏顶部标题更换。
- 右侧菜单栏把对于的商品区块置顶
- 左侧导航栏点击的该项也进行置顶(如果是最底部的那几个就不要动)
2. 右侧滚动商品项时,如果商品项的标题被右侧菜单栏顶部标题掩盖时
- 左侧导航栏对应的项目也对应置顶(如果是最底部的那几个就不动,切换active即可)
OK 需求分析完毕。
实现方案:
基本思想:
- 通过ref获取组件的TOP偏移量数组,然后获得的偏移量数据,可以通过Scroll组件中去调用scrollTo()这个API来达到置顶效果
componentDidMount() {
//获取左侧导航栏的top信息
let leftScrollYList = [];
let initLeftTop = this.leftRef.current.childNodes[0].getBoundingClientRect().top
for (let item of this.leftRef.current.childNodes) {
leftScrollYList.push(item.getBoundingClientRect().top - initLeftTop)
}
console.log('获取的左侧导航栏信息---------')
console.log(leftScrollYList)
//获取右侧菜单块的top信息
let rightScrollYList = [];
let initRightTop = this.rightRef.current.childNodes[0].getBoundingClientRect().top
console.log(initRightTop)
for (let item of this.rightRef.current.childNodes) {
rightScrollYList.push(item.getBoundingClientRect().top - initRightTop + this.BLOCKTITLEHEIGHT)
}
console.log('获取的右侧导航栏信息(处理后)---------')
rightScrollYList = rightScrollYList.slice(0, -1)
rightScrollYList[0] = rightScrollYList[0] - this.BLOCKTITLEHEIGHT;
console.log(rightScrollYList)
this.setState({ leftScrollYList, rightScrollYList })
}
- 然后就是根据功能分离函数
// 监听右侧滚轮 如果数值达到就改变leftCurrentIndex
onListenRightTop() {
if(this.rightChildRef.state.disableRightListen) {
return;
}else {
let currentIndex = this.rightChildRef.state.currentIndex;
if (currentIndex !== this.state.leftCurrentIndex) {
this.setState({ leftCurrentIndex: currentIndex }, () => {
this._syncBlockTitle();
this._shiftLeft()
})
}
}
}
// 让右侧商品标题与currentIndex同步
_syncBlockTitle() {
let { leftCurrentIndex, blockTitleList, currentBlockTitle } = this.state;
if (blockTitleList[leftCurrentIndex] !== currentBlockTitle) {
console.log(this.state.blockTitleList)
this.setState({ currentBlockTitle: blockTitleList[leftCurrentIndex] })
}
}
//左侧进行滚动
_shiftLeft() {
let length = this.leftRef.current.childNodes.length;
let lastBottom = this.leftRef.current.childNodes[length - 1].getBoundingClientRect().bottom;
let currentOffsetTop = this.leftRef.current.childNodes[this.state.leftCurrentIndex].getBoundingClientRect().top;
console.log('当前active元素的Top偏移移---------')
console.log(currentOffsetTop);
let leftScollY = this.state.leftScrollYList[this.state.leftCurrentIndex]
if (lastBottom > this.state.windowHeight || currentOffsetTop < 300) {
this.leftChildRef.scrollTo(leftScollY);
}
}
//右侧进行滚动
_shiftRight() {
console.log('右侧的top漂移-------------------');
let rightScollY = this.state.rightScrollYList[this.state.leftCurrentIndex]
this.rightChildRef.scrollTo(rightScollY)
}
clickLeft(event) {
//重构此方法 他的作用分离为只是获得当前元素的key,偏移等方法分离出来
event.preventDefault()
let leftCurrentIndex = event.currentTarget.dataset.lkey;
let currentBlockTitle = leftList[leftCurrentIndex].blockName;
this.rightChildRef.setState({disableRightListen: true},() => {
this.setState({
leftCurrentIndex,
currentBlockTitle,
}, () => {
this._shiftLeft();
this._shiftRight();
})
})
}
- 剩下的就是一些关于组件通信,你需要掌握子组件给父组件通信,父组件调用子组件方法等。
备注: 关于右侧滚轮监听,监听是放在Scroll组件里面的onScroll里面,实时监听,这样就产生了一个问题,当点击左侧导航栏的时候,右侧在滚动这样又将调用右侧滚轮监听函数然后执行。这样两者就会矛盾冲突,这里的解决办法是: 设置一个开关变量,如果点击左侧按钮时,开关打开,在右侧滚动结束时再把开关关闭,这样在开关打开时,右侧滚轮监听函数不执行。
结语:
最后,真挚的感谢你的阅读
项目地址: github.com/g00d-mornin…
better-scroll文档: ustbhuangyi.github.io/better-scro…