vue3实现瀑布流布局组件

这篇具有很好参考价值的文章主要介绍了vue3实现瀑布流布局组件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

先看效果图
vue3实现瀑布流布局组件,vue3,vue.js,前端,javascript

直接上代码
utils.js

// 用于模拟接口请求
export const getRemoteData = (data = '获取数据', time = 2000) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`模拟获取接口数据`, data)
            resolve(data)
        }, time)
    })
}

// 获取数组随机项
export const getRandomElement = (arr) => {
    var randomIndex = Math.floor(Math.random() * arr.length);
    return arr[randomIndex];
}

// 指定范围随机数
export const getRandomNumber = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

// 节流
export const throttle = (fn, time) => {
    let timer = null
    return (...args) => {
        if (!timer) {
            timer = setTimeout(() => {
                timer = null
                fn.apply(this, args)
            }, time)
        }
    }
}
// 防抖
export const debounce = (fn, time) => {
    let timer = null
    return (...args) => {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, time)
    }
}

data.js 模拟后台返回的数据

import { getRandomElement, getRandomNumber } from "./utils.js"

const colorList = ['red', 'blue', 'green', 'pink', 'yellow', 'orange', 'purple', 'brown', 'gray', 'skyblue']

export const createList = (pageSize) => {
    let list = Array.from({ length: pageSize }, (v, i) => i)
    return list.map(x => {
        return {
            background: getRandomElement(colorList),
            width: getRandomNumber(200, 600),
            height: getRandomNumber(400, 700),
            x: 0,
            y: 0
        }
    })
}

瀑布流布局组件waterfall.vue

<template>
  <div class="waterfall-container" ref="containerRef" @scroll="handleScroll">
    <div class="waterfall-list">
      <div
        class="waterfall-item"
        v-for="(item, index) in resultList"
        :key="index"
        :style="{
          width: `${item.width}px`,
          height: `${item.height}px`,
          transform: `translate3d(${item.x}px, ${item.y}px, 0)`,
        }"
      >
        <slot name="item" v-bind="item"></slot>
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, onMounted, computed, nextTick, onUnmounted } from "vue";
import { createList } from "@/common/data.js";
import { getRemoteData, throttle, debounce } from "@/common/utils.js";
const props = defineProps({
  // 间距
  gap: {
    type: Number,
    default: 10,
  },
  // 列数
  columns: {
    type: Number,
    default: 3,
  },
  // 距离底部
  bottom: {
    type: Number,
    default: 0,
  },
  // 分页大小
  pageSize: {
    type: Number,
    default: 10,
  },
});

// 容器ref
const containerRef = ref(null);

// 卡片宽度
const cardWidth = ref(0);

// 列高度
const columnHeight = ref(new Array(props.columns).fill(0));

// 数据list
const resultList = ref([]);

// 当前页码
const pageNum = ref(1);

// 加载状态
const loading = ref(false);

// 计算最小列高度及其下标
const minColumn = computed(() => {
  let minIndex = -1,
    minHeight = Infinity;

  columnHeight.value.forEach((item, index) => {
    if (item < minHeight) {
      minHeight = item;
      minIndex = index;
    }
  });

  return {
    minIndex,
    minHeight,
  };
});

// 获取接口数据
const getData = async () => {
  loading.value = true;
  const list = createList(props.pageSize);
  const resList = await getRemoteData(list, 300).finally(
    () => (loading.value = false)
  );
  pageNum.value++;
  resultList.value = [...resultList.value, ...getList(resList)];
};

// 滚动到底部获取新一页数据-节流
const handleScroll = throttle(() => {
  const { scrollTop, clientHeight, scrollHeight } = containerRef.value;
  const bottom = scrollHeight - clientHeight - scrollTop;
  if (bottom <= props.bottom) {
    !loading.value && getData();
  }
});

// 拼装数据结构
const getList = (list) => {
  return list.map((x, index) => {
    const cardHeight = Math.floor((x.height * cardWidth.value) / x.width);
    const { minIndex, minHeight } = minColumn.value;
    const isInit = index < props.columns && resultList.length <= props.pageSize;
    if (isInit) {
      columnHeight.value[index] = cardHeight + props.gap;
    } else {
      columnHeight.value[minIndex] += cardHeight + props.gap;
    }

    return {
      width: cardWidth.value,
      height: cardHeight,
      x: isInit
        ? index % props.columns !== 0
          ? index * (cardWidth.value + props.gap)
          : 0
        : minIndex % props.columns !== 0
        ? minIndex * (cardWidth.value + props.gap)
        : 0,
      y: isInit ? 0 : minHeight,
      background: x.background,
    };
  });
};

// 监听元素
const resizeObserver = new ResizeObserver(() => {
  handleResize();
});

// 重置计算宽度以及位置
const handleResize = debounce(() => {
  const containerWidth = containerRef.value.clientWidth;
  cardWidth.value =
    (containerWidth - props.gap * (props.columns - 1)) / props.columns;
  columnHeight.value = new Array(props.columns).fill(0);
  resultList.value = getList(resultList.value);
});

const init = () => {
  if (containerRef.value) {
    const containerWidth = containerRef.value.clientWidth;
    cardWidth.value =
      (containerWidth - props.gap * (props.columns - 1)) / props.columns;
    getData();
    resizeObserver.observe(containerRef.value);
  }
};

onMounted(() => {
  init();
});
// 取消监听
onUnmounted(() => {
  containerRef.value && resizeObserver.unobserve(containerRef.value);
});
</script>

<style lang="scss">
.waterfall {
  &-container {
    width: 100%;
    height: 100%;
    overflow-y: scroll;
    overflow-x: hidden;
  }

  &-list {
    width: 100%;
    position: relative;
  }
  &-item {
    position: absolute;
    left: 0;
    top: 0;
    box-sizing: border-box;
    transition: all 0.3s;
  }
}
</style>

使用该组件(这里columns写死了3列)

<template>
  <div class="container">
    <WaterFall :columns="3" :gap="10">
      <template #item="{ background }">
        <div class="card-box" :style="{ background }"></div>
      </template>
    </WaterFall>
  </div>
</template>

<script setup>
import WaterFall from "@/components/waterfall.vue";
</script>

<style scoped lang="scss">
.container {
  width: 700px;  /* 一般业务场景不是固定宽度 */
  height: 800px;
  border: 2px solid #000;
  margin-top: 10px;
  margin-left: auto;
}
.card-box {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 4px;
}
</style>

若要响应式调整列数,可参考以下代码


const fContainerRef = ref(null);
const columns = ref(3);
const fContainerObserver = new ResizeObserver((entries) => {
  changeColumn(entries[0].target.clientWidth);
});

// 根据宽度,改变columns列数
const changeColumn = (width) => {
  if (width > 1200) {
    columns.value = 5;
  } else if (width >= 768 && width < 1200) {
    columns.value = 4;
  } else if (width >= 520 && width < 768) {
    columns.value = 3;
  } else {
    columns.value = 2;
  }
};

onMounted(() => {
  fContainerRef.value && fContainerObserver.observe(fContainerRef.value);
});

onUnmounted(() => {
  fContainerRef.value && fContainerObserver.unobserve(fContainerRef.value);
});

瀑布流布局组件监听columns变化文章来源地址https://www.toymoban.com/news/detail-831487.html

watch(
  () => props.columns,
  () => {
    handleResize();
  }
);

到了这里,关于vue3实现瀑布流布局组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序实现吸顶、网格、瀑布流布局

    微信小程序开发通常是在webview模式下编写,但是对小程序的渲染性能有一定的追求,就需要使用Skyline模式进行渲染,同时在这种模式下有也有一些特殊的组件,可以轻松的实现想要的效果,本文将介绍在Skyline模式下如何实现吸顶、网格、瀑布流布局。 以下是具体的实现:

    2024年02月22日
    浏览(45)
  • 微信小程序实现瀑布流布局(方式一)

    根据奇数列和偶数列区分左边和右边数据 设置width固定,mode=“widthFix” 适用于:左右两列的高度相差不是很大

    2024年02月12日
    浏览(29)
  • 微信小程序css实现瀑布流布局

    废话不多说 直接上代码 博客地址:BULINGBULING

    2024年02月13日
    浏览(31)
  • 前端vue仿京东天猫简单好用的瀑布流瀑布流式布局列表组件waterfall

    前端vue仿京东天猫简单好用的瀑布流瀑布流式布局列表组件waterfall, 阅读全文下载完整代码请关注微信公众号: 前端组件开发 效果图如下: 使用方法 HTML代码部分 JS代码 (引入组件 填充数据) CSS

    2024年02月09日
    浏览(30)
  • vue3使用vue-masonry插件实现瀑布流

    《Vue插件》瀑布流插件vue-masonry的使用与踩坑记录 参数: item-selector transition-duration column-width origin-left origin-top gutter 前言: 之前其实有分享过一篇纯CSS实现瀑布流的方法: https://oliver.blog.csdn.net/article/details/126450691,但纯CSS实现的方案都不是比较好的方案,总归有一些各式各样的

    2024年01月19日
    浏览(26)
  • 微信小程序瀑布流布局

    2024年02月07日
    浏览(38)
  • css 使用flex 完成瀑布流布局

    瀑布流布局在商城类、文章类 app、网页中都是常用的,使用这样的形式,能过让整个页面更加的活波,也能让图片根据实际的大小来显示,更好的展示图片内容。那么代码如何实现呢 其中下面代码部分是scss

    2024年02月04日
    浏览(28)
  • 基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十一:通用表单组件封装实现

      本章实现通用表单组件,根据实体配置识别实体属性,并自动生成编辑组件,实现对应数据填充、校验及保存等逻辑。 1. 详细课程地址: https://edu.csdn.net/course/detail/38183 2. 源码下载地址: 点击下载

    2024年02月10日
    浏览(38)
  • vue3 vue.config.js配置Element-plus组件和Icon图标实现按需自动引入

    打包时,报警告,提示包太大会影响性能 在页面直接使用,直接使用 SVG 图标,当做一般的 svg 使用 icon使用时需要用以下两种方式方式: 如果用在el-button里面的icon属性上使用,用SVG方式无效,还是需要引入再使用(不知道有没有其他方式) 注意: 使用 :icon=\\\"Edit\\\" 则icon的大

    2024年02月06日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包