肯德基宅急送,你值得学习的滚动屏

590 阅读3分钟

在移动端的滚动屏效果里面,肯德基的效果确实不错。原生的是用vue写的,这里我用react强行撸了个效果细节几乎一致的。

技术栈


react + antd + better-scroll + react-lazyload

better-scroll:也许你并不陌生,是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件,来自于黄轶老师。滚动场景在开发中经常遇见,有必要去掌握它。

我的项目效果展示:


左右联动,效果贼棒。滚动也是如丝般顺滑。

关于实现:


不知道原作是怎么实现的,这里我用react去实现它走了不少弯路。 这里简单的分析这个滚动屏效果的需求吧:

1. 点击左侧导航栏:

  • 点击的块有active指示(即有背景图)
  • 右侧菜单栏顶部标题更换。
  • 右侧菜单栏把对于的商品区块置顶
  • 左侧导航栏点击的该项也进行置顶(如果是最底部的那几个就不要动)

2. 右侧滚动商品项时,如果商品项的标题被右侧菜单栏顶部标题掩盖时

  • 左侧导航栏对应的项目也对应置顶(如果是最底部的那几个就不动,切换active即可)

OK 需求分析完毕。

实现方案
基本思想:

  1. 通过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 })
    }
  1. 然后就是根据功能分离函数
// 监听右侧滚轮 如果数值达到就改变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();
            })
        })
    }
  1. 剩下的就是一些关于组件通信,你需要掌握子组件给父组件通信,父组件调用子组件方法等。

备注: 关于右侧滚轮监听,监听是放在Scroll组件里面的onScroll里面,实时监听,这样就产生了一个问题,当点击左侧导航栏的时候,右侧在滚动这样又将调用右侧滚轮监听函数然后执行。这样两者就会矛盾冲突,这里的解决办法是: 设置一个开关变量,如果点击左侧按钮时,开关打开,在右侧滚动结束时再把开关关闭,这样在开关打开时,右侧滚轮监听函数不执行。

结语:


最后,真挚的感谢你的阅读

项目地址: github.com/g00d-mornin…

better-scroll文档: ustbhuangyi.github.io/better-scro…