Vue CLI 3项目中实现Masonry布局并封装为组件的技术咨询
Got it, let's break down how to wrap MasonryJS with the GridLoaderFx animation into a reusable Vue component—this is totally doable, and I'll walk you through each step with concrete code examples.
First, make sure you've got the necessary packages installed. Open your terminal and run:
npm install masonry-layout imagesloaded --save
We use imagesloaded to ensure Masonry calculates layout correctly after all images in your grid have loaded (no more wonky waterfall positioning!).
WaterfallGrid Component Create a new file called WaterfallGrid.vue in your components directory. Here's the complete code with comments explaining each part:
<template> <div class="waterfall-grid-wrapper"> <!-- GridLoaderFx Loading Animation --> <div v-if="isLoading" class="grid-loader"> <div class="grid-loader__item" v-for="i in 12" :key="i"></div> </div> <!-- Masonry Layout Container --> <div ref="masonryContainer" class="masonry-grid" v-show="!isLoading"> <!-- Slot lets parent component pass custom grid items --> <slot></slot> </div> </div> </template> <script> import Masonry from 'masonry-layout' import imagesLoaded from 'imagesloaded' export default { name: 'WaterfallGrid', props: { // Control loader visibility from parent (e.g., during data fetch) isLoading: { type: Boolean, default: true }, // Customizable Masonry options (tweak from parent as needed) masonryOptions: { type: Object, default: () => ({ itemSelector: '.masonry-item', columnWidth: '.masonry-item', gutter: 16 }) } }, data() { return { masonryInstance: null } }, mounted() { // Initialize Masonry once loader is hidden and content is ready this.$watch('isLoading', (newVal) => { if (!newVal) this.initMasonry() }, { immediate: true }) // Re-calculate layout when window resizes window.addEventListener('resize', this.handleResize) }, beforeUnmount() { // Clean up to prevent memory leaks window.removeEventListener('resize', this.handleResize) if (this.masonryInstance) this.masonryInstance.destroy() }, methods: { initMasonry() { const container = this.$refs.masonryContainer if (!container) return // Wait for all images to load before initializing Masonry imagesLoaded(container, () => { this.masonryInstance = new Masonry(container, this.masonryOptions) }) }, handleResize() { if (this.masonryInstance) this.masonryInstance.layout() }, // Expose method to manually refresh layout (useful for dynamic content updates) reloadLayout() { if (this.masonryInstance) { this.masonryInstance.reloadItems() this.masonryInstance.layout() } } } } </script> <style scoped> /* GridLoaderFx Animation Styles */ .grid-loader { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; padding: 20px; } .grid-loader__item { background-color: #f0f0f0; border-radius: 8px; animation: pulse 1.5s infinite ease-in-out; } /* Match your masonry item height variations for realistic loader */ .grid-loader__item:nth-child(1), .grid-loader__item:nth-child(4), .grid-loader__item:nth-child(7), .grid-loader__item:nth-child(10) { height: 250px; } .grid-loader__item:nth-child(2), .grid-loader__item:nth-child(5), .grid-loader__item:nth-child(8), .grid-loader__item:nth-child(11) { height: 300px; } .grid-loader__item:nth-child(3), .grid-loader__item:nth-child(6), .grid-loader__item:nth-child(9), .grid-loader__item:nth-child(12) { height: 200px; } @keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } } /* Masonry Grid Base Styles */ .masonry-grid { padding: 20px; } .masonry-item { margin-bottom: 16px; border-radius: 8px; overflow: hidden; } </style>
Now you can import and use this component anywhere in your Vue app. Here's an example of how to integrate it into a gallery page:
<template> <div class="photo-gallery-page"> <WaterfallGrid :is-loading="loading" :masonry-options="customMasonryOpts" ref="waterfallGrid" > <div class="masonry-item" v-for="item in galleryItems" :key="item.id"> <img :src="item.imageUrl" alt="Gallery item" /> <p class="item-caption">{{ item.caption }}</p> </div> </WaterfallGrid> </div> </template> <script> import WaterfallGrid from '@/components/WaterfallGrid.vue' export default { components: { WaterfallGrid }, data() { return { loading: true, galleryItems: [], customMasonryOpts: { itemSelector: '.masonry-item', columnWidth: 220, gutter: 20 } } }, async mounted() { // Simulate data fetch (replace with your actual API call) await new Promise(resolve => setTimeout(resolve, 2000)) this.galleryItems = [ { id: 1, imageUrl: '/path/to/image1.jpg', caption: 'Sunset over mountains' }, { id: 2, imageUrl: '/path/to/image2.jpg', caption: 'Urban street art' }, { id: 3, imageUrl: '/path/to/image3.jpg', caption: 'Coastal waves' }, // Add more items as needed ] this.loading = false // Optional: If you add items dynamically later, call this to refresh layout // this.$refs.waterfallGrid.reloadLayout() } } </script> <style scoped> .masonry-item img { width: 100%; height: auto; display: block; } .item-caption { padding: 10px; background-color: #fff; margin: 0; } </style>
- Loader Tweaks: Adjust the
grid-loaderstyles (grid columns, item heights, animation speed) to match your app's design system. - Masonry Behavior: Pass different
masonry-optionsfrom the parent to tweak gutter size, column width, or enable features like fit-width. - Dynamic Content: If you add/remove items after the initial load, use the
reloadLayout()method via$refsto update the waterfall layout.
内容的提问来源于stack exchange,提问作者Momin

