Bootstrap

vue实现瀑布流布局

简介:商城瀑布流,商品的图片尺寸不同,实现高低错落排列的感觉。
原理:图片尺寸不一样,让宽度保持一致,高度自适应,所以后端返回了图片的原始宽高,方便计算。
分成2列(可自定义),接口拿到数据,先计算出图片所占高度,在根据当前高度计算出两列中哪列矮,将数据push到哪一列。
效果:在这里插入图片描述
代码:vue3组合式api+ts+vant

<template>
  <div class="all_wrap">
    <div class="inner">
      <van-list
        v-model:loading="state.loading"
        v-model:error="state.error"
        :finished="state.finished"
        error-text="请求失败,点击重新加载"
        finished-text="没有更多了"
        @load="onLoad"
      >
        <div class="list_inner">
          <div class="lists_more" v-for="(ele, i) of waterfallList" :key="i">
            <div
              class="list_item"
              v-for="(item, index) in ele"
              :key="index"
            >
            <!-- 自定义瀑布流内容 start -->
              <img
                v-if="item.showImage"
                :style="{ height: item.height + 'px' }"
                :src="item.showImage.split(',')[0]"
                alt=""
              />
              <div
                class="else_picture"
                :style="{ height: item.height + 'px' }"
                v-else
              ></div>
              <div class="list_detail">
                <div class="list_name">{{ item.name }}</div>
                <div class="list_pos">地点:{{ item.address }}</div>
              </div>
            <!-- 自定义瀑布流内容 end -->
            </div>
          </div>
        </div>
      </van-list>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { reactive, onMounted, nextTick, ref } from "vue";
import {
  attractionsRecommended,
} from "@/api/common";
const wrapWidth = ref(0); // 瀑布的wrap的宽度
onMounted(async () => {
  // 获取当前设备宽度
  wrapWidth.value = document.querySelector(".list_inner").clientWidth;
});
// 最新推荐
const state = reactive({
  loading: true,
  error: false, // 加载失败
  finished: false,
});
const pages = reactive({
  pageNo: 1,
  pageSize: 6,
  total: 0,
}); // 分页信息
const waterfallList = ref([[], []]); // 瀑布数据,分2列(几列就几个空数组)
const onLoad = async () => {
  state.loading = true;
  state.error = false; // 请求失败关闭
  const data = {
    pageNo: pages.pageNo,
    pageSize: pages.pageSize,
  };
  const res: any = await attractionsRecommended(data); // 请求接口
  state.loading = false;
  if (res && res.code === 200) {
    res.data.forEach(async (item) => {
      // 获取图片宽高信息
      let temp = item.showImage.split("?")[1];
      if (temp) {
        temp = temp.split("&");
        const _picWidth = wrapWidth.value * 0.49; // 自定义宽度
        // 换算比例
        const _H = ((_picWidth / temp[0].split("=")[1]) * temp[1].split("=")[1]).toFixed(2);
        item.height = _H;
      }
      // 将数据push
      nextTick(() => {
        // 不能去掉定时器,需要异步延时,不然一下子全部push进去,就跑到一列上了
        setTimeout(() => {
          const DOMS = document.querySelectorAll(".lists_more");
          if (DOMS[0].clientHeight <= DOMS[1].clientHeight) {
            waterfallList.value[0].push(item);
          } else {
            waterfallList.value[1].push(item);
          }
        }, 0);
      });
    });
    // 分页等信息
    pages.total = res.total;
    const temp = pages.pageNo * pages.pageSize;
    if (pages.total > temp) {
      pages.pageNo++;
      state.finished = false;
    } else {
      state.finished = true;
    }
  } else {
    state.error = true; // 展示请求失败
  }
};
// 加载列表
</script>

<style lang="scss" scoped>
.all_wrap {
  width: 100%;
  height: 100%;
  background: #f6f7f8;
  position: relative;
  font-size: 16px;
  box-sizing: border-box;
  font-family: PingFangSC-Medium, PingFang SC;
  overflow: hidden;
  .inner {
    width: 100%;
    height: 100%;
    overflow: auto;
    padding: 12px;
    &::-webkit-scrollbar {
      display: none !important;
    }
    box-sizing: border-box;
  }
  // 瀑布流样式
  .list_inner {
    width: 100%;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    .lists_more {
      width: 49%;
      height: min-content;
      .list_item {
        margin-bottom: 6px;
        border-radius: 4px;
        overflow: hidden;
        img {
          width: 100%;
          min-height: 100px;
          max-height: 300px;
          display: block;
        }
        .list_detail {
          padding: 6px 12px;
          background: #fff;

          .list_name,
          .list_pos {
            width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            text-overflow: -webkit-ellipsis-lastline;
            display: -webkit-box;
            -webkit-line-clamp: 1;
            line-clamp: 1;
            -webkit-box-orient: vertical;
            word-break: break-all;
          }
          .list_name {
            font-size: 14px;
            font-weight: 600;
            color: #333333;
            line-height: 20px;
            margin-bottom: 4px;
          }
          .list_pos {
            font-size: 12px;
            color: #666666;
            line-height: 17px;
          }
        }
      }
    }
  }
}
</style>

;