查看原文
其他

Pinterest Masonry 瀑布流布局方案详解

码中人 码农真经 2023-12-25

封面

最近爬了一些漂亮的小姐姐的图片,做成一个小网站  妹子图-魔力美少女[1],http://meizitu.mzh.ren/ 专门孝敬给各位码农大哥。网站使用瀑布流布局,简单总结经验如下:

什么是瀑布流?

Pinterest Masonry 瀑布流布局是一种常见的网页布局方式,常见于图片类网站。看两个例子:

妹子图 - Bing images[2]

必应图片搜索

小红书[3]

小红书

它的特点是每个元素的宽度相同,但是高度不同,且元素之间的高度差异较大。大家厌烦了普通的网格布局,而 Pinterest Masonry 瀑布流让元素有错位的感觉,看起来更加有趣。

当然,这种布局现在也被广泛应用在各种网站上,也造成了审美疲劳。任何创意都有其适用范围,最好的布局还是因地制宜,不要盲目追求创意。

比如,图片类型的网站,依然非常适合 Pinterest Masonry 瀑布流布局。因为图片的大小不一,而且图片之间的高度差异较大,这种布局就非常适合。而视频类的网站,因为视频的长宽比一般固定,自然就也不需要此布局。

为什么叫瀑布流(无限滚动)?

老外把这种布局叫做 Masonry,是“彻砖”的意思,侧重于该布局的静态表现。

中文把这种布局叫做 “瀑布流”,侧重于强调该布局的动态效果。指网页向下滚动,数据自动加载,然后页面高度增长,也称作无限滚动(infinite scroll) 。


接下来介绍几种 Masonry 布局的几种实现方案,并陈述其利弊。

最省事的方案 columns

columns 是纯CSS方案,不需要JS,也不需要任何库。只需要在父元素上设置 column-count 属性,然后在子元素上设置 break-inside 属性即可(break-inside 也可以不用设置)。

columns:用于设置元素的列宽和列数。它是column-width和column-count的简写属性。

结构如下 (HTML):

<div class="container">
    <div class="item" style="height: 140px"></div>
    <div class="item" style="height: 190px"></div>
    <div class="item" style="height: 170px"></div>
    <div class="item" style="height: 120px"></div>
    <div class="item" style="height: 160px"></div>
    <div class="item" style="height: 180px"></div>
    <div class="item" style="height: 140px"></div>
    <div class="item" style="height: 150px"></div>
    <div class="item" style="height: 170px"></div>
    <div class="item" style="height: 170px"></div>
    <div class="item" style="height: 140px"></div>
    <div class="item" style="height: 190px"></div>
    <div class="item" style="height: 170px"></div>
</div>

CSS 如下:

.container {
    max-width600px;
    margin20px auto;
    columns4;
    gap20px;
    counter-reset: items;
}

.item {
    border-radius3px;
    background-color#a1cbfa;
    border1px solid #4290e2;
    color#fff;
    padding15px;
    margin-bottom10px;
    box-sizing: border-box;
     /*防止元素内部分离而导致出现在两列*/
    break-inside: avoid;
}

/* item排序 */
div.item::before {
    counter-increment: items;
    contentcounter(items);
}

效果如下:

css column 瀑布流效果

这种方式可以简单快速实现砖墙布局,几近完美。有两个瑕疵:

一个是子元素是从上到下排列,这个是下一个问题的源头。

一般而言,砖彻上去之后,理论上是不地动的,在上面添加新的砖,只会增加墙的高度,而不会重新排列旧的砖头的位置。而这种方式,添加新的元素,会导致旧的元素重新排列,这是不符合砖墙布局的特性的。

例如,上面的例子,新增加砖头之后,那么,旧的砖头,会重新排列,这是不符合砖墙布局的特性的。

css column 瀑布流的问题

原本第4、5 块砖头,是在第2列的,但是,当新增加砖头之后,第4、5块砖头,就会被移动到第1列,这是不符合现实情况,当然,网页世界也不用完全对应现实世界。但是,这种方式会导致浏览过的内容,反复出现在不同的位置,这是不符合用户的预期的。

Grid Masonry

这种方式是通过设置row的高度,来实现瀑布流布局的。原理如下:

.grid {
  display: grid;
  grid-template-columnsrepeat(auto-fill, minmax(250px1fr));
  grid-gap1rem;
}

横向的图片设置成short, 纵向的图片设置成tall

.short{
    grid-row:span 1;
}
.tall{
    grid-row:span 2;
}

这种方式,可以实现瀑布流布局的特性,但是,这种方式,需要手动计算每个砖的高度,然后设置row的高度,这样做,会导致砖墙布局的实现,和砖的高度,有很大的关系,如果砖的高度,发生了变化,那么,砖墙布局的实现,也会发生变化。

flexbox 方案

CSS masonry with flexbox, :nth-child(), and order | Tobias Ahlin[4]

flexbox 通过设置子元素的 order 属性,来实现瀑布流布局的。原理如下:

/* Render items as columns */
.container {
  display: flex;
  flex-flow: column wrap;
}

/* Re-order items into rows */
.item:nth-child(3n+1) { order1; }
.item:nth-child(3n+2) { order2; }
.item:nth-child(3n)   { order3; }

/* Force new columns */
.container::before,
.container::after {
  content"";
  flex-basis100%;
  width0;
  order2;
}

效果如下:

Flexbox 瀑布流布局效果

flex-flow 是 flex-direction 和 flex-wrap 组合的简写属性 。第一个指定的值为 flex-direction ,第二个指定的值为 flex-wrap.

这种方式非常的巧妙,而且让元素看上去是从左到右横向排列的(从左到右,符合预期)。这个方案算得上真正的完美,唯一的瑕疵就是代码量大。如果要做成响应式,适配不同的窗口大小,那css的代码量会更大一点。

JavaScript 方案

以上CSS的方案各有优缺点,也存在浏览器兼容性问题,要想真正实现 Masonry 布局,还得引入 JavaScript,推荐两实用的类库:

  • • Masonry[5]

  • • Infinite Scroll[6]

其原理原理[7] 大致为:

  • • 写一个分列的方法 generateMasonryGrid(columns_count,posts)

  • • 网页 resize 时再重新分列

const container = document.querySelector('.container');

function generateMasonryGrid(columns, posts){

    container.innerHTML = '';
    
    let columnWrappers = {};

    for(let i = 0; i < columns; i++){
        columnWrappers[`column${i}`] = [];
    }

    for(let i = 0; i < posts.length; i++){
        const column = i % columns;
        columnWrappers[`column${column}`].push(posts[i]);
    }

    for(let i = 0; i < columns; i++){
        let columnPosts = columnWrappers[`column${i}`];
        let div = document.createElement('div');
        div.classList.add('column');

        columnPosts.forEach(post => {
            let postDiv = document.createElement('div');
            postDiv.classList.add('post');
            let image = document.createElement('img');
            image.src = post.image;
            let hoverDiv = document.createElement('div');
            hoverDiv.classList.add('overlay');
            let title = document.createElement('h3');
            title.innerText = post.title;
            hoverDiv.appendChild(title);
    
            
            postDiv.append(image, hoverDiv)
            div.appendChild(postDiv) 
        });
        container.appendChild(div);
    }
}

let previousScreenSize = window.innerWidth;

window.addEventListener('resize', () => {
    imageIndex = 0;
    if(window.innerWidth < 600 && previousScreenSize >= 600){
        generateMasonryGrid(1, posts);
    }else if(window.innerWidth >= 600 && window.innerWidth < 1000 && (previousScreenSize < 600 || previousScreenSize >= 1000)){
        generateMasonryGrid(2, posts);

    }else if(window.innerWidth >= 1000 && previousScreenSize < 1000){
        generateMasonryGrid(4, posts)
    }
    previousScreenSize = window.innerWidth;

})

if(previousScreenSize < 600){
    generateMasonryGrid(1, posts)
}else if(previousScreenSize >= 600 && previousScreenSize < 1000){
    generateMasonryGrid(2, posts)
}else{
    generateMasonryGrid(4, posts)
}

我的方案

给码农的妹子图

我写的妹子图主要使用了columns的方案,这样简单省事。除了做滚动加载之外,我还在前面也添加了“加载更多”的按钮。同时创造性的数据前插,一定程度上让图片的展示更加凌乱。

useEffect(() => {
    if (!isFetching) return;
    let url = `/api/page/${page}`;
    setTimeout(() => {
        fetch(url)
            .then((res) => res.json())
            .then((data) => {
                setIsFetching(false);
                // 数据往前插
                setPosts([...data, ...posts])
            });
    }, 1000);

}, [isFetching, page, posts])

未来的方案

CSS Grid Layout Module Level 3[8]

目前 CSS 布局模块Level 3已经进入到 ED(Editor’s Draft)阶段,该规范为 grid 添加了一个名为 masonry 值,可以轻松的实现瀑布流。届时,浏览器布局将消耗更多的计算性能。

CSS Grid Masonry

参考资料

  • • CSS { In Real Life } | Masonry? In CSS?![9]

  • • Masonry[10]

  • • CSS Grid Layout Module Level 3[11]

  • • break-inside | CSS-Tricks - CSS-Tricks[12]

  • • CSS Multi-column Layout Module Level 1[13]

  • • Using multi-column layouts - CSS: Cascading Style Sheets | MDN[14]

  • • grid-auto-rows - CSS: Cascading Style Sheets | MDN[15]

  • • break-inside - CSS: Cascading Style Sheets | MDN[16]

  • • html - CSS-only masonry layout - Stack Overflow[17]

  • • CSS masonry with flexbox, :nth-child(), and order | Tobias Ahlin[18]

  • • Pinterest-like masonry layout with placed and spanning items[19]

  • • flex 布局的基本概念 - CSS(层叠样式表) | MDN[20]

往期推荐

欢迎关注我的公众号“码中人”,原创技术文章第一时间推送。

引用链接

[1] 妹子图-魔力美少女: http://meizitu.mzh.ren/
[2] 妹子图 - Bing images: https://www.bing.com/images/search?q=%E5%A6%B9%E5%AD%90%E5%9B%BE&qs=n&form=QBIR&sp=-1&pq=%E5%A6%B9%E5%AD%90%E5%9B%BE&sc=10-3&cvid=76204E25004B4273B0A715F5267195EC&ghsh=0&ghacc=0&first=1&tsc=ImageHoverTitle&cw=1177&ch=697
[3] 小红书: https://www.xiaohongshu.com/explore
[4] CSS masonry with flexbox, :nth-child(), and order | Tobias Ahlin: https://tobiasahlin.com/blog/masonry-with-css/
[5] Masonry: https://masonry.desandro.com/
[6] Infinite Scroll: https://infinite-scroll.com/
[7] 原理: https://github.com/conorbailey90/masonry-grid-layout/blob/main/app.js
[8] CSS Grid Layout Module Level 3: https://drafts.csswg.org/css-grid-3/
[9] CSS { In Real Life } | Masonry? In CSS?!: https://css-irl.info/masonry-in-css/
[10] Masonry: https://masonry.desandro.com/
[11] CSS Grid Layout Module Level 3: https://drafts.csswg.org/css-grid-3/
[12] break-inside | CSS-Tricks - CSS-Tricks: https://css-tricks.com/almanac/properties/b/break-inside/
[13] CSS Multi-column Layout Module Level 1: https://www.w3.org/TR/css-multicol-1/
[14] Using multi-column layouts - CSS: Cascading Style Sheets | MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Using_multi-column_layouts
[15] grid-auto-rows - CSS: Cascading Style Sheets | MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-auto-rows
[16] break-inside - CSS: Cascading Style Sheets | MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/break-inside
[17] html - CSS-only masonry layout - Stack Overflow: https://stackoverflow.com/questions/44377343/css-only-masonry-layout
[18] CSS masonry with flexbox, :nth-child(), and order | Tobias Ahlin: https://tobiasahlin.com/blog/masonry-with-css/
[19] Pinterest-like masonry layout with placed and spanning items: https://drafts.csswg.org/css-grid-3/examples/pinterest-with-span.html
[20] flex 布局的基本概念 - CSS(层叠样式表) | MDN: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox#%E7%AE%80%E5%86%99%E5%B1%9E%E6%80%A7_flex-flow


继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存