瀑布流的原理以及实现

624 阅读2分钟

瀑布流

1.什么是瀑布流

  • 特点
    • 内容以垂直滚动的方式呈现,形成类似瀑布的视觉效果。
    • 每个独立的内容块都是一个完整的视觉单元,可以独立浏览。
    • 内容块之间没有明显的分隔边界,给人一种连贯流畅的视觉体验。
    • 这种布局方式适用于展示大量内容,如新闻、博客文章、产品列表等。
    • 瀑布流布局可以根据用户的滚动行为自动加载更多内容,提升用户体验。
    • 该布局模式广泛应用于各类移动端和PC端网站,尤其适用于内容密集型网站。

2. 瀑布流的原理以及实现

2.1 父页面获取数据

部分关键代码如下

<script>
// 获取&&加工数据
const list = ref<any[]>([]);
onMounted(() => {
  fetch("https://api.thecatapi.com/v1/images/search?limit=100&api_key=YOUR_API_KEY")
    .then((res) => res.json())
    .then((res) => {
        const _res = res.map((item: any) => {
            return {
                ...item,
                height: Math.round(item.height / item.width * 120),
                width: item.width,
            }
        })
        list.value = _res
    });
});
</script>
<template>
  <waterFallView :waterList="list" />
</template>

2.2 子页面渲染视图

<script setup lang="ts">
import { reactive, watch } from "vue";

const props = defineProps<{
  waterList: any[];
}>();
const _waterList = reactive<any[]>([]);
const heightList: number[] = [];
watch(
  () => props.waterList,
  () => {
    init();
  }
)
const init = () => {
  const width = 130; // 每一项宽度设置大一点,每一列之间间距就可以大一点
  const clientWidth = document.body.clientWidth; // 窗口可视区域宽度
  const cols = Math.floor(clientWidth / width); // 列数
  for (let i = 0; i < props.waterList.length; i++) {
    // 第一行的
    if (i < cols) {
      props.waterList[i].left = i * width; // 左偏移
      props.waterList[i].top = 0;
      _waterList.push(props.waterList[i]);
      heightList.push(props.waterList[i].height);
    } else {
      // 其他行的
      // 1. 找到最短的那一列
      let minHeight = heightList[0]; // 假设第一个最短
      let minIndex = 0;
      for (let j = 0; j < heightList.length; j++) {
        if (minHeight > heightList[j]) {
          minHeight = heightList[j]; // 最短列高度
          minIndex = j;
        }
      }

      // 2. 设置当前item的left和top
      props.waterList[i].top = minHeight + 10; // 距离第一行的偏移量 = 最短列高度 + 间距
      props.waterList[i].left = minIndex * width; // 左偏移
      // 3. 更新高度数组
      heightList[minIndex] += props.waterList[i].height + 10; // 最矮的高度 + 新加上去的高度 + 间距
      // 3. 更新视图
      _waterList.push(props.waterList[i]);
    }
  }
};
</script>

<template>
  <div class="hy-waterfall__wrapper">
    <div
      v-for="item in _waterList"
      class="hy-waterfall__item"
      :style="{
        height: item.height + 'px',
        background: item.background,
        left: item.left + 'px',
        top: item.top + 'px',
      }"
    >
      <img :src="item.url" alt="item.id" loading="lazy">
    </div>
  </div>
</template>

<style scoped lang="scss">
@include b(waterfall) {
  @include e(wrapper) {
    position: relative;
  }
  @include e(item) {
    position: absolute;
    width: 120px; // 定义每个item的宽度
    img{
        position: absolute;
        width: 100%;
    }
  }
}
</style>

3. 效果图

image-20240529183413303

4. 总结

先固定宽度,让图片自适应,算出每一张图片的高度。维护一个高度数组,用来存储每一列的高度。在每一行插入完成后,往最矮的那一列插入图片,并且更新高度数组,依次往复,直到所有的图片被插入到视图中。
感谢小满老师。