Giới thiệu
- Layout masonry là 1 bố cục dạng lưới dựa trên các cột, không giống như grid, nó không có chiều cao cố định. Tựu chung lại, masonry layout sẽ tối ưu hoá không gian sử dụng trong 1 trang web bằng cách giảm thiểu tất các khoảng trống giữa các element. Một ví dụ đơn giản và trực quan nhất là https://www.pinterest.com/ khoảng trống giữa các ảnh chỉ là phần phân chia giữa các ảnh mà thôi, không có không gian thừa
- virtualized là gì thì trong series bài viết của mình thì có khá nhiều bài viết liên quan, bạn có thể check lại nhé.
- Đã lâu lắm rồi, sau khi vào trang pinterest mình đã mong muốn có thể tạo được 1 layout như vậy, nếu bạn để ý, thì pinterest vừa sử dụng dạng layout masonry này, vừa sử dụng phương pháp virtualized, vừa sửa dụng phương pháp load more, cũng như những thuật toán tối ưu ảnh để tăng tốc độ load. Nhưng hôm nay mình chỉ tập trung vào 2 cái đầu là layout masonry và kết hợp nó với virtualized. Vì vậy ngày hôm nay mình sẽ sử dụng 1 thư viện gọi là masonic để hỗ trợ làm việc này tốt hơn.
Ví dụ
Để cho nhanh chóng, lần này ta lại sử dụng create-react-app để thao tác nhé:
create-react-app masonry_layout
Mình install thư viện masonic luôn nhé: npm i masonic
Ở trong file App.js mình tạo 1 danh sách các ảnh với 5000 ảnh:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">const</span> items <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> randomPhoto <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5000</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span> <span class="token keyword">const</span> randomHeight <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span> <span class="token keyword">const</span> randomWidth <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span> <span class="token keyword">const</span> imgUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://picsum.photos/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomWidth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">00/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomHeight<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">00?random=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomPhoto<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> i<span class="token operator">++</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Img-title-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> src<span class="token operator">:</span> imgUrl <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> |
Tiếp đó là component Masonry:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token operator"><</span>Masonry items<span class="token operator">=</span><span class="token punctuation">{</span>items<span class="token punctuation">}</span> <span class="token comment">// Adds 8px of space between the grid cells</span> columnGutter<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">8</span><span class="token punctuation">}</span> <span class="token comment">// Sets the minimum column width to 172px</span> columnWidth<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">300</span><span class="token punctuation">}</span> <span class="token comment">// Pre-renders 5 windows worth of content</span> overscanBy<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">5</span><span class="token punctuation">}</span> <span class="token comment">// This is the grid item component</span> render<span class="token operator">=</span><span class="token punctuation">{</span>ImageCard<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> |
Đây là file App.js đầy đủ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> Masonry <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"masonic"</span><span class="token punctuation">;</span> <span class="token keyword">import</span> ImageCard <span class="token keyword">from</span> <span class="token string">'./ImageCard'</span> <span class="token keyword">import</span> <span class="token string">'./App.css'</span> <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token keyword">const</span> <span class="token function-variable function">App</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> items <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token function">Array</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> randomPhoto <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5000</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span> <span class="token keyword">const</span> randomHeight <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span> <span class="token keyword">const</span> randomWidth <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span> <span class="token keyword">const</span> imgUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://picsum.photos/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomWidth<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">00/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomHeight<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">00?random=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>randomPhoto<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> i<span class="token operator">++</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Img-title-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>i<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> src<span class="token operator">:</span> imgUrl <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>main className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'container'</span><span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'masonic'</span><span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>Masonry items<span class="token operator">=</span><span class="token punctuation">{</span>items<span class="token punctuation">}</span> columnGutter<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">8</span><span class="token punctuation">}</span> <span class="token comment">// Set khoảng cách giữa các column</span> columnWidth<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">300</span><span class="token punctuation">}</span> <span class="token comment">// Set chiều rộng tối thiểu là 300px</span> overscanBy<span class="token operator">=</span><span class="token punctuation">{</span><span class="token number">5</span><span class="token punctuation">}</span> <span class="token comment">// Giá trị để render trước khi scroll tới</span> render<span class="token operator">=</span><span class="token punctuation">{</span>ImageCard<span class="token punctuation">}</span> <span class="token comment">// Grid item của component</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>main<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> App<span class="token punctuation">;</span> |
Sau nữa là components ImageCard dùng để render mỗi cell của layout masonry: file ImageCard.js
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">const</span> <span class="token function-variable function">ImageCard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> id<span class="token punctuation">,</span> name<span class="token punctuation">,</span> src <span class="token punctuation">}</span> <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'card'</span><span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>img className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token string">'img'</span><span class="token punctuation">}</span> alt<span class="token operator">=</span><span class="token string">"kitty"</span> src<span class="token operator">=</span><span class="token punctuation">{</span>src<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>span children<span class="token operator">=</span><span class="token punctuation">{</span>name<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> ImageCard |
Cuối cùng, mình có thêm 1 số css để layout dễ nhìn hơn ở file App.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <span class="token selector">.masonic</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 8px<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">max-width</span><span class="token punctuation">:</span> 960px<span class="token punctuation">;</span> <span class="token property">margin</span><span class="token punctuation">:</span> 163px auto 0<span class="token punctuation">;</span> <span class="token property">box-sizing</span><span class="token punctuation">:</span> border-box<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.container</span> <span class="token punctuation">{</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.card</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">flex-direction</span><span class="token punctuation">:</span> column<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 6px<span class="token punctuation">;</span> <span class="token property">justify-content</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> transform 100ms ease-in-out<span class="token punctuation">;</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">min-height</span><span class="token punctuation">:</span> 100px<span class="token punctuation">;</span> <span class="token property">overflow</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">.img</span> <span class="token punctuation">{</span> <span class="token property">width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token property">border-top-left-radius</span><span class="token punctuation">:</span> 6px<span class="token punctuation">;</span> <span class="token property">border-top-right-radius</span><span class="token punctuation">:</span> 6px<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> block<span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Và đây là kết quả:
Nếu bạn inspect element lên thì bạn đã có layout masonry cùng với phương pháp virtualized rồi nhé
Kết luận
Vậy là mình đã tạo cho mình 1 UI layout giống pinterest rồi, bạn hãy thử xem sao nhé, chúc các bạn thành công (yaoming)