實現瀑布流布局,就這幾行代碼?

2022年06月22日08:31:02 熱門 1338

大家好,我是Echa 哥,瀑布流布局我記得前年有跟大家介紹用純CSS方案實現過,不知道粉絲們還記得嗎? 不記得沒關係,請見實現瀑布流布局,就這幾行代碼? - 天天要聞

前言

瀑布流布局是一種比較流行的頁面布局方式,表現為參差不齊的多欄卡片。跟網格布局相比,顯得更靈動,更具藝術氣息。

實現瀑布流布局的方式有多種,比如multi-column布局,grid布局,flex 布局等。但是這些實現方式都有各自的局限性,代碼也略複雜。

其實,有個最原始、最簡單,也是兼容性最好的實現方式,那就是使用絕對定位。瀑布流布局的元素是一些等寬不等高的卡片,只要根據元素的實際寬高計算出自己的坐標位置就行了。

要計算坐標自然要用到 JavaScript,這就不是純 CSS 方案,對某些前端來講顯得不那麼純粹。不過只要理清思路了,也用不了幾行代碼。本文就給出最近實現的一個版本。

// 計算每個卡片的坐標
export function calcPositions({ columns = 2, gap = 7, elements }) {
  if (!elements || !elements.length) {
    return [];
  }
  const y = []; //上一行卡片的底部縱坐標數組,用於找到新卡片填充位置
  const positions = []; // 每個卡片的坐標數組
  elements.forEach((item, index) => {
    if (y.length < columns) { // 還未填滿一行
      y.push(item.offsetHeight);
      positions.push({
        left: (index % columns) * (item.offsetWidth + gap),
        top: 0
      });
    } else {
      const min = Math.min(...y); // 最小縱坐標
      const idx = y.indexOf(min); // 縱坐標最小的卡片索引
      y.splice(idx, 1, min + gap + item.offsetHeight); // 替換成新卡片的縱坐標
      positions.push({
        left: idx * (item.offsetWidth + gap),
        top: min + gap
      });
    }
  });
// 由於採用絕對定位,容器是無法自動撐開的。因此需要計算實際高度,即最後一個卡片的top加上自身高度
  return { positions, containerHeight: positions[positions.length - 1].top + elements[elements.length - 1].offsetHeight };
}

上面這段代碼的作用就是計算每個卡片的lefttop,以及容器的總高度。關鍵位置都有注釋,應該不難理解。

有了這幾行核心代碼,要想封裝成瀑布流組件就很容易了。以 Vue 為例,可以這樣封裝:
MasonryLite.vue

<template>
  <div class="masonry-lite">
    <slot></slot>
  </div>
</template>
<script>
import { calcPositions } from './index.js';
export default {
  name: 'MasonryLite',
  props: {
    gap: {
      type: Number,
      default: 12,
    },
    columns: {
      type: Number,
      default: 2,
    },
  },
  data() {
    return {};
  },
  mounted() {
    this.doLayout();
  },
  methods: {
    doLayout() {
      const children = [...this.$el.querySelectorAll('.masonry-item')];
      if (children.length === 0) {
        return;
      }
      const { positions, containerHeight } = calcPositions({
        elements: children,
        columns: this.columns,
        gap: this.gap,
      });
      children.forEach((item, index) => {
        item.style.cssText = `left:${positions[index].left}px;top:${positions[index].top}px;`;
      });
      this.$el.style.height = `${containerHeight}px`;
    },
  },
};
</script>
<style lang="scss" scoped>
.masonry-lite{
  position: relative;
}
.masonry-item {
  position: absolute;
}
</style>

使用組件:

<MasonryLite>
  <div class="product-card masonry-item" v-v-for="(item, index) in items" :key="index">
    <img :src="item.imageUrl" />
    <header>{{ item.title }}</header>
  </div>
</MasonryLite>

不過這樣其實還會有點問題,就是doLayout的執行時機。因為該方案基於絕對定位,需要元素在渲染完成後才能獲取到實際寬高。如果卡片內有延遲加載的圖片或者其他動態內容,高度會發生變化。這種情況下就需要在DOM更新後主動調用一次doLayout重新計算布局。

如果大家有更好的實現方案,歡迎交流!

代碼倉庫:https://github.com/kaysonli/masonry-lite

npm 包:masonry-lite

如果覺得對你有幫助,幫忙點個不要錢的star。

熱門分類資訊推薦

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO - 天天要聞

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO

曾小賢的上司Lisa榕,現實中不僅才貌雙全,還嫁給了CEO雖然說《愛情公寓》這部劇在劇情上充滿了爭議,但是一定程度上,這部劇也是很多人的回憶,是伴隨了一代人的青春回憶,而且劇中的很多角色都成為了經典,他們的口頭禪也一直被拿來玩兒梗。
Lisa榕做主持多年沒紅,被陳赫拉進愛情公寓爆紅,如今怎樣了 - 天天要聞

Lisa榕做主持多年沒紅,被陳赫拉進愛情公寓爆紅,如今怎樣了

談到《愛情公寓》這部火爆一時的歡樂喜劇,大家肯定都不陌生。不知道大家是否還記得《愛情公寓》中那個把曾小賢治得服服帖帖的女上司Lisa榕,現實中的她名叫榕榕,和劇中的形象也判若兩人。1981年出生在遼寧瀋陽的榕榕,畢業於上海戲劇學院,後來成為了上海東方傳媒集團有限公司的一名主持人。