現(xiàn)原理與 SSR 支持)
Material UI Lab Masonry 組件完全指南瀑布流布局的配置、實(shí)現(xiàn)原理與 SSR 支持【免費(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-uiMasonry 是 MUI Lab 中用于構(gòu)建“瀑布流”布局的組件它把寬度一致、高度不一的內(nèi)容塊按行順序排布每個(gè)新元素都會進(jìn)入當(dāng)前最矮的列從而最大化利用空間。本篇基于 MUI 官方文檔與mui/lab源碼完整覆蓋 Masonry 的基本用法、columns/spacing/sequential/default*等全部配置項(xiàng)并深入源碼剖析其基于 CSS Flexbox 負(fù)邊距與order重排的布局引擎、ResizeObserver/MutationObserver的響應(yīng)機(jī)制以及服務(wù)端渲染SSR模式的實(shí)現(xiàn)細(xì)節(jié)。一、Masonry 是什么布局規(guī)則與適用場景官方文檔 masonry.md 對 Masonry 的定義是Masonry lays out contents of varying dimensions as blocks of the same width and different height with configurable gaps.Masonry 把尺寸各異的組織內(nèi)容排列為寬度相同、高度不同、間隙可配置的塊。具體布局規(guī)則有三條等寬變高M(jìn)asonry 維護(hù)一組寬度一致、高度不同的內(nèi)容塊子元素可以是任意 React 元素包括div /和img /按行排序內(nèi)容按行row順序進(jìn)入布局。當(dāng)某一行已被指定的列數(shù)填滿后下一個(gè)元素開始新行最短列優(yōu)先新元素被添加到當(dāng)前最矮的列中以此優(yōu)化空間利用。組件實(shí)現(xiàn)位于 Masonry.js類型聲明位于 Masonry.d.ts測試位于 Masonry.test.js。它與另一個(gè)組件的分工值得注意文檔中明確提示Masonry是按行row排序子元素的如果你希望圖片按列column排序應(yīng)使用ImageList的masonry布局變體。二、基本用法最小可用示例BasicMasonry.tsx 展示了 Masonry 的最小形態(tài)import Box from mui/material/Box; import Paper from mui/material/Paper; import Masonry from mui/lab/Masonry; import { styled } from mui/material/styles; const heights [150, 30, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 30, 50, 80]; const Item styled(Paper)(({ theme }) ({ backgroundColor: #fff, ...theme.typography.body2, padding: theme.spacing(0.5), textAlign: center, color: (theme.vars || theme).palette.text.secondary, })); export default function BasicMasonry() { return ( Box sx{{ width: 500, minHeight: 393 }} Masonry columns{4} spacing{2} {heights.map((height, index) ( Item key{index} sx{{ height }} {index 1} /Item ))} /Masonry /Box ); }幾個(gè)要點(diǎn)Masonry是容器可接收任意數(shù)量的子元素children是必填屬性見 Masonry.d.ts 中children: NonNullableReact.ReactNode的聲明空 children 會觸發(fā) propTypes 警告測試 Masonry.test.js 中有專門用例驗(yàn)證每個(gè)子項(xiàng)的寬度由 Masonry 統(tǒng)一控制按columns均分容器寬度高度由子項(xiàng)內(nèi)容決定整個(gè)布局包裹在固定寬度 500px 的Box中便于演示列寬分配。三、圖片瀑布流Image MasonryImageMasonry.tsx 演示了 Masonry 在圖片墻場景的應(yīng)用每個(gè)子項(xiàng)是一個(gè)包含Label帶編號的Paper和img /的div三列排布圖片通過srcSet提供 2x 高清版本并開啟loadinglazy懶加載Masonry columns{3} spacing{2} {itemData.map((item, index) ( div key{index} Label{index 1}/Label img srcSet{${item.img}?w162autoformatdpr2 2x} src{${item.img}?w162autoformat} alt{item.title} loadinglazy style{{ borderBottomLeftRadius: 4, borderBottomRightRadius: 4, display: block, width: 100%, }} / /div ))} /Masonry圖片場景有一個(gè)源碼層面的細(xì)節(jié)在 Masonry.js 的handleResize中布局引擎會檢查每個(gè)子項(xiàng)內(nèi)的嵌套IMG節(jié)點(diǎn)如果發(fā)現(xiàn)某個(gè)圖片的clientHeight 0圖片尚未加載完成、沒有實(shí)際渲染高度整個(gè)重排會被標(biāo)記為skip等待圖片加載后再由ResizeObserver觸發(fā)重新計(jì)算。也就是說圖片異步加載完成會自動觸發(fā)一次瀑布流重排無需手動干預(yù)。四、變高項(xiàng)Items with Variable HeightMasonryWithVariableHeightItems.tsx 用可展開的Accordion最小高度各異作為子項(xiàng)說明 Masonry 對“動態(tài)高度”的支持Masonry columns{3} spacing{2} {heights.map((height, index) ( Paper key{index} StyledAccordion sx{{ minHeight: height }} AccordionSummary expandIcon{ExpandMoreIcon /} Typography componentspanAccordion {index 1}/Typography /AccordionSummary AccordionDetailsContents/AccordionDetails /StyledAccordion /Paper ))} /Masonry文檔特別強(qiáng)調(diào)為了滿足“新元素永遠(yuǎn)進(jìn)入最短列”的規(guī)則項(xiàng)目之間可能在不同列之間移動。從源碼結(jié)構(gòu)看這正是由 Masonry.test.js 中 “should re-compute the height of masonry when dimensions of any child change” 用例所驗(yàn)證的行為當(dāng)某個(gè)子項(xiàng)的高度從 20px 變?yōu)?10px 后容器總高度會隨之重算——因?yàn)槿魏巫禹?xiàng)尺寸變化都會通過ResizeObserver觸發(fā)一次完整重排見后文第五節(jié)原理分析。五、配置列數(shù)columns固定列數(shù)FixedColumns.tsx 演示columns{4}容器寬度被等分為 4 份每份再減去spacing得到子項(xiàng)實(shí)際寬度。響應(yīng)式列數(shù)columns接受 MUI 標(biāo)準(zhǔn)的響應(yīng)式值。ResponsiveColumns.tsx 中Masonry columns{{ xs: 3, sm: 4 }} spacing{2}即小屏 3 列、sm斷點(diǎn)及以上 4 列。從類型聲明看Masonry.d.tscolumns的類型是ResponsiveStyleValuenumber | string因此除了對象形式{ xs, sm, md, lg, xl }還支持?jǐn)?shù)組形式[3, sm, 4]。源碼印證在 Masonry.js 的getStyle函數(shù)中columns先經(jīng)unstable_resolveBreakpointValues解析為各斷點(diǎn)取值再由handleBreakpoints為每個(gè)斷點(diǎn)生成媒體查詢樣式核心公式是子項(xiàng)寬度width (100 / columnValue).toFixed(2) %; // 子項(xiàng)實(shí)際寬度calc(width% - spacing)測試文件 Masonry.test.js 的 “should generate correct responsive styles regardless of breakpoints order” 用例還驗(yàn)證了一個(gè)實(shí)用細(xì)節(jié)響應(yīng)式對象的鍵序不影響結(jié)果傳入{ sm: 5, md: 7, xs: 3 }與按斷點(diǎn)升序排列生成完全相同的媒體查詢樣式。六、配置間距spacing固定間距FixedSpacing.tsx 使用spacing{3}。這里有一個(gè)關(guān)鍵約定文檔原文傳入spacing的值會被乘以主題theme的spacing字段。即spacing{3}在默認(rèn)主題下spacing: (factor) 8 * factor px等效于 24px 的間隙。這一行為在源碼中由createUnarySpacing(theme)生成的transformer實(shí)現(xiàn)對數(shù)字值以及可解析為數(shù)字的字符串執(zhí)行g(shù)etValue(transformer, number)其余字符串如4px則原樣使用。響應(yīng)式間距ResponsiveSpacing.tsx 中Masonry columns{3} spacing{{ xs: 1, sm: 2, md: 3 }}同樣受ResponsiveStyleValue約束可傳對象或數(shù)組。間距的實(shí)現(xiàn)機(jī)制值得單獨(dú)說明。Masonry 并不使用 CSS gap而是采用“容器負(fù)邊距 子項(xiàng)四向半邊距”的經(jīng)典做法getStyle中生成{ margin: calc(0px - (${spacing} / 2)), // 容器抵消外溢的邊距 *: { margin: calc(${spacing} / 2) } // 每個(gè)子項(xiàng)四周 spacing/2 }這樣無論水平還是垂直方向任意兩個(gè)相鄰子項(xiàng)之間的凈距離都恰好是spacing。同時(shí)容器高度會在布局引擎測得maxColumnHeight最矮列之外最高的那一列總高后設(shè)置為maxColumnHeight spacing測試用例 “should apply correct default styles” 精確斷言了這三個(gè)值容器負(fù)邊距-spacing/2、子項(xiàng)邊距spacing/2、子項(xiàng)寬度width/columns - spacing。七、順序模式sequentialSequential.tsx 演示Masonry columns{4} spacing{2} defaultHeight{450} defaultColumns{4} defaultSpacing{1} sequential 開啟sequential后元素按從左到右的順序依次填入各列第 1、2、3、4 個(gè)元素分別在第 1~4 列第 5 個(gè)元素回到第 1 列而不是進(jìn)入當(dāng)前最短列。適合需要保持閱讀順序嚴(yán)格的場景。源碼印證Masonry.js 的handleResize中兩種策略是并列分支if (sequential) { columnHeights[nextOrder - 1] childHeight; child.style.order nextOrder; nextOrder 1; if (nextOrder currentNumberOfColumns) { nextOrder 1; } } else { const currentMinColumnIndex columnHeights.indexOf(Math.min(...columnHeights)); columnHeights[currentMinColumnIndex] childHeight; child.style.order currentMinColumnIndex 1; }測試用例 “should place children in sequential order”瀏覽器環(huán)境驗(yàn)證了 2 列 3 個(gè)子項(xiàng)時(shí)計(jì)算樣式 order 依次為1, 2, 1。八、服務(wù)端渲染SSRdefaultHeight/defaultColumns/defaultSpacingMasonry 的動態(tài)布局依賴瀏覽器 API讀取子項(xiàng)實(shí)際高度在服務(wù)端執(zhí)行時(shí)這些值不可用。為此組件提供三個(gè)僅用于 SSR 的 prop見 SSRMasonry.tsxMasonry columns{4} spacing{2} defaultHeight{450} defaultColumns{4} defaultSpacing{1} 文檔中的注意事項(xiàng)原文defaultHeight應(yīng)當(dāng)足夠大以容納所有行。另外需要注意在服務(wù)端渲染的情況下元素不會被添加到最短列。進(jìn)入 SSR 分支的條件Masonry.js L189-L196const isSSR !maxColumnHeight defaultHeight defaultColumns ! undefined defaultSpacing ! undefined;即首次服務(wù)端渲染尚未測得maxColumnHeight且三個(gè)default*prop 全部提供時(shí)啟用。SSR 分支生成的純 CSS 樣式為L52-L77容器高度固定為defaultHeightpx容器與子項(xiàng)使用defaultSpacing換算出的固定負(fù)/正半邊距每個(gè)子項(xiàng)寬度為calc(100/defaultColumns% - defaultSpacing)用nth-of-type(defaultColumns n i)選擇器為子項(xiàng)依次賦予order: 1..defaultColumns實(shí)現(xiàn)“按行左到右”的確定性列分配——這正是文檔所說“SSR 下不進(jìn)入最短列”的原因純 CSS 無法知道每項(xiàng)高度只能做靜態(tài)輪轉(zhuǎn)。瀏覽器端水合hydrate完成后handleResize會立即運(yùn)行用真實(shí)測量值接管布局SSR 靜態(tài)樣式隨之被動態(tài)order與容器高度覆蓋。測試用例 “should support server-side rendering” 對isSSR: true時(shí)的完整樣式對象含nth-of-type(4n1)→order: 1等規(guī)則做了精確斷言。九、布局引擎原理從源碼看 Masonry 如何工作結(jié)合 Masonry.js 全文Masonry 的運(yùn)行時(shí)機(jī)制可以歸納為四部分1. 靜態(tài)骨架Flexbox 容器getStyle生成的基礎(chǔ)容器樣式是{ width: 100%, display: flex, flexFlow: column wrap, // 縱向主軸 允許換行 alignContent: flex-start, boxSizing: border-box, }注意主軸方向是column每個(gè)“行”是 Flex 容器的一條主軸換行產(chǎn)生下一行。子項(xiàng)通過order屬性決定落入哪一列從而把二維瀑布流映射為“縱向 Flex order 重排”的一維問題。2. 防合并哨兵line breaks源碼 L353-L358 揭示了一個(gè)精妙設(shè)計(jì)——組件末尾渲染一組透明哨兵元素// A line break is added to the end of each column to prevent columns from merging. const lineBreaks new Array(numberOfLineBreaks).fill().map((_, index) ( span key{index} contenteditable="false">【免費(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),僅供參考