戰(zhàn)指南:固定頂欄、滾動(dòng)響應(yīng)與深色模式適配的完整實(shí)現(xiàn))
Material UI AppBar 實(shí)戰(zhàn)指南固定頂欄、滾動(dòng)響應(yīng)與深色模式適配的完整實(shí)現(xiàn)【免費(fèi)下載鏈接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ma/material-ui本文基于 MUIMaterial UI官方文檔頁(yè) App Bar 組件文檔系統(tǒng)講解AppBar、Toolbar、Menu與useScrollTrigger的組合用法從基礎(chǔ)頂欄、菜單式/響應(yīng)式頂欄、搜索欄到positionfixed的內(nèi)容遮擋解決方案、滾動(dòng)顯隱與enableColorOnDark深色模式適配并結(jié)合倉(cāng)庫(kù)源碼剖析其樣式生成機(jī)制與 API 底層實(shí)現(xiàn)幫助你在 React 項(xiàng)目中直接落地符合 Material Design 規(guī)范的頂部導(dǎo)航欄。一、AppBar 的定位與角色根據(jù)文檔定義App Bar 用于展示與當(dāng)前屏幕相關(guān)的信息和操作The App Bar displays information and actions relating to the current screen. The top App bar provides content and actions related to the current screen. Its used for branding, screen titles, navigation, and actions. It can transform into a contextual action bar or be used as a navbar.也就是說(shuō)頂欄top App bar承擔(dān)品牌標(biāo)識(shí)、屏幕標(biāo)題、導(dǎo)航與操作按鈕四大職責(zé)既可以演化為上下文操作欄也可以直接當(dāng)作應(yīng)用級(jí)導(dǎo)航欄navbar使用此外還配有底部 App barbottom App bar用于移動(dòng)端動(dòng)作區(qū)。從源碼結(jié)構(gòu)看AppBar的實(shí)現(xiàn)位于 AppBar.js它并不是一個(gè)獨(dú)立的基礎(chǔ)組件而是基于Paper用styled二次封裝而來(lái)并固定了若干關(guān)鍵屬性根元素渲染為headercomponentheader語(yǔ)義化標(biāo)簽利于無(wú)障礙與 SEO默認(rèn)elevation{4}陰影深度 4接受 0–24默認(rèn)square不啟用圓角基礎(chǔ)樣式為display: flex; flex-direction: column; width: 100%; box-sizing: border-box; flex-shrink: 0其中box-sizing: border-box的注釋明確說(shuō)明是為了“防止 Modal 和 fixed 定位 AppBar 的 padding 問(wèn)題”。默認(rèn)屬性值color primary、enableColorOnDark false、position fixed也在同一文件的解構(gòu)賦值中可以直接確認(rèn)// packages/mui-material/src/AppBar/AppBar.js const { className, color primary, enableColorOnDark false, position fixed, ...other } props;二、基礎(chǔ) App Bar文檔的第一個(gè)示例是最小可用的頂欄左側(cè)菜單按鈕 中間標(biāo)題 右側(cè)操作按鈕這是絕大多數(shù) Web 應(yīng)用頂欄的起點(diǎn)import AppBar from mui/material/AppBar; import Box from mui/material/Box; import Toolbar from mui/material/Toolbar; import Typography from mui/material/Typography; import Button from mui/material/Button; import IconButton from mui/material/IconButton; import MenuIcon from mui/icons-material/Menu; export default function ButtonAppBar() { return ( Box sx{{ flexGrow: 1 }} AppBar positionstatic Toolbar IconButton sizelarge edgestart colorinherit aria-labelmenu sx{{ mr: 2 }} MenuIcon / /IconButton Typography varianth6 componentdiv sx{{ flexGrow: 1 }} News /Typography Button colorinheritLogin/Button /Toolbar /AppBar /Box ); }對(duì)應(yīng)完整實(shí)現(xiàn)可參考倉(cāng)庫(kù)中的演示文件 ButtonAppBar.js。這里值得注意的兩個(gè)細(xì)節(jié)positionstatic使頂欄跟隨文檔流不遮擋內(nèi)容——這是演示中最常用的取值Toolbar是 AppBar 的直接內(nèi)容容器它內(nèi)置theme.mixins.toolbar的最小高度約束并預(yù)留了固定頂欄所需的占位能力后文“固定定位”一節(jié)會(huì)用到。三、帶菜單的 App Bar當(dāng)頂欄右側(cè)需要用戶菜單Profile / My account 等時(shí)文檔給出的方案是AppBarToolbarMenu的組合。演示文件 MenuAppBar.js 的核心邏輯如下export default function MenuAppBar() { const [auth, setAuth] React.useState(true); const [anchorEl, setAnchorEl] React.useState(null); const handleMenu (event) { setAnchorEl(event.currentTarget); }; const handleClose () { setAnchorEl(null); }; return ( Box sx{{ flexGrow: 1 }} AppBar positionstatic Toolbar {/* 左側(cè)菜單按鈕與標(biāo)題同 ButtonAppBar省略 */} {auth ( div IconButton sizelarge aria-labelaccount of current user aria-controlsmenu-appbar aria-haspopuptrue onClick{handleMenu} colorinherit AccountCircle / /IconButton Menu idmenu-appbar anchorEl{anchorEl} anchorOrigin{{ vertical: top, horizontal: right }} keepMounted transformOrigin{{ vertical: top, horizontal: right }} open{Boolean(anchorEl)} onClose{handleClose} MenuItem onClick{handleClose}Profile/MenuItem MenuItem onClick{handleClose}My account/MenuItem /Menu /div )} /Toolbar /AppBar /Box ); }該模式的關(guān)鍵點(diǎn)用anchorElstate 控制Menu的錨點(diǎn)aria-controlsmenu-appbar與idmenu-appbar建立無(wú)障礙關(guān)聯(lián)keepMounted讓菜單在關(guān)閉后仍保留在 DOM 中避免首次打開(kāi)時(shí)的渲染延遲頂欄內(nèi)的按鈕統(tǒng)一使用colorinherit繼承 AppBar 根據(jù)colorprop 計(jì)算出的文字色源碼中對(duì)應(yīng)--AppBar-colorCSS 變量。四、響應(yīng)式 App BarResponsiveAppBar這是文檔中最完整的實(shí)戰(zhàn)模板桌面端展示橫向?qū)Ш桨粹o窄屏切換為漢堡菜單 下拉Menu右側(cè)保留頭像用戶菜單。完整實(shí)現(xiàn)見(jiàn) ResponsiveAppBar.js其響應(yīng)式骨架為AppBar positionstatic Container maxWidthxl Toolbar disableGutters {/* Logo桌面端顯示 */} AdbIcon sx{{ display: { xs: none, md: flex }, mr: 1 }} / Typography varianth6 noWrap componenta href#app-bar-with-responsive-menu sx{{ mr: 2, display: { xs: none, md: flex }, /* ... */ }} LOGO /Typography {/* 移動(dòng)端漢堡按鈕 下拉菜單 */} Box sx{{ flexGrow: 1, display: { xs: flex, md: none } }} IconButton sizelarge aria-labelaccount of current user aria-controlsmenu-appbar aria-haspopuptrue onClick{handleOpenNavMenu} colorinherit MenuIcon / /IconButton Menu idmenu-appbar anchorEl{anchorElNav} anchorOrigin{{ vertical: bottom, horizontal: left }} keepMounted transformOrigin{{ vertical: top, horizontal: left }} open{Boolean(anchorElNav)} onClose{handleCloseNavMenu} sx{{ display: { xs: block, md: none } }} {pages.map((page) ( MenuItem key{page} onClick{handleCloseNavMenu} Typography sx{{ textAlign: center }}{page}/Typography /MenuItem ))} /Menu /Box {/* 桌面端橫向?qū)Ш桨粹o */} Box sx{{ flexGrow: 1, display: { xs: none, md: flex } }} {pages.map((page) ( Button key{page} onClick{handleCloseNavMenu} sx{{ my: 2, color: white, display: block }} {page} /Button ))} /Box {/* 用戶頭像 設(shè)置菜單Tooltip Avatar Menu略 */} /Toolbar /Container /AppBar這個(gè)模板值得直接借鑒的要點(diǎn)Container maxWidthxl讓內(nèi)容居中且限制最大寬度配合Toolbar disableGutters消除工具欄內(nèi)邊距斷點(diǎn)切換完全依賴sx的響應(yīng)式對(duì)象語(yǔ)法display: { xs: ..., md: ... }兩套導(dǎo)航橫排 Button 與漢堡 Menu始終共存于 DOM、僅顯示與否不同避免條件渲染帶來(lái)的狀態(tài)丟失演示中的pages與settings定義為模塊級(jí)常量const pages [Products, Pricing, Blog]、const settings [Profile, Account, Dashboard, Logout]。五、搜索欄式 App Bar文檔提供兩種搜索布局演示文件分別為 SearchAppBar.js 與 PrimarySearchAppBar.jsSide searchbar次要搜索欄TextField以圖標(biāo)按鈕形式折疊在頂欄右側(cè)點(diǎn)擊后展開(kāi)為輸入框占據(jù)工具欄局部空間適合以導(dǎo)航為主、搜索為輔的頁(yè)面Primary searchbar主要搜索欄TextField占據(jù)工具欄主體區(qū)域flexGrow搜索是頁(yè)面第一入口適合搜索主導(dǎo)型應(yīng)用。兩者共同結(jié)構(gòu)都是AppBar Toolbar IconButton Collapse/InputBase或 TextField通過(guò) state 切換輸入框的顯隱。六、Drawer 響應(yīng)式布局與更多形態(tài)文檔還覆蓋了幾種典型形態(tài)演示文件位于同一目錄Responsive App bar with DrawerDrawerAppBar.js桌面端側(cè)邊Drawerpermanent/persistent 頂欄聯(lián)動(dòng)移動(dòng)端切換為可滑出的臨時(shí)抽屜是最完整的響應(yīng)式應(yīng)用外殼模板Dense僅桌面DenseAppBar.js通過(guò)Toolbar variantdense壓縮高度適合信息密度高的后臺(tái)Prominent高亮頂欄ProminentAppBar.js加高頂欄標(biāo)題下沉對(duì)齊底部其實(shí)現(xiàn)要點(diǎn)是一個(gè)StyledToolbarconst StyledToolbar styled(Toolbar)(({ theme }) ({ alignItems: flex-start, paddingTop: theme.spacing(1), paddingBottom: theme.spacing(2), // Override media queries injected by theme.mixins.toolbar media all: { minHeight: 128, // Material Design 規(guī)范的 prominent 高度 }, }));注意源碼注釋theme.mixins.toolbar會(huì)注入媒體查詢?cè)O(shè)置最小高度prominent 頂欄必須用media all覆蓋它才能生效——這是該演示中最容易踩坑的地方。Bottom App barBottomAppBar.js移動(dòng)端底部操作欄典型形態(tài)為居中懸浮的SpeedDialFAB 兩側(cè)圖標(biāo)按鈕文檔中該演示以 400px 寬的 iframe 呈現(xiàn)移動(dòng)端效果。七、position 屬性與固定頂欄的遮擋問(wèn)題7.1 五種定位取值position接受fixed默認(rèn)、absolute、sticky、static、relative五種取值。源碼 AppBar.js 中通過(guò) styled variants 為每種取值生成對(duì)應(yīng)類positionfixed/absolutetop: 0; left: auto; right: 0并疊加zIndex: theme.zIndex.appBarfixed 額外加了打印適配——media print時(shí)降級(jí)為absolute防止 AppBar 出現(xiàn)在每一頁(yè)打印輸出上positionsticky同樣top: 0zIndex.appBar但保留在文檔流內(nèi)positionstatic/relative僅設(shè)置定位類型。另有一個(gè)隱藏細(xì)節(jié)當(dāng)positionfixed時(shí)根節(jié)點(diǎn)會(huì)自動(dòng)附加mui-fixed類源碼注釋說(shuō)明它“對(duì) Dialog 有用”——Dialog內(nèi)部的 Portal 會(huì)讀取該類以正確對(duì)齊滾動(dòng)偏移這一點(diǎn)也有測(cè)試用例覆蓋見(jiàn)第九節(jié)。7.2 fixed 頂欄導(dǎo)致內(nèi)容被遮擋的 3 種解法文檔“Fixed placement”一節(jié)明確指出渲染positionfixed時(shí)元素尺寸不再影響頁(yè)面其余部分頁(yè)面內(nèi)容可能被頂欄遮擋。官方給出三種解決方案方案一改用positionsticky——頂欄保留在文檔流中天然不遮擋內(nèi)容。方案二渲染第二個(gè)空的Toolbar /作為占位——Toolbar自帶與頂欄等高的最小高度 mixinfunction App() { return ( React.Fragment AppBar positionfixed Toolbar{/* content */}/Toolbar /AppBar Toolbar / /React.Fragment ); }方案三使用theme.mixins.toolbar生成偏移元素const Offset styled(div)(({ theme }) theme.mixins.toolbar); function App() { return ( React.Fragment AppBar positionfixed Toolbar{/* content */}/Toolbar /AppBar Offset / /React.Fragment ); }文檔自帶演示如 HideAppBar.js正是采用方案二在滾動(dòng)內(nèi)容前放置一個(gè)空Toolbar /占位。八、color 屬性與深色模式enableColorOnDark8.1 color 的取值體系color默認(rèn)為primary支持default、inherit、primary、secondary、transparent以及error/info/success/warning等調(diào)色板色。從 AppBar.js 的樣式定義可以看到其實(shí)現(xiàn)機(jī)制每個(gè)非contrastText的 palette 鍵都會(huì)生成一個(gè) variant把palette[color].main與palette[color].contrastText寫(xiě)入--AppBar-background/--AppBar-color兩個(gè) CSS 變量即背景與文字色自動(dòng)取調(diào)色板色及其對(duì)比色colordefault時(shí)背景為grey[100]深色模式下grey[900]文字色通過(guò)theme.palette.getContrastText(...)計(jì)算colorinherit時(shí)背景取自繼承的 Paper 背景文字色直接inheritcolortransparent時(shí)背景透明、文字色繼承且深色模式下顯式清除backgroundImage。8.2 深色模式下的行為與 enableColorOnDark文檔“Enable color on dark”一節(jié)說(shuō)明按照 Material Design 深色主題指南深色模式下colorprop 默認(rèn)不生效頂欄顯示為深色底而非 primary 藍(lán)。需要覆蓋該行為時(shí)將enableColorOnDark設(shè)為true。源碼中對(duì)應(yīng)的 variant 邏輯是// packages/mui-material/src/AppBar/AppBar.jsvariants 節(jié)選 { props: (props) props.enableColorOnDark true ![inherit, transparent].includes(props.color), style: { backgroundColor: var(--AppBar-background), color: var(--AppBar-color), }, },注意兩個(gè)邊界inherit與transparent永遠(yuǎn)不受enableColorOnDark影響而enableColorOnDark{false}默認(rèn)時(shí)深色模式會(huì)改用theme.vars.palette.AppBar.darkBg/darkColor變量作為優(yōu)先值源碼用一個(gè)joinVars工具函數(shù)把它們拼接成var(--a, var(--b))形式的 CSS 變量回退鏈。官方演示 EnableColorOnDarkAppBar.js 在同一深色主題下對(duì)比了兩個(gè)頂欄const darkTheme createTheme({ palette: { mode: dark, primary: { main: #1976d2 } }, }); ThemeProvider theme{darkTheme} AppBar positionstatic colorprimary enableColorOnDark {appBarLabel(enableColorOnDark)} /AppBar AppBar positionstatic colorprimary {appBarLabel(default)} /AppBar /ThemeProvider前者保留 primary 藍(lán)底后者回退為深色默認(rèn)底直觀展示了該 prop 的差異。九、滾動(dòng)響應(yīng)useScrollTrigger 鉤子文檔“Scrolling”一節(jié)介紹用useScrollTrigger()鉤子響應(yīng)滾動(dòng)并給出三個(gè)典型應(yīng)用演示效果演示文件Hide App bar向下滾動(dòng)時(shí)頂欄下滑隱藏讓出閱讀空間HideAppBar.jsElevate App bar滾動(dòng)后增加陰影提示用戶“已不在頁(yè)面頂部”ElevateAppBar.jsBack to top滾動(dòng)后出現(xiàn)懸浮按鈕一鍵回到頂部BackToTop.js9.1 API 參考繼承自文檔useScrollTrigger([options]) triggerArgumentsoptionsobject可選options.disableHysteresisbool可選默認(rèn)false。禁用磁滯hysteresis即判定trigger時(shí)忽略滾動(dòng)方向options.targetNode可選默認(rèn)window可傳入任意可滾動(dòng) DOM 節(jié)點(diǎn)options.thresholdnumber可選默認(rèn)100垂直滾動(dòng)嚴(yán)格越過(guò)該閾值exclusive時(shí)切換trigger值。Returnstriggerboolean——當(dāng)前滾動(dòng)位置是否滿足條件。文檔給出的最小示例import useScrollTrigger from mui/material/useScrollTrigger; function HideOnScroll(props) { const trigger useScrollTrigger(); return ( Slide in{!trigger} divHello/div /Slide ); }HideAppBar.js演示的完整封裝還展示了Slide的appear{false} directiondown用法并說(shuō)明演示因運(yùn)行在文檔站 iframe 中才需要手動(dòng)注入windowref你自己的項(xiàng)目中useScrollTrigger默認(rèn)監(jiān)聽(tīng)window無(wú)需設(shè)置 target。9.2 源碼實(shí)現(xiàn)剖析useScrollTrigger的實(shí)現(xiàn)位于 useScrollTrigger.js與 API 文檔一一對(duì)應(yīng)function defaultTrigger(store, options) { const { disableHysteresis false, threshold 100, target } options; const previous store.current; if (target) { // Get vertical scroll store.current target.pageYOffset ! undefined ? target.pageYOffset : target.scrollTop; } if (!disableHysteresis previous ! undefined) { if (store.current previous) { return false; // 向上滾動(dòng)時(shí)強(qiáng)制返回 false —— 這就是“磁滯” } } return store.current threshold; }可以推斷出兩個(gè)設(shè)計(jì)意圖磁滯機(jī)制默認(rèn)情況下只要發(fā)生“向上滾動(dòng)”store.current previous就立即返回false即“向上滾一點(diǎn)就恢復(fù)顯示、向下滾過(guò)閾值才隱藏”避免觸發(fā)器在閾值附近抖動(dòng)disableHysteresis: true則退化為純粹的scrollTop threshold判斷閾值是嚴(yán)格大于store.current threshold與文檔中“strictly crosses this threshold (exclusive)”的表述一致。鉤子主體通過(guò)useRef保存上一次滾動(dòng)值實(shí)現(xiàn)磁滯比較、useState保存 trigger并監(jiān)聽(tīng)scroll事件——注意監(jiān)聽(tīng)器使用{ passive: true }且target為nullSSR 場(chǎng)景下window不存在時(shí)會(huì)直接setTrigger(false)兜底保證服務(wù)端首屏渲染安全。十、類名系統(tǒng)與樣式定制AppBar 的 utility class 定義在 appBarClasses.tsclassesprop 可覆蓋以下插槽定位類root、positionFixed、positionAbsolute、positionSticky、positionStatic、positionRelative顏色類colorDefault、colorPrimary、colorSecondary、colorInherit、colorTransparent、colorError、colorInfo、colorSuccess、colorWarning。類名由generateUtilityClasses(MuiAppBar, [...])生成因此默認(rèn)類名形如MuiAppBar-root、MuiAppBar-colorPrimary。overridesResolver按root → position* → color*的順序合并主題覆蓋樣式意味著在components.MuiAppBar.styleOverrides中定義的positionFixed/colorPrimary會(huì)精確命中對(duì)應(yīng) prop 組合。十一、測(cè)試用例中的行為佐證AppBar.test.js 對(duì)文檔描述的默認(rèn)行為做了自動(dòng)化驗(yàn)證可作為實(shí)現(xiàn)事實(shí)的交叉印證默認(rèn)渲染同時(shí)具備root與colorPrimary類且不含colorSecondary對(duì)應(yīng)color默認(rèn)primarypositionfixed時(shí)自動(dòng)附加mui-fixed類should add a .mui-fixed class瀏覽器環(huán)境下colorinherit時(shí)背景繼承palette.background.paper且ThemeProvider與CssVarsProvider兩套樣式機(jī)制下行為一致describeConformance以Paper作為inheritComponent進(jìn)一步證實(shí) AppBar 繼承 Paper 的elevation、square等能力。十二、小結(jié)選型路徑靜態(tài)演示/文檔站用positionstatic常駐頂欄用fixed并搭配空Toolbar /或theme.mixins.toolbar占位需要保留文檔流語(yǔ)義時(shí)優(yōu)先sticky響應(yīng)式外殼優(yōu)先參考ResponsiveAppBarDrawerAppBar兩個(gè)官方模板斷點(diǎn)切換用sx響應(yīng)式對(duì)象實(shí)現(xiàn)深色主題下默認(rèn)忽略color這是 Material Design 規(guī)范的有意為之需要品牌色時(shí)顯式加enableColorOnDark對(duì)inherit/transparent無(wú)效滾動(dòng)交互統(tǒng)一走useScrollTrigger理解其磁滯機(jī)制向上滾動(dòng)立即復(fù)位、threshold默認(rèn) 100px、strictly greater than判定有助于避免“隱藏/顯示抖動(dòng)”類問(wèn)題。以上所有演示源碼集中在 docs/data/material/components/app-bar/ 目錄組件實(shí)現(xiàn)與測(cè)試位于 packages/mui-material/src/AppBar/可按需對(duì)照閱讀。【免費(fèi)下載鏈接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ma/material-ui創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考