简介
本文将会基于react实现滚动菜单栏功能。
技术实现
实现效果
点击菜单,内容区域会自动滚动到对应卡片。内容区域滑动,指定菜单栏会被选中。
ScrollMenu.js
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
.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
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;