Files
log-lottery/src/components/Waterfall/index.vue
2026-01-04 11:21:49 +08:00

114 lines
3.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { Ref } from 'vue'
import { throttle } from 'lodash-es' // lodash-es 节流
import Masonry from 'masonry-layout'
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
// 布局参数 Props
interface MasonryWaterfallProps {
columnWidth?: number | string // 列宽px/选择器)
gutter?: number // 列/行间距
fitWidth?: boolean // 容器宽度自适应居中
}
// 默认参数
const props = withDefaults(defineProps<MasonryWaterfallProps>(), {
columnWidth: 120,
gutter: 16,
fitWidth: true,
})
// Vue Ref 管理 DOM 容器和 masonry 实例
const masonryContainer: Ref<HTMLDivElement | null> = ref(null)
const masonryInstance: Ref<Masonry | null> = ref(null)
// 初始化 masonry仅执行一次因卡片固定
async function initMasonry() {
if (!masonryContainer.value)
return
// 等待插槽内容(固定卡片)完全渲染
await nextTick()
// 初始化 masonry 实例(固定卡片无需销毁旧实例)
masonryInstance.value = new Masonry(masonryContainer.value, {
itemSelector: '.masonry-container > *', // 匹配所有固定子项
columnWidth: props.columnWidth,
gutter: props.gutter,
fitWidth: props.fitWidth,
initLayout: true, // 固定卡片直接初始化布局
})
}
// 刷新布局(仅用于卡片内部内容高度变化)
async function refreshLayout() {
await nextTick()
if (masonryInstance.value) {
masonryInstance.value.layout?.()
}
}
// 窗口缩放节流重排(优化性能)
const handleResize = throttle(() => {
if (masonryInstance.value) {
masonryInstance.value.layout?.()
}
}, 300)
// 生命周期:挂载时初始化,卸载时清理
onMounted(async () => {
await initMasonry()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 销毁实例 + 释放内存
if (masonryInstance.value) {
masonryInstance.value.destroy?.()
masonryInstance.value = null
}
// 移除监听 + 取消节流任务
window.removeEventListener('resize', handleResize)
handleResize.cancel()
})
// 仅暴露刷新方法(适配卡片内部内容变化)
defineExpose({ refreshLayout })
</script>
<template>
<!-- masonry 容器ref 绑定接收固定插槽内容 -->
<div ref="masonryContainer" class="masonry-container">
<!-- 插槽直接传入固定的卡片/组件 -->
<slot />
</div>
</template>
<style scoped>
.masonry-container {
width: 100%;
/* max-width: 1400px; */
margin: 0 auto;
padding: 20px;
box-sizing: border-box;
}
/* 固定卡片基础样式 */
.masonry-container > * {
margin-bottom: v-bind('`${gutter}px`');
box-sizing: border-box;
break-inside: avoid; /* 兼容 Safari */
min-height: 100px; /* 避免内容过矮导致布局异常 */
}
/* 响应式适配:小屏调整列宽 */
/* @media (max-width: 768px) {
.masonry-container {
padding: 10px;
}
.masonry-container > * {
width: calc(50% - v-bind('`${gutter}px`')) !important;
}
} */
</style>