当前位置: 首页 > news >正文

vue3实现图片瀑布流(基于Masonry实现)

前言

需要实现一个图片+视频的瀑布流效果,使用了vue3+Masonry库进行实现,这是一个关于调用AI提供接口(聚合起来)再实现一个二次AI的平台

效果

模板一

模板二

开始

1.安装

npm install masonry-layout

2.代码1实现

<script setup lang="ts"> import { reactive, ref, watch, onMounted, nextTick } from "vue"; import Masonry from "masonry-layout"; /** * 图片瀑布流 */ const gridRef = ref(null); let msnry = null; // 初始化 Masonry const initMasonry = async () => { console.log("====initMasonry======"); await nextTick(); if (!gridRef.value) return; msnry = new Masonry(gridRef.value, { itemSelector: ".i-c-card", columnWidth: 224, gutter: 5, fitWidth: false, // 水平排序 horizontalOrder: true, // 取消掉左上角动画 transitionDuration: 0, }); }; // 监听数据变化[更新布局] watch( () => dataImgArr.value, async () => { await nextTick(); if (msnry) { msnry.reloadItems(); msnry.layout(); } }, { deep: true }, ); // 图片|视频加载好调用msnry.layout const relayout = () => { if (msnry) { msnry.layout(); } }; // 页面渲染完执行initMasonry onMounted(() => { initMasonry(); }); </script> <template> <div class="my-container"> <!-- 图片瀑布流 --> <div class="img-container" @scroll="handleScroll"> <div class="img-content" ref="gridRef"> <template v-for="(item, key) in dataArr" :key="item.id"> <div class="i-c-card" ref="cardRef"> <div style="position: relative"> <img v-if="item.chat_type == 'image'" :src="item.chat_message[0]" alt="Image" @load="relayout" /> <video v-if="item.chat_type == 'video'" :src="item.chat_message[0]" @loadedmetadata="relayout" ></video> <div class="i-c-nav-content"> <div style=" display: flex; justify-content: center; align-items: center; " > <div class="logo"> <img :src="item.userinfo.avatar" alt="" /> </div> <div class="title"> <span>{{ item.userinfo.nickname }}</span> </div> </div> <div v-if="item.glsquare" style=" display: flex; justify-content: center; align-items: center; " > <div class="btn"> <div class="content"> <img src="@assets/icons/white-watch.svg" alt="" /> <span>{{ item.glsquare.views }}</span> </div> </div> <div class="btn" style="margin-left: 10px"> <div class="content"> <img src="@assets/icons/collect.svg" alt="" /> <span>{{ item.glsquare.likes }}</span> </div> </div> </div> </div> </div> <div class="i-c-nan-title-content" v-if="false"> <div class="title"> <span>{{ item.glsquare.title }}</span> </div> </div> </div> </template> </div> </div> <!-- end 图片瀑布流 --> </div> </template> <style scoped lang="scss"> .img-container { height: calc(100vh - 60px); overflow-y: auto; margin-left: 20px; .img-content { width: 100%; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 2px; padding: 0px 0px; padding-bottom: 100px; // 滚动条 .ready { opacity: 1; } .i-c-card::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0); transition: all 0.25s ease; pointer-events: none; } .i-c-card:hover::after { background: rgba(0, 0, 0, 0.4); } .i-c-card:hover { // transform: translateY(-6px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18), 0 4px 10px rgba(0, 0, 0, 0.12); } .i-c-card:hover .i-c-nav-content { z-index: 9; } .i-c-card:hover .i-c-nan-title-content { z-index: 9; } .i-c-card { overflow: hidden; display: flex; flex-direction: column; //width: 100%; width: 224px; // width: calc((100vw - 280px) / 8); border-radius: 8px; margin-bottom: 10px; //background-color: red; //height: auto; img { cursor: pointer; width: 100%; height: auto; display: block; object-fit: cover; } video { cursor: pointer; width: 100%; height: auto; display: block; object-fit: cover; } .i-c-nan-title-content { padding: 10px 20px; display: flex; align-items: center; border: 1px solid rgba(38, 38, 38, 1); border-top: 0; background-color: rgba(23, 23, 23, 1); color: rgba(196, 199, 200, 1); font-size: 12px; .title { width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } } .i-c-nav-content { width: 100%; position: absolute; bottom: 5px; padding: 0px 20px; display: flex; align-items: center; justify-content: space-between; .i-c-n-li { flex: 1; } .logo { width: 24px; img { width: 24px; height: 24px; border-radius: 100%; } } .title { width: 80px; margin-left: 4px; margin-right: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 10px; color: rgba(255, 253, 253, 1); } .btn { //background-color: rgba(217, 217, 217, 0.8); border-radius: 10px; padding: 5px 5px; img { width: 12px; height: 12px; margin-right: 2px; } .content { display: flex; justify-content: center; align-items: center; span { margin-left: 5px; font-size: 14px; } } } } } } @media (max-width: 1600px) { .img-content { grid-template-columns: repeat(5, 1fr); } } @media (max-width: 1400px) { .img-content { grid-template-columns: repeat(4, 1fr); } } @media (max-width: 1100px) { .img-content { grid-template-columns: repeat(3, 1fr); } } @media (max-width: 768px) { .img-content { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 480px) { .img-content { grid-template-columns: repeat(1, 1fr); } } } </style>

代码2实现

<script setup lang="ts"> import { ref, watch, onMounted, nextTick } from "vue"; const list = ref([ { id: 0, chat_type: "image", chat_message: ["https://picsum.photos/id/10/1200/675"], orientation: "横", }, { id: 1, chat_type: "image", chat_message: ["https://picsum.photos/id/11/675/1200"], orientation: "竖", }, { id: 2, chat_type: "image", chat_message: ["https://picsum.photos/id/12/1920/1080"], orientation: "横", }, { id: 3, chat_type: "image", chat_message: ["https://picsum.photos/id/13/1080/1920"], orientation: "竖", }, { id: 4, chat_type: "image", chat_message: ["https://picsum.photos/id/14/1440/900"], orientation: "横", }, { id: 5, chat_type: "image", chat_message: ["https://picsum.photos/id/15/900/1440"], orientation: "竖", }, { id: 6, chat_type: "image", chat_message: ["https://picsum.photos/id/16/1600/900"], orientation: "横", }, { id: 7, chat_type: "image", chat_message: ["https://picsum.photos/id/17/900/1600"], orientation: "竖", }, { id: 8, chat_type: "image", chat_message: ["https://picsum.photos/id/18/1280/720"], orientation: "横", }, { id: 9, chat_type: "image", chat_message: ["https://picsum.photos/id/19/720/1280"], orientation: "竖", }, { id: 10, chat_type: "image", chat_message: ["https://picsum.photos/id/20/1920/1080"], orientation: "横", }, { id: 11, chat_type: "image", chat_message: ["https://picsum.photos/id/21/1080/1920"], orientation: "竖", }, { id: 12, chat_type: "image", chat_message: ["https://picsum.photos/id/22/1440/900"], orientation: "横", }, { id: 13, chat_type: "image", chat_message: ["https://picsum.photos/id/23/900/1440"], orientation: "竖", }, { id: 14, chat_type: "image", chat_message: ["https://picsum.photos/id/24/1600/900"], orientation: "横", }, { id: 15, chat_type: "image", chat_message: ["https://picsum.photos/id/25/900/1600"], orientation: "竖", }, { id: 16, chat_type: "image", chat_message: ["https://picsum.photos/id/26/1280/720"], orientation: "横", }, { id: 17, chat_type: "image", chat_message: ["https://picsum.photos/id/27/720/1280"], orientation: "竖", }, { id: 18, chat_type: "image", chat_message: ["https://picsum.photos/id/28/1920/1080"], orientation: "横", }, { id: 19, chat_type: "image", chat_message: ["https://picsum.photos/id/29/1080/1920"], orientation: "竖", }, { id: 20, chat_type: "image", chat_message: ["https://picsum.photos/id/30/1440/900"], orientation: "横", }, { id: 21, chat_type: "image", chat_message: ["https://picsum.photos/id/31/900/1440"], orientation: "竖", }, { id: 22, chat_type: "image", chat_message: ["https://picsum.photos/id/32/1600/900"], orientation: "横", }, { id: 23, chat_type: "image", chat_message: ["https://picsum.photos/id/33/900/1600"], orientation: "竖", }, { id: 24, chat_type: "image", chat_message: ["https://picsum.photos/id/34/1280/720"], orientation: "横", }, { id: 25, chat_type: "image", chat_message: ["https://picsum.photos/id/35/720/1280"], orientation: "竖", }, { id: 26, chat_type: "image", chat_message: ["https://picsum.photos/id/36/1920/1080"], orientation: "横", }, { id: 27, chat_type: "image", chat_message: ["https://picsum.photos/id/37/1080/1920"], orientation: "竖", }, { id: 28, chat_type: "image", chat_message: ["https://picsum.photos/id/38/1440/900"], orientation: "横", }, { id: 29, chat_type: "image", chat_message: ["https://picsum.photos/id/39/900/1440"], orientation: "竖", }, ]); const gridRef = ref<HTMLElement | null>(null); let msnry: Masonry | null = null; import Masonry from "masonry-layout"; /** 初始化 Masonry */ const initMasonry = async () => { await nextTick(); if (!gridRef.value) return; msnry = new Masonry(gridRef.value, { itemSelector: ".i-c-card", gutter: 5, fitWidth: false, horizontalOrder: true, transitionDuration: 0, }); }; /** 重新布局(图片/视频加载后调用) */ const relayout = () => { msnry?.layout(); }; onMounted(initMasonry); // 数据变化后重新计算布局 watch( () => list.value, async () => { await nextTick(); if (msnry) { msnry.reloadItems(); msnry.layout(); } }, { deep: true }, ); </script> <template> <div class="img-container"> <div class="img-content" ref="gridRef"> <template v-for="item in list" :key="item.id"> <div class="i-c-card"> <div> <img v-if="item.chat_type == 'image'" :src="item.chat_message[0]" alt="Image" @load="relayout" /> </div> <div> <!--其他元素--> </div> </div> </template> </div> </div> </template> <style lang="scss" scoped> .img-container { height: calc(100vh - 60px); overflow-y: auto; margin-left: 20px; .img-content { width: 100%; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 2px; padding: 0px 0px; padding-bottom: 200px; // 滚动条 .i-c-card::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0); transition: all 0.25s ease; pointer-events: none; } .i-c-card:hover::after { background: rgba(0, 0, 0, 0.4); } .i-c-card:hover { transform: translateY(-3px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18), 0 4px 10px rgba(0, 0, 0, 0.12); } .i-c-card { overflow: hidden; display: flex; flex-direction: column; width: 224px; border-radius: 2px; margin-bottom: 6px; transition: transform 0.3s ease; img { cursor: pointer; width: 100%; height: auto; display: block; object-fit: cover; } } } } </style>

只能提供大概得代码模板,仅供参考

总结

1.先安装npm install masonry-layout

2.查看它的配置参数

3.代码实现

http://www.jsqmd.com/news/1067485/

相关文章:

  • 告别论文熬夜内耗,okbiye 细分式论文创作面板拆解全流程学术辅助逻辑
  • OpenClaw+GitHub Actions:自动生成 Action 配置、管理 GitHub 仓库
  • 026、多文件协同修改:跨文件的批量重构、依赖更新与一致性保障
  • Pearson(皮尔逊)相关系数是一种常用的统计指标,用于衡量两个连续型变量之间的线性相关程度
  • 猫抓插件:浏览器资源嗅探神器,一键捕获网页所有媒体文件
  • 02-数字孪生三大厂商2026最新技术布局深度解析
  • 2026年中AI圈观察:当“拼参数“不再是终点,企业的AI落地该看什么?
  • 2026企业大模型管理平台推荐 | 五家主流运营治理服务商对比+FAQ答疑
  • 收藏 | AI小白必看:从Prompt到Loop Engineering,解锁大模型落地新思路
  • 如何用WeChatMsg将微信聊天记忆变成永久数字财富?
  • NI Multisim 访问数据库失败的解决方法
  • 咸阳师范学子企业见习日记|一天打卡三家硬核企业,这才是实践课该有的样子!
  • 如何解锁Arduino-ESP32中隐藏的ESP32-C2支持:完整指南
  • 5个必装插件!让ComfyUI AI绘画工作流效率翻倍的终极指南
  • foobar2000终极美化指南:5分钟打造专业音乐播放界面
  • 射频网络分析仪(VNA)校准完成后,接入测试夹具测量数据失真原因及行业标准化解决方案
  • Mermaid Live Editor:5分钟掌握零代码图表制作的神器
  • FastANI 终极指南:3分钟掌握基因组相似性快速分析
  • 为什么孩子用过很多背单词App,单词还是背不牢
  • AI短剧创作平台源码,从剧本到成片
  • OpenArk深度解析:Windows系统内核级安全分析实战指南
  • 从零开始掌握SiYuan笔记:5个实用技巧让你的知识管理更高效
  • 鸿蒙ArkUI零基础入门:布局
  • 好用的佛山市电动伸缩门供应商
  • 2026年山东大学软件学院创新项目实训博客(八)
  • 计算机视觉(实训一)
  • FastANI:实现1000倍速度提升的微生物基因组相似性分析专业方案
  • 栈和堆for golang
  • IT Help Desk 自动化:哪些工作可以交给系统,哪些必须留给人
  • 2025-2026铝合金门窗行业十大品牌盘点