vue3实现图片瀑布流(基于Masonry实现)
前言
需要实现一个图片+视频的瀑布流效果,使用了vue3+Masonry库进行实现,这是一个关于调用AI提供接口(聚合起来)再实现一个二次AI的平台
效果
模板一
模板二
开始
1.安装
npm install masonry-layout2.代码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.代码实现
