輕質砌體解決方案
五月,我了解到Firefox在CSS Grid中添加了砌體佈局功能。砌體佈局是我一直想從頭開始實現的東西,但一直不知道從哪裡入手。因此,我自然地查看了演示,然後當我理解這個新的CSS特性是如何工作時,我靈光一閃。
目前,支持僅限於Firefox(而且,即使在那裡,也只有在啟用特定標誌的情況下才支持),但這仍然為我提供了一個JavaScript實現的起點,該實現可以涵蓋當前缺乏支持的瀏覽器。
Firefox在CSS中實現砌體佈局的方式是將grid-template-rows
(如示例所示)或grid-template-columns
設置為masonry
值。
我的方法是利用此功能來支持瀏覽器(再次強調,目前僅指Firefox),並為其餘瀏覽器創建JavaScript回退方案。讓我們看看如何使用圖像網格的特定案例來實現這一點。
首先,啟用標誌
為此,我們在Firefox中訪問about:config
並蒐索“masonry”。這將顯示layout.css.grid-template-masonry-value.enabled
標誌,我們可以通過雙擊將其值從false
(默認值)更改為true
來啟用它。
讓我們從一些標記開始
HTML結構如下所示:
<img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597525146.jpg" class="lazy" alt="A Lightweight Masonry Solution">
現在,讓我們應用一些樣式
首先,我們將頂級元素設置為CSS網格容器。接下來,我們為圖像定義最大寬度,例如10em
。我們還希望這些圖像縮小到網格內容框可用的任何空間,如果視口變得太窄而無法容納單個10em
列網格,則將實際設置的值為Min(10em, 100%)
。由於響應式設計如今非常重要,我們不使用固定列數,而是自動適應盡可能多的此寬度的列:
$w: Min(10em, 100%); .grid--masonry { display: grid; grid-template-columns: repeat(auto-fit, $w); > * { width: $w; } }
請注意,我們使用了Min()
而不是min()
,以避免Sass衝突。
好吧,這是一個網格!
不過,它不是一個非常漂亮的網格,所以讓我們強制其內容水平居中,然後添加網格間隙和填充,兩者都等於一個間距值( $s
)。我們還設置了一個背景,以便更容易查看。
$s: .5em; /* 砌體網格樣式*/ .grid--masonry { /* 與之前的樣式相同*/ justify-content: center; grid-gap: $s; padding: $s; } /* 美化樣式*/ html { background: #555; }
稍微美化了網格之後,我們轉向對網格項目(即圖像)進行同樣的操作。讓我們應用一個濾鏡,使它們看起來更統一一些,同時通過稍微圓角和陰影添加一些額外的風格。
img { border-radius: 4px; box-shadow: 2px 2px 5px rgba(#000, .7); filter: sepia(1); }
現在,對於支持砌體佈局的瀏覽器,我們只需要聲明它:
.grid--masonry { /* 與之前的樣式相同*/ grid-template-rows: masonry; }
雖然這在大多數瀏覽器中不起作用,但在啟用了前面解釋的標誌的Firefox中,它會產生預期的結果。
但是其他瀏覽器呢?這就是我們需要…
JavaScript回退方案
為了節省瀏覽器必須運行的JavaScript代碼,我們首先檢查頁面上是否存在任何.grid--masonry
元素,以及瀏覽器是否已理解並應用了grid-template-rows
的masonry
值。請注意,這是一種通用方法,假設我們頁面上可能有多個這樣的網格。
let grids = [...document.querySelectorAll('.grid--masonry')]; if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { console.log('糟糕,不支持砌體佈局?'); } else { console.log('太好了,無需操作!'); }
如果新的砌體功能不受支持,那麼我們將獲取每個砌體網格的行間隙和網格項目,然後設置列數(每個網格最初為0)。
let grids = [...document.querySelectorAll('.grid--masonry')]; if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { grids = grids.map(grid => ({ _el: grid, gap: parseFloat(getComputedStyle(grid).gridRowGap), items: [...grid.childNodes].filter(c => c.nodeType === 1), ncol: 0 })); grids.forEach(grid => console.log(`網格項目:${grid.items.length};網格間隙:${grid.gap}px`)); }
請注意,我們需要確保子節點是元素節點(這意味著它們的nodeType
為1)。否則,我們最終可能會在項目數組中得到由回車符組成的文本節點。
在繼續進行之前,我們必須確保頁面已加載並且元素仍在移動。一旦我們處理了這個問題,我們就獲取每個網格並讀取其當前的列數。如果這與我們已經擁有的值不同,那麼我們將更新舊值並重新排列網格項目。
if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { grids = grids.map(/* 與之前相同*/); function layout() { grids.forEach(grid => { /* 獲取調整大小/加載後的列數*/ let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length; if (grid.ncol !== ncol) { grid.ncol = ncol; console.log('重新排列網格項目'); } }); } addEventListener('load', e => { layout(); /* 初始加載*/ addEventListener('resize', layout, false); }, false); }
請注意,調用layout()
函數是我們需要在初始加載和調整大小時都執行的操作。
要重新排列網格項目,第一步是從所有項目中刪除頂部邊距(這可能是為了在當前調整大小之前實現砌體效果而設置為非零值)。
如果視口足夠窄,我們只有一列,那麼我們就完成了!
否則,我們將跳過前ncol
個項目,然後循環遍歷其餘項目。對於每個考慮的項目,我們計算上面項目的底部邊緣位置及其頂部邊緣的當前位置。這使我們能夠計算需要垂直移動多少,以便其頂部邊緣位於上面項目的底部邊緣下方一個網格間隙。
/* 如果列數已更改*/ if (grid.ncol !== ncol) { /* 更新列數*/ grid.ncol = ncol; /* 恢復初始定位,無邊距*/ grid.items.forEach(c => c.style.removeProperty('margin-top')); /* 如果我們有多於一列*/ if (grid.ncol > 1) { grid.items.slice(ncol).forEach((c, i) => { let prev_fin = grid.items[i].getBoundingClientRect().bottom, /* 上面項目的底部邊緣*/ curr_ini = c.getBoundingClientRect().top; /* 當前項目的頂部邊緣*/ c.style.marginTop = `${prev_fin grid.gap - curr_ini}px`; }); } }
現在我們有一個可工作的跨瀏覽器解決方案!
一些小的改進
更真實的結構
在現實世界中,我們更有可能將每個圖像包裝在一個鏈接中,鏈接到其全尺寸圖像,以便大圖像在燈箱中打開(或者我們將其作為回退導航到它)。
<a href="https://www.php.cn/link/849c1f472f609bb4a3bacafef177f541"> <img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/174364597550777.jpg" class="lazy" alt="A Lightweight Masonry Solution"> </a>
這意味著我們也需要稍微更改一下CSS。雖然我們不再需要顯式地設置網格項目的寬度——因為它們現在是鏈接——但我們需要設置align-self: start
,因為與圖像不同,它們默認會拉伸以覆蓋整個行高,這會擾亂我們的算法。
.grid--masonry > * { align-self: start; } img { display: block; /* 避免底部出現奇怪的額外空間*/ width: 100%; /* 與之前的樣式相同*/ }
使第一個元素跨越網格
我們還可以使第一個項目水平跨越整個網格(這意味著我們可能還應該限制其高度並確保圖像不會溢出或變形):
.grid--masonry > :first-child { grid-column: 1 / -1; max-height: 29vh; } img { max-height: inherit; object-fit: cover; /* 與之前的樣式相同*/ }
我們還需要在獲取網格項目列表時添加另一個篩選條件來排除此拉伸項目:
grids = grids.map(grid => ({ _el: grid, gap: parseFloat(getComputedStyle(grid).gridRowGap), items: [...grid.childNodes].filter(c => c.nodeType === 1 && getComputedStyle(c).gridColumnEnd !== -1 ), ncol: 0 }));
處理具有可變縱橫比的網格項目
假設我們想將此解決方案用於博客之類的用途。我們保留完全相同的JS和幾乎完全相同的砌體特定CSS——我們只更改列的最大寬度並刪除第一個項目的max-height
限制。
從下面的演示可以看出,我們的解決方案在這種情況下也能完美地工作,我們有一個博客文章網格:
您還可以調整視口大小以查看它在這種情況下是如何工作的。
但是,如果我們希望列的寬度有一定的靈活性,例如:
$w: minmax(Min(20em, 100%), 1fr);
那麼我們在調整大小時就會遇到問題:
網格項目寬度變化以及每個項目文本內容不同的事實相結合意味著,當超過某個閾值時,我們可能會獲得網格項目的不同文本行數(從而改變高度),但其他項目則不會。如果列數沒有改變,那麼垂直偏移量就不會重新計算,我們最終會得到重疊或更大的間隙。
為了解決這個問題,我們需要在至少一個項目的當前網格高度發生變化時重新計算偏移量。這意味著我們還需要測試當前網格中是否有超過零個項目改變了它們的高度。然後,我們需要在if
塊的末尾重置此值,以便我們下次不必不必要地重新排列項目。
if (grid.ncol !== ncol || grid.mod) { /* 與之前相同*/ grid.mod = 0; }
好的,但是我們如何更改這個grid.mod
值呢?我的第一個想法是使用ResizeObserver
:
if (grids.length && getComputedStyle(grids[0]).gridTemplateRows !== 'masonry') { let o = new ResizeObserver(entries => { entries.forEach(entry => { grids.find(grid => grid._el === entry.target.parentElement).mod = 1; }); }); /* 與之前相同*/ addEventListener('load', e => { /* 與之前相同*/ grids.forEach(grid => { grid.items.forEach(c => o.observe(c)); }); }, false); }
這確實可以在必要時重新排列網格項目,即使網格列數沒有改變也是如此。但它也使即使有那個if
條件也是沒有意義的!
這是因為它在至少一個項目的高度或寬度發生變化時將grid.mod
更改為1。項目的height會因為text reflow而改變,text reflow是由width改變引起的。但是width的改變每次我們調整視口大小時都會發生,並不一定會觸發height的改變。
這就是為什麼我最終決定存儲以前的項目高度並在調整大小時檢查它們是否已更改,以確定grid.mod
是否保持為0:
function layout() { grids.forEach(grid => { grid.items.forEach(c => { let new_h = c.getBoundingClientRect().height; if (new_h !== c.dataset.h) { c.dataset.h = new_h; grid.mod ; } }); /* 與之前相同*/ }); }
就是這樣!現在我們有一個不錯的輕量級解決方案。壓縮後的JavaScript不到800字節,而嚴格的砌體相關樣式不到300字節。
但是,但是,但是……
瀏覽器支持如何?
好吧, @supports
碰巧比這裡使用的任何較新的CSS特性都有更好的瀏覽器支持,因此我們可以將其中的好東西放在裡面,並為不支持的瀏覽器提供一個基本的非砌體網格。此版本一直向後兼容到IE9。
它可能看起來不一樣,但它看起來不錯,而且功能完善。支持瀏覽器並不意味著為其複制所有視覺效果。這意味著頁面可以工作,並且看起來不損壞或難看。
沒有JavaScript的情況如何?
好吧,我們只有在根元素具有我們通過JavaScript添加的js
類時才能應用花哨的樣式!否則,我們將獲得一個所有項目大小相同的基本網格。
以上是輕質砌體解決方案的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

您是否曾經在項目上需要一個倒計時計時器?對於這樣的東西,可以自然訪問插件,但實際上更多

在元素個數不固定的情況下如何通過CSS選擇第一個指定類名的子元素在處理HTML結構時,常常會遇到元素個數不�...

關於Flex佈局中紫色斜線區域的疑問在使用Flex佈局時,你可能會遇到一些令人困惑的現象,比如在開發者工具(d...

格子呢是一塊圖案布,通常與蘇格蘭有關,尤其是他們時尚的蘇格蘭語。在Tartanify.com上,我們收集了5,000多個格子呢
