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