5 種瀑布流場景的實現原理解析

一、背景

本文介紹 5 種瀑布流場景的實現,大家可以根據自身的需求場景進行選擇

5 種場景分別是:

瀑布流 特點
縱向+高度排序 純 CSS 多列實現,是最簡單的瀑布流寫法
縱向+高度排序+根據寬度自適應列數 通過 JS 根據屏幕寬度計算列數,在 web 端更加靈活的展示瀑布流
橫向 純 CSS 彈性佈局實現,是最簡單的橫向瀑布流寫法
橫向+高度排序 橫向+高度排序的瀑布流,需要通過 JS 計算每一列高度,損耗性能,但是可以避免某列特別長的情況,體驗更好
橫向+高度排序+根據寬度自適應列數 需要通過 JS 計算每一列高度,並根據屏幕寬度計算列數,損耗性能,但是可以避免某列特別長的情況,並且可以在 web 端更加靈活的展示瀑布流,體驗更好,是 5 種瀑布流中用戶體驗最好的

我已經將這 5 種場景的實現封裝成 npm 包,npm 包地址:https://www.npmjs.com/package/react-masonry-component2,可以直接在 React 項目中安裝使用。

二、介紹

瀑布流,是比較流行的一種網站頁面佈局,視覺表現爲參差不齊的多欄佈局,隨着頁面滾動條向下滾動,這種佈局還會不斷加載數據塊並附加至當前尾部。

下圖就是一個瀑布流佈局的示意圖:

image.png

三、縱向+高度排序

縱向+高度排序指的是,每列按照縱向排列,往高度最小的列添加內容,如下圖所示。

image.png

實現縱向+高度排序瀑布流的方法是 CSS 多列布局

1. 多列布局介紹

多列布局指的是 CSS3 可以將文本內容設計成像報紙一樣的多列布局,如下實例:

image.png

CSS3 的多列屬性:

  • column-count:指定了需要分割的列數;
  • column-gap:指定了列與列間的間隙;
  • column-rule-style:指定了列與列間的邊框樣式;
  • column-rule-width:指定了兩列的邊框厚度;
  • column-rule-color:指定了兩列的邊框顏色;
  • column-rule:是 column-rule-* 所有屬性的簡寫;
  • column-span:指定元素跨越多少列;
  • column-width:指定了列的寬度。

2. 實現思路

瀑布流實現思路如下:

  • 通過 CSS column-count 分割內容爲指定列;
  • 通過 CSS break-inside 保證每個子元素渲染完再換行;

3. 實現代碼

.css-column {
  column-count: 4; //分爲4列
}

.css-column div {
  break-inside: avoid; // 保證每個子元素渲染完在換行
}

4. 直接使用 npm 包

npm - react-masonry-component2 的使用方法:

import { Masonry } from 'react-masonry-component2'

export const MyComponent = (args) => {
  return (
    <Masonry direction='column'>
      <div></div>
      <div></div>
      <div></div>
    </Masonry>
  )
}

在線預覽

四、縱向+高度排序+根據寬度自適應列數

在縱向+高度排序的基礎上,按照寬度自適應列數。

image.png

1. 實現思路

  • 監聽 resize 方法,根據屏幕寬度得到該寬度下應該展示的列數

2. 實現代碼

import { useCallback, useEffect, useMemo, useState } from 'react'

import { DEFAULT_COLUMNS_COUNT } from '../const'

export const useHasMounted = () => {
  const [hasMounted, setHasMounted] = useState(false)
  useEffect(() => {
    setHasMounted(true)
  }, [])
  return hasMounted
}

export const useWindowWidth = () => {
  const hasMounted = useHasMounted()
  const [width, setWidth] = useState(0)

  const handleResize = useCallback(() => {
    if (!hasMounted) return
    setWidth(window.innerWidth)
  }, [hasMounted])

  useEffect(() => {
    if (hasMounted) {
      window.addEventListener('resize', handleResize)
      handleResize()
      return () => window.removeEventListener('resize', handleResize)
    }
  }, [hasMounted, handleResize])

  return width
}

export const useColumnCount = (columnsCountBreakPoints: {
  [props: number]: number
}) => {
  const windowWidth = useWindowWidth()
  const columnCount = useMemo(() => {
    const breakPoints = (
      Object.keys(columnsCountBreakPoints as any) as unknown as number[]
    ).sort((a: number, b: number) => a - b)
    let count =
      breakPoints.length > 0
        ? columnsCountBreakPoints![breakPoints[0]]
        : DEFAULT_COLUMNS_COUNT

    breakPoints.forEach((breakPoint) => {
      if (breakPoint < windowWidth) {
        count = columnsCountBreakPoints![breakPoint]
      }
    })

    return count
  }, [windowWidth, columnsCountBreakPoints])

  return columnCount
}

動態定義 style columnCount,實現根據屏幕寬度自適應列數:

const { columnsCountBreakPoints } = props
const columnCount = useColumnCount(columnsCountBreakPoints)
return (
  <div className={classNames(['masonry-column-wrap'])} style={{ columnCount }}>
    {children}
  </div>
)

3. 直接使用 npm 包

npm - react-masonry-component2 的使用方法:

import { Masonry } from 'react-masonry-component2'

export const MyComponent = (args) => {
  return (
    <Masonry
      direction='column'
      columnsCountBreakPoints={{
        1400: 5,
        1000: 4,
        700: 3,
      }}
    >
      <div></div>
      <div></div>
      <div></div>
    </Masonry>
  )
}

在線預覽

五、橫向

橫向瀑布流指的是,每列按照橫向排列,如下圖所示。

image.png

實現橫向瀑布流的方法是CSS 彈性佈局

1. 彈性佈局介紹

彈性佈局,是一種當頁面需要適應不同的屏幕大小以及設備類型時確保元素擁有恰當的行爲的佈局方式。

引入彈性盒佈局模型的目的是提供一種更加有效的方式來對一個容器中的子元素進行排列、對齊和分配空白空間。

CSS3 的彈性佈局屬性:

  • flex-dicreation:指定了彈性子元素的排列方式;
  • justify-content:指定了彈性佈局的主軸對齊方式;
  • align-items:指定了彈性佈局的側軸對齊方式;
  • flex-wrap:指定了彈性子元素的換行方式;
  • align-content:指定彈性佈局各行的對齊方式;
  • order:指定彈性子元素的排列順序;
  • align-self:指定彈性子元素的縱向對齊方式;
  • flex 屬性用於指定彈性子元素如何分配空間;
    • auto: 計算值爲 1 1 auto
    • initial: 計算值爲 0 1 auto
    • none:計算值爲 0 0 auto
    • inherit:從父元素繼承
    • [ flex-grow ]:定義彈性盒子元素的擴展比率。
    • [ flex-shrink ]:定義彈性盒子元素的收縮比率。
    • [ flex-basis ]:定義彈性盒子元素的默認基準值。

2. 實現思路

瀑布流實現思路如下:

  • CSS 彈性佈局對 4 列按橫向排列,對每一列內部按縱向排列。

3. 實現代碼

瀑布流實現代碼如下:

<div className={classNames(['masonry-flex-wrap'])}>
  <div className='masonry-flex-wrap-column'>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
  <div className='masonry-flex-wrap-column'>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>
.masonry-flex-wrap {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-content: stretch;

  &-column {
    display: 'flex';
    flex-direction: 'column';
    justify-content: 'flex-start';
    align-content: 'stretch';
    flex: 1;
  }
}

4. 直接使用 npm 包

npm - react-masonry-component2 的使用方法:

import { Masonry } from 'react-masonry-component2'

export const MyComponent = (args) => {
  return (
    <Masonry
      columnsCountBreakPoints={{
        1400: 5,
        1000: 4,
        700: 3,
      }}
    >
      <div></div>
      <div></div>
      <div></div>
    </Masonry>
  )
}

在線預覽

六、橫向+高度排序

橫向+高度排序指的是,每列按照橫向排列,往高度最小的列添加內容,如下圖所示。

image.png

高度排序就需要用 JS 邏輯來做了。

1. 實現思路

  • JS 將瀑布流的列表按高度均爲分爲指定列數,比如瀑布流爲 4 列,那麼就要把瀑布流列表分成 4 個列表

2. 實現代碼

export const getColumnsSortWithHeight = (
  children: React.ReactNode,
  columnCount: number
) => {
  const columns: {
    height: number
    children: React.ReactNode[]
  }[] = Array.from({ length: columnCount }, () => ({
    height: 0,
    children: [],
  }))

  React.Children.forEach(children, (child: React.ReactNode, index) => {
    if (child && React.isValidElement(child)) {
      if (index < columns.length) {
        columns[index % columnCount].children.push(child)
        columns[index % columnCount].height += child.props.height
        return
      }

      const minHeightColumn = minBy(columns, (a) => a.height) as {
        height: number
        children: React.ReactNode[]
      }
      minHeightColumn.children.push(child)
      minHeightColumn.height += child.props.height
    }
  })

  return columns
}

3. 直接使用 npm 包

npm - react-masonry-component2 的使用方法:

import { Masonry, MasonryItem } from 'react-masonry-component2'

export const MyComponent = (args) => {
  return (
    <Masonry
      sortWithHeight
      columnsCountBreakPoints={{
        1400: 5,
        1000: 4,
        700: 3,
      }}
    >
      <MasonryItem height={200}>
        <div></div>
      </MasonryItem>
      <MasonryItem height={300}>
        <div></div>
      </MasonryItem>
      <MasonryItem height={400}>
        <div></div>
      </MasonryItem>
    </Masonry>
  )
}

在線預覽

七、橫向+高度排序+根據寬度自適應列數

根據寬度自適應列數的做法和縱向場景一致,都是監聽 resize 方法,根據屏幕寬度得到該寬度下應該展示的列數,這裏不做贅述。

image.png

1. 直接使用 npm 包

npm - react-masonry-component2 的使用方法:

import { Masonry } from 'react-masonry-component2'

export const MyComponent = (args) => {
  return (
    <Masonry
      sortWithHeight
      direction='column'
      columnsCountBreakPoints={{
        1400: 5,
        1000: 4,
        700: 3,
      }}
    >
      <div></div>
      <div></div>
      <div></div>
    </Masonry>
  )
}

在線預覽

小結

本文介紹了 5 種瀑布流場景的實現:

  • 縱向+高度排序
  • 縱向+高度排序+根據寬度自適應列數
  • 橫向
  • 橫向+高度排序
  • 橫向+高度排序+根據寬度自適應列數

感興趣的同學可以到項目源碼查看完整實現代碼。

也可以下載直接使用。

更多思考

當瀑布流數據特別多時,dom 節點過多,會影響到頁面性能,那麼就需要爲瀑布流添加滾動預加載和節點回收功能來進行優化了,在下個版本中將更新滾動預加載和節點回收功能的實現原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章