Xây dựng Masonry Layout từ trái sang phải bằng CSS Grid

Việc xây dựng bố cục trang web dạng masonry bằng Css Grid Layout có thể nói là một cách làm khá hay. Nếu như trước đây, tôi đã từng đề cập tới việc xây dựng bố cục web bằng cách sử dụng thuộc tính float và flexbox trong css thì cách làm mà tôi sắp đề cập đến sẽ hoàn toàn khác biệt và tiện lợi hơn.

Css Grid có gì đặc biệt?

Một vấn đề mà chúng ta thường gặp phải khi thiết kế giao diện website dạng masonry đó là việc dàn các phần tử từ trái sang phải. Css Grid sẽ giúp bạn giải quyết vấn đề đó một cách dễ dàng, xét cho cùng thì việc xây dựng layout bằng Css Grid cũng khá giống với việc áp dụng thuộc tính flexbox trong css, tuy nhiên Css Grid có nhiều ưu điểm và tiện lợi hơn. Ngoài ra, việc kết hợp với JS sẽ giúp chúng ta tránh gặp phải những rắc rối liên quan đến kích thước.

Khái niệm

Có thể nói CSS Grid là cách làm tốt nhất hiện nay để tạo ra những layout website dạng grid đẹp, độc đáo, giúp việc xây dựng grid trở nên dễ dàng hơn bao giờ hết. CSS Grid cũng được hỗ trợ bởi hầu hết các trình duyệt phổ biến hiện nay, như Apple Safari, Google Chrome, Mozilla Firefox hay Microsoft Edge.

masonry-css-grid

Sau đây tôi sẽ chia sẻ nhanh cách sử dụng Css grid layout cho những ai mới bắt đầu.

– Thiết lập giá trị cho thuộc tính display là grid cho vùng container mà bàn bạn muốn thực hiện
– Thiết lập giá trị phù hợp cho 2 thuộc tính grid-template-columns và grid-template-rows để chia grid thành các cột và các hàng

Hãy xem qua ví dụ sau đây khi muốn xây dựng một Grid có khoảng cách giữa các cột và hàng

.masonry {
display: grid;
grid-gap: 2em; /* [1] tạo khoảng cách giữa cột và hàng */
grid-template-columns: repeat( auto-fill, minmax( 200px, 1fr ) ); /* [2] Làm cho kích thước của cột phù hợp với các viewport */
grid-auto-rows: 250px; /* [3] Thiết lập chiều cao cho implicit grid row */
}

Giải thích

1. Một Grid cần phải có khoảng cách giữa các cột và hàng, điều này sẽ được thực hiện một cách dễ dàng bằng thuộc tính grid-gap

2. Kích thước của các cột phải linh hoạt và phù hợp với kích cỡ của các loại màn hình. Vì vậy, chúng ta sẽ sử dụng giá trị repeat() và minmax() trong css. Vì dụ ở trên cho thấy các cột sẽ có chiều rộng tối thiểu là 200px và tối đa là 1fr. Grid sẽ được lấp đầy bởi các cột một cách tự động

3. Chúng ta chưa thiết lập thuộc tính rõ ràng cho các hàng trong ví dụ ở trên. Vì vậy, trong trường hợp này, mọi hàng implicit grid row phải có chiều cao là 250px.

Kích thước của các track có thể được tùy chỉnh thông qua thuộc tính grid-auto-rows. Chúng ta cần thiết lập nó vì sau này sẽ sử dụng chúng để căn chỉnh kích thước của các cột để tạo thành bố cục dạng masonry layout

Implicit tracks

Implicit tracks sẽ được tự động tạo ra bởi grid khi không có các đường explicit rõ ràng nào được thêm vào cho các hàng hoặc các cột.

Mỗi một track về cơ bản là tổng của grid-gap và kích thước của Implicit tracks được tạo ra tự động khi không được chỉ định.

Tạo masonry layout

Bây giờ chúng ta sẽ thiết lập chiều cao cho các Implicit row tracks. Chúng ta có thể thiết lập các item có chiều cao là 1, 2, hay bất kì số hàng mà chúng ta muốn:

.col--2x {
grid-row-end: span 2;
}
.col--3x {
grid-row-end: span 3;
}

Khi bạn thiết lập giá trị span 2 cho grid-row-end có nghĩa là muốn item đó bằng 2 lần kích thước của implicit row-track. Và đối với giá trị là span 3 cũng tương tự như vậy..

Đây là kết quả chúng ta có được khi thiết lập thuộc tính grid-row-end như ví dụ ở trên:

xay dung bo cuc web bang css grid

Như vậy thứ tự từ trái sang phải là vấn đề luôn gây khó khăn cho chúng ta khi xây dựng một layout web đã được giải quyết một cách dễ dàng. 

Tuy nhiên, một vấn đề nảy sinh đó là khi bạn có nhiều item, ví dụ như khi bạn có 50 item và mỗi một item lại có kích thước nội dung khác nhau và chúng ta không thể thiết lập thủ công các giá trị kích thước, khoảng cách cho từng item đó.

CSS Grid Layout + JavaScript = Perfect Masonry

Hãy xem qua layout sau đây css grid layout Thông thường, grid sẽ thiết lập lại chiều cao của mỗi item, vì vậy chúng ta cần phải sử dụng thuộc tính grid-row-end để tùy chỉnh lại kích thước của các item đó. Và khi đó, chúng ta cần sử dụng javaScript để tính toán kích thước của mỗi item sao cho phù hợp. Chúng ta đều thấy rằng Spanning là kết quả của một giá trị nào đó với kích thước của implicit row-tracks. Những gì chúng ta cần làm bây giờ là xác định con số phù hợp cho mỗi item.
.masonry {
display: grid;
grid-gap: 1em;
grid-template-columns: repeat(auto-fill, minmax(200px,1fr));
grid-auto-rows: 0;
}
Tôi đã thiết lập kích thước của implicit row-track là 0. Điều này có nghĩa là chúng ta đã có grid-gap Đây là giả thuyết của tôi để có được số span phù hợp cho mỗi thành phần: Với G là row-gap, R là kích thước của implicit row-tracks và H là chiều cao của nội dung trong item thì chiều cao thực (H1) của item đó là: H1 = H + G Và kích thước thực của mỗi track(T) đó là: T = G + R Bây giờ, số span phù hợp cho bất kì item nào cũng có thể được áp dụng bằng công thức: S = H1 / T

Thiết lập kích thước phù hợp cho bất kì item nào

Sau đây, hãy xem qua hàm tính toán kích thước phù hợp cho bất kì item nào bằng đoạn code sau đây:.
/**
* Thiết lập khoảng spanning phù hợp cho bất kì item
* Nhận các thuộc tính mà chúng ta đã thiết lập cho khối layout, tính toán chiều cao hoặc khoảng span cho bất kì item nào của khối layout dựa trên chiều cao của phần chứa nội dung, khoảng cách(hàng) của grid và kích thước của implicit row tracks.

* @param item Object A brick/tile/cell inside the masonry
*/
function resizeMasonryItem(item){
/* Nhận đối tượng grid, khoảng cách các row và kích thước của implicit rows */
var grid = document.getElementsByClassName('masonry')[0],
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap')),
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));

/*
* Spanning for any brick = S
* Grid's row-gap = G
* Size of grid's implicitly create row-track = R
* Height of item content = H
* Net height of the item = H1 = H + G
* Net height of the implicit row-track = T = G + R
* S = H1 / T
*/
var rowSpan = Math.ceil((item.querySelector('.masonry-content').getBoundingClientRect().height+rowGap)/(rowHeight+rowGap));

/* Set the spanning as calculated above (S) */
item.style.gridRowEnd = 'span '+rowSpan;
}

Áp dụng để xác định kích thước cho từng item

