React - 实现菜单栏滚动

简介

本文将会基于react实现滚动菜单栏功能。

技术实现

实现效果

点击菜单,内容区域会自动滚动到对应卡片。内容区域滑动,指定菜单栏会被选中。

ScrollMenu.js
javascript 复制代码
import {useRef, useState} from "react";
import './ScrollMenu.css';

export const ScrollMenu = ({products}) => {
    // 获取 categoryProductMap
    const categoryProductMap = new Map();
    products.forEach(product => {
        const category = product.category;
        let categoryProductList = categoryProductMap.get(category);
        if (!categoryProductList) {
            categoryProductList = [];
        }
        categoryProductList.push(product);
        categoryProductMap.set(category, categoryProductList);
    });

    // 获取类别列表
    const categoryList = Array.from(categoryProductMap.keys());

    // 菜单选中索引
    const [current, setCurrent] = useState(0);

    /**
     * 内容引用
     */
    const contentRef = useRef();

    /**
     * 当左侧菜单点击时候
     */
    const onMenuClick = (idx) => {
        if (idx !== current) {
            // 内容自动滚动到对应菜单位置
            contentRef.current.scrollTop = height.slice(0, idx).reduce((a, b) => a + b, 0);
            setCurrent(idx);
        }
    }

    /**
     *  计算右侧商品类别卡片高度
     */
    const height = [];
    const itemHeight = 25;
    categoryList.forEach((category, index) => {
        var productCnt = categoryProductMap.get(category).length;
        height.push((productCnt + 1) * itemHeight); // 0.8 是header高度
    });

    console.log(height)
    /**
     * 当右侧内容滚动时候
     */
    const onContentScroll = () => {
        const scrollTop = contentRef.current.scrollTop;
        if (current < height.length - 1){
            const nextIdx = current + 1;
            // 计算下一个位置高度
            const nextHeight = height.slice(0, nextIdx).reduce((a, b) => a + b, 0);
            console.log('scrollTop', scrollTop, 'nextHeight', nextHeight, 'nextIdx', nextIdx)
            if (scrollTop >= nextHeight) {
                contentRef.current.scrollTop = nextHeight;
                setCurrent(nextIdx);
                return;
            }
        }
        if (current > 0) {
            const lastIdx = current - 1;
            // 计算上一个位置高度
            const lastHeight = height.slice(0, lastIdx).reduce((a, b) => a + b, 0);
            console.log('scrollTop', scrollTop, 'lastHeight', lastHeight, 'lastIdx', lastIdx)
            if (scrollTop <= lastHeight) {
                contentRef.current.scrollTop = lastHeight;
                setCurrent(lastIdx);
                return;
            }
        }

    }

    return (
        <div className='scroll-menu'>
            <div className='menu'>
                {
                    // 菜单列表
                    categoryList.map((category, index) => {
                        return (
                            <div className={"menu-item" + ((index === current )? '-active' : '')}
                                 key={`${index}`} id={`menu-item-${index}`}
                                 onClick={(event) => {
                                     onMenuClick(index)
                                 }}>
                                {category}
                            </div>
                        )
                    })
                }
            </div>
            <div className='content' ref={contentRef} onScroll={(event) => {
                onContentScroll()
            }}>
                {
                    categoryList.map((category, index) => {
                        // 获取类别商品
                        const productList = categoryProductMap.get(category);
                        return (
                            <div key={index}>
                                <div className='content-item-header' key={`${index}`}
                                     id={`content-item-${index}`} style={{
                                    height: itemHeight
                                }} >{category}</div>
                                {
                                    productList.map((product,idx) => {
                                        return <div className='content-item-product'style={{
                                            height: itemHeight
                                        }}  key={`${index}-${idx}`} >{product.name}</div>
                                    })
                                }
                            </div>
                        )
                    })
                }
            </div>
        </div>


    )
}
ScrollMenu.css
css 复制代码
.scroll-menu {
    display: flex;
    flex-direction: row;
    width: 300px;
    height: 100px;
}

.menu{
    width: 90px;
    height: 100px;
    display: flex;
    flex-direction: column;
}

.menu-item {
    text-align: center;
    vertical-align: middle;

}

.menu-item-active {
    text-align: center;
    vertical-align: middle;
    background-color: lightcoral;
}

.content {
    width: 210px;

    overflow: auto;
}

.content-item-header{
    text-align: left;
    vertical-align: top;
    background-color: lightblue;
}

.content-item-product{
    text-align: center;
    vertical-align: center;
    background-color: lightyellow;
}
App.js
javascript 复制代码
import './App.css';
import {ScrollMenu} from "./component/scroll-menu/ScrollMenu";

const App = ()=> {
    const products = [
        {
            category:'蔬菜',
            name:'辣椒'
        },
        {
            category:'蔬菜',
            name:'毛豆'
        },
        {
            category:'蔬菜',
            name:'芹菜'
        },
        {
            category:'蔬菜',
            name:'青菜'
        },
        {
            category:'水果',
            name:'苹果'
        },
        {
            category:'水果',
            name:'梨'
        },
        {
            category:'水果',
            name:'橘子'
        },   {
            category:'食物',
            name:'肉'
        },   {
            category:'食物',
            name:'罐頭'
        }
        ,   {
            category:'食物',
            name:'雞腿'
        }
    ];

    return (
        <ScrollMenu products={products}/>
    )
}



export default App;
相关推荐
ClareXi3 小时前
react项目通过http调用后端springboot服务最简单示例
spring boot·react.js·http
咔咔库奇14 小时前
react动态路由
前端·react.js·前端框架
yqcoder15 小时前
react 中 FC 模块作用
前端·react.js·前端框架
刘志辉16 小时前
react的创建与书写
前端·react.js·前端框架
奔跑草-19 小时前
【前端】深入浅出的React.js详解
前端·react.js·前端框架
小牛itbull20 小时前
ReactPress:深入解析技术方案设计与源码
javascript·react.js·reactpress
秃头女孩y20 小时前
【React】条件渲染——逻辑与&&运算符
前端·react.js·前端框架
小满zs21 小时前
React第十五章(useEffect)
前端·react.js
破浪前行·吴1 天前
使用@react-three/fiber,@mkkellogg/gaussian-splats-3d加载.splat,.ply,.ksplat文件
前端·react.js·three.js
咔咔库奇1 天前
react之了解jsx
前端·javascript·react.js