React實(shí)現(xiàn)菜單欄滾動(dòng)功能
簡(jiǎn)介
本文將會(huì)基于react實(shí)現(xiàn)滾動(dòng)菜單欄功能。
技術(shù)實(shí)現(xiàn)
實(shí)現(xiàn)效果

點(diǎn)擊菜單,內(nèi)容區(qū)域會(huì)自動(dòng)滾動(dòng)到對(duì)應(yīng)卡片。內(nèi)容區(qū)域滑動(dòng),指定菜單欄會(huì)被選中。
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);
/**
* 內(nèi)容引用
*/
const contentRef = useRef();
/**
* 當(dāng)左側(cè)菜單點(diǎn)擊時(shí)候
*/
const onMenuClick = (idx) => {
if (idx !== current) {
// 內(nèi)容自動(dòng)滾動(dòng)到對(duì)應(yīng)菜單位置
contentRef.current.scrollTop = height.slice(0, idx).reduce((a, b) => a + b, 0);
setCurrent(idx);
}
}
/**
* 計(jì)算右側(cè)商品類別卡片高度
*/
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)
/**
* 當(dāng)右側(cè)內(nèi)容滾動(dòng)時(shí)候
*/
const onContentScroll = () => {
const scrollTop = contentRef.current.scrollTop;
if (current < height.length - 1){
const nextIdx = current + 1;
// 計(jì)算下一個(gè)位置高度
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;
// 計(jì)算上一個(gè)位置高度
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;到此這篇關(guān)于React實(shí)現(xiàn)菜單欄滾動(dòng)的文章就介紹到這了,更多相關(guān)React 菜單欄滾動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react動(dòng)態(tài)路由的實(shí)現(xiàn)示例
React中動(dòng)態(tài)路由通過ReactRouter庫實(shí)現(xiàn),根據(jù)應(yīng)用狀態(tài)或用戶交互動(dòng)態(tài)顯示或隱藏組件,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
React配置多個(gè)代理實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求返回問題
這篇文章主要介紹了React之配置多個(gè)代理實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求返回問題,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
手動(dòng)用webpack搭建第一個(gè)ReactApp的示例
本篇文章主要介紹了手動(dòng)用webpack搭第一個(gè) ReactApp的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
react實(shí)現(xiàn)復(fù)選框全選和反選組件效果
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)復(fù)選框全選和反選組件效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
如何使用Redux Toolkit簡(jiǎn)化Redux
redux-toolkit是目前redux官方推薦的編寫redux邏輯的方法,針對(duì)redux的創(chuàng)建store繁瑣、樣板代碼太多、依賴外部庫等問題進(jìn)行了優(yōu)化,官方總結(jié)了四個(gè)特點(diǎn)是簡(jiǎn)易的/有想法的/強(qiáng)勁的/高效的,總結(jié)來看,就是更加的方便簡(jiǎn)單了2022-12-12
react 項(xiàng)目 中使用 Dllplugin 打包優(yōu)化技巧
在用 Webpack 打包的時(shí)候,對(duì)于一些不經(jīng)常更新的第三方庫,比如 react,lodash,vue 我們希望能和自己的代碼分離開,這篇文章主要介紹了react 項(xiàng)目 中 使用 Dllplugin 打包優(yōu)化,需要的朋友可以參考下2023-01-01
react源碼層深入刨析babel解析jsx實(shí)現(xiàn)
同作為MVVM框架,React相比于Vue來講,上手更需要JavaScript功底深厚一些,本系列將閱讀React相關(guān)源碼,從jsx -> VDom -> RDOM等一些列的過程,將會(huì)在本系列中一一講解2022-10-10