Bây giờ chúng ta có thể áp dụng cho tất cả các item để thiết lập kích thước phù hợp. Việc này sẽ được hỗ trợ bởi hàm resizeMasonryItem():
**
* Apply spanning to all the masonry items
*
* Loop through all the items and apply the spanning to them using
* `resizeMasonryItem()` function.
*
* @uses resizeMasonryItem
*/
function resizeAllMasonryItems(){
// Get all item class objects in one list
var allItems = document.getElementsByClassName('masonry-brick');

/*
* Loop through the above list and execute the spanning function to
* each list-item (i.e. each masonry item)
*/
for(var i=0;i<allItems.length;i++){
resizeMasonryItem(allItems[i]);
}
}

Thay đổi kích thước khi hình ảnh tải xong

Nếu thành phần có chứa hình ảnh thì chiều cao nội dung sẽ thay đổi khi hình ảnh tải xong. Kết quả là masonry layout sẽ trở nên rất lộn xộn. Hàm JS dưới đây sẽ giải quyết vấn đề đó và thay đổi kích thước của các thành phần một lần nữa sao khi tất cả hình ảnh được tải xong. Tôi sử dụng ImagesLoaded để đơn giản hóa vấn đề này.
/**
* Resize the items when all the images inside the masonry grid
* finish loading. This will ensure that all the content inside our
* masonry items is visible.
*
* @uses ImagesLoaded
* @uses resizeMasonryItem
*/
function waitForImages() {
var allItems = document.getElementsByClassName('masonry-brick');
for(var i=0;i<allItems.length;i++){
imagesLoaded( allItems[i], function(instance) {
var item = instance.elements[0];
resizeMasonryItem(item);
} );
}
}

Kết nối các hàm cho các sự kiện phù hợp

Điều quan trọng là khai báo với DOM về thời điểm thực hiện cả 3 hàm javaScript mà tôi đã viết ở trên. Tất nhiên, chúng ta muốn mọi thành phần được căn chỉnh chính xác khi tải cửa sỏ trình duyệt tải. Nhưng còn việc thay đổi kích thước thì sao? Bạn có muốn lưới trông đẹp khi thay đổi kích thước cửa sổ không?

/* Resize all the grid items on the load and resize events */
var masonryEvents = ['load', 'resize'];
masonryEvents.forEach( function(event) {
window.addEventListener(event, resizeAllMasonryItems);
} );

Cuối cùng, chúng tôi thiết lập cho hàm waitForImages() mở để nhận ra hình ảnh bất kể sự kiện nào xảy ra
/* Do a resize once more when all the images finish loading */
waitForImages();

Lưu ý
Tuy grid rất thích hợp cho việc trình bày trang web nhưng nó vẫn tồn tại một số vấn đề sau:

Tốn kém. Vòng lặp qua từng item và thuộc tính cài đặt sẽ dẫn đến việc tốn khá nhiều tài nguyên

Khối masonry sẽ không thể được thực hiện nếu bạn thiết lập grid-gap là 0. Do đó, bạn phải sử dụng JS để tạo ra các grid-gaps nhỏ hon.

 

Nó sẽ gặp lỗi khi trình duyệt mất quá nhiều thời gian để áp dụng cài đặt. Bạn cần phải tiến hành giữ lưới hoặc phân trang.

 

Bài viết này sẽ được tôi cập nhật một cách thường xuyên. Và tôi có một bổ sung nho nhỏ đó là tôi sẽ cung cấp một công cụ để tạo, tùy chỉnh, demo và tải một số masonry layout với nhiều tùy chỉnh khác nhau. Sẽ rất hữu ích cho những người lập trình không chuyên:  https://w3bits.com/tools/masonry-generator/

 

Hi vọng bài viết này sẽ giúp ích cho các bạn trong việc xây dựng layout web. Hãy để lại bình luận ý kiến của mình nhé.

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest

Leave a Comment

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Đăng ký nhận tư vấn

Bấm gọi