瀑布流三种方式

1,754 阅读2分钟

瀑布流

效果展示

定义

瀑布流是一种页面布局,有多个宽度相同高度不同模块组成;能够自动地适应,达到一行一行展示的效果。 视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。这种瀑布流多见于图片为主的网站。

为什么使用

  • 由于多栏分布,可以更好地适应移动端;
  • 可以打破常规的页面布局,有良好的视觉体验;
  • 参差不齐布局,有利调起下划的好奇心,吸引用户;

实现方式

  • 方法一:通过css3的多列布局中的columns-count 来实现瀑布流;最为简单的实现方法,但这种布局为竖向排序,当需要滚动加载时,弊端就显现出来了。效果预览
  • 方法二:通过js获取父级元素和子级元素的宽度和高度,子集元素进行遍历来确定当前元素所在位置;从而可以使用css的position进行定位; 具体的细节可看效果预览
  • 方法三:同样也需要js来获取元素 使用grid的栅格布局中的grid-row-end,从而可达到效果;具体的细节可看效果展示 注:具体的实现方式可根据下面代码注释走

方法一

html

<div class="root">
  <div class="item">
    <img src="https://picsum.photos/450/325?image=100" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/450?image=200" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/280?image=300" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/540?image=400" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/380?image=500" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/300?image=600" alt="">
  </div>
  <div class="item">
    <img src="https://picsum.photos/450/400?image=700" alt="">
  </div>
</div>

css

 .root {
  columns-count: 4;
  column-gap: 10px;
}
img {
  width: 100%;
  border-radius:10px;
  margin-bottom:10px;
}
@media only screen and (max-width: 768px) {
  .root {
    columns: 1;
  }
}
@media only screen and (max-width: 992px) and (min-width: 768px) {
  .root {
    columns: 3;
  }
}
@media only screen and (max-width: 1200px) and (min-width: 992px) {
  .root {
    columns: 4;
  }
}
@media only screen and (min-width: 1200px) {
  .root {
    columns: 6;
  }
}

方法二

html

  <div>
    <div id="root">
    </div>
  </div>

css

    #root {
      position: relative;
      opacity: 0;
    }

    .item {
      position: absolute;
    }

    .item>img {
      width: 200px;
      vertical-align: middle;
    }

js

    const imgList = [
    "https://picsum.photos/450/325?image=10",
    "https://picsum.photos/450/540?image=11",
    "https://picsum.photos/450/600?image=12",
    "https://picsum.photos/450/325?image=13",
    "https://picsum.photos/450/540?image=14",
    "https://picsum.photos/450/325?image=15",
    "https://picsum.photos/450/325?image=16",
    "https://picsum.photos/450/325?image=17",
    "https://picsum.photos/450/540?image=18",
    "https://picsum.photos/450/325?image=19",
    "https://picsum.photos/450/605?image=20",
    "https://picsum.photos/450/540?image=21"
    ]
    let itemAll = '' // 所有的子节点
    for (let index = 0; index < imgList.length; index++) {
      itemAll = itemAll + `<div class="item">
        <img src=${imgList[index]} alt="">
      </div>`
    }
    // 函数防抖
    const debounce = (fn) => {
      let timeOut = null;
      return (e) => {
        clearTimeout(timeOut)
        timeOut = setTimeout(() => {
          fn()
        }, 500)
      }
    }
   
    // 设图片的宽度为200px
    const imgWidth = 200
    // 父节点
    const rootDom = document.getElementById('root')
    // 添加子节点
    rootDom.innerHTML = itemAll
    //  等图片加载完执行
    window.addEventListener("load", () => waterFall())
    window.addEventListener("resize", debounce(()=>waterFall()))
    // 函数
    const waterFall = () => {
      console.log(234);
      rootDom.style.opacity=1
      // 动态的获取父节点的宽
      const rootWidth = rootDom.offsetWidth
      // 获取可以排对的列数
      const columnNum = Math.floor(rootWidth / imgWidth)
      // 获取子节点
      const itemDom = document.getElementsByClassName('item')
      // 高度数组
      let heightArr = []
      // 子元素进行遍历
      for (let index = 0; index < itemDom.length; index++) {
        // 当前的元素
        const element = itemDom[index]
        const elementHeight = element.offsetHeight
        // 判断是否是第一行 下标小于列数是第一行
        if (index < columnNum) {
          heightArr.push(elementHeight)
          element.style.left = index * imgWidth + 'px';
          element.style.top = 0 + 'px';
        } else {
          const minIndex = minBox(heightArr)
          const minValue = heightArr[minIndex]
          element.style.left = minIndex * imgWidth + 'px';
          element.style.top = minValue + 'px';
          heightArr[minIndex] += elementHeight;
        }
      }

    }

    function minBox(box) {
      var j = 0;
      for (i in box) {
        if (box[j] > box[i]) j = i
      }
      return j;
    }

方法三

htnml

  <div class="box">
    <div id="root">
    </div>
  </div>

css

    .box {
      max-width: 960px;
      margin-right: auto;
      margin-left: auto;
    }

    #root {
      display: grid;
      grid-template-columns: repeat(1, minmax(100px, 1fr));
      grid-gap: 10px;
      grid-auto-rows: 0;
      opacity: 0;
    }

    .item {
      border-radius: 10px;
      border: 1px solid #000;
      overflow: hidden;
    }

    .item>img {
      max-width: 100%;
      vertical-align: middle;
    }

    @media only screen and (max-width: 1023px) and (min-width: 768px) {
      #root {
        grid-template-columns: repeat(2, minmax(100px, 1fr));
      }
    }

    @media only screen and (min-width: 1024px) {
      #root {
        grid-template-columns: repeat(3, minmax(100px, 1fr));
      }
    }

js

    const imgList = [
    "https://picsum.photos/450/325?image=10",
    "https://picsum.photos/450/540?image=11",
    "https://picsum.photos/450/600?image=12",
    "https://picsum.photos/450/325?image=13",
    "https://picsum.photos/450/540?image=14",
    "https://picsum.photos/450/325?image=15",
    "https://picsum.photos/450/325?image=16",
    "https://picsum.photos/450/325?image=17",
    "https://picsum.photos/450/540?image=18",
    "https://picsum.photos/450/325?image=19",
    "https://picsum.photos/450/605?image=20",
    "https://picsum.photos/450/540?image=21"
    ]
    let itemAll = '' // 所有的子节点
    for (let index = 0; index < imgList.length; index++) {
      itemAll = itemAll + `<div class="item">
      <img src=${imgList[index]} alt="">
    </div>`
    }
    // 父节点
    const rootDom = document.getElementById('root')
    // 添加子节点
    rootDom.innerHTML = itemAll
    // 主函数
    function resizeAllMasonryItems() {
      const rootDom = document.getElementById('root')
      rootDom.style.opacity = 1
      const itemDom = document.getElementsByClassName('item')
      if (itemDom && rootDom) {
        for (var i = 0; i < itemDom.length; i++) {
          const curDom = itemDom[i]
          // 当前的元素
          var rowGap = parseInt(window.getComputedStyle(rootDom).getPropertyValue('grid-row-gap'))
          // 获取css中 grid-auto-rows 的值
          var rowHeight = parseInt(window.getComputedStyle(rootDom).getPropertyValue('grid-auto-rows'))
          // 获取当前节点下的img
          var gridImagesAsContent = curDom.querySelector('img');
          // 获取 模块所在位置 rowHeight没设置时为0
          var rowSpan = Math.ceil((gridImagesAsContent.offsetHeight + rowGap) / (rowHeight + rowGap));
          // 最为关键 grid-row-end
          curDom.style.gridRowEnd = 'span ' + rowSpan;
        }
      }
    }
    window.addEventListener("load", resizeAllMasonryItems)

总结

方法一虽是最简单的,但方法二是最常用到的 ;方法三相对比方法二简单,但存在着向上取整,导致数值有一定的偏差;还是结合业务进行选择。


注: