)
Vue2瀑布流Waterfall瀑布流_百度百科可自定義設(shè)置以下屬性圖片數(shù)組images類(lèi)型Image[]默認(rèn) []要?jiǎng)澐值牧袛?shù)columnCount類(lèi)型number默認(rèn) 3各列之間的間隙columnGap類(lèi)型number單位 px默認(rèn) 30瀑布流區(qū)域的總寬度width類(lèi)型number | string單位 px默認(rèn) 100%瀑布流區(qū)域和圖片圓角borderRadius類(lèi)型number單位 px默認(rèn) 8瀑布流區(qū)域背景填充色backgroundColor類(lèi)型string默認(rèn) #F2F4F8Spin 組件屬性配置spinProps類(lèi)型object默認(rèn) {}參考 Spin Props用于配置圖片加載中樣式效果如下圖在線預(yù)覽在線預(yù)覽①創(chuàng)建瀑布流組件Waterfall.vue其中引用使用了以下組件和工具函數(shù)Vue3加載中Spin監(jiān)聽(tīng)DOM尺寸 useResizeObserverscript setup langts import { ref, computed, onMounted, watch } from vue import Spin from components/spin import { useResizeObserver } from components/utils /* 寬度固定圖片等比例縮放使用JS獲取每張圖片寬度和高度結(jié)合 relative 和 absolute 定位 計(jì)算每個(gè)圖片的位置 topleft保證每張新的圖片都追加在當(dāng)前高度最小的那列末尾 */ export interface Image { name?: string // 圖片名稱(chēng) src: string // 圖片地址 link?: string // 圖片跳轉(zhuǎn)鏈接 target?: _self | _blank // 如何打開(kāi)跳轉(zhuǎn)鏈接 } export interface Props { images?: Image[] // 圖片數(shù)組 columnCount?: number // 要?jiǎng)澐值牧袛?shù) columnGap?: number // 各列之間的間隙單位 px width?: string | number // 瀑布流區(qū)域的總寬度單位 px borderRadius?: number // 瀑布流區(qū)域和圖片圓角單位 px backgroundColor?: string // 瀑布流區(qū)域背景填充色 spinProps?: object // Spin 組件屬性配置參考 Spin Props用于配置圖片加載中樣式 } const props withDefaults(definePropsProps(), { images: () [], columnCount: 3, columnGap: 20, width: 100%, borderRadius: 8, backgroundColor: #F2F4F8, spinProps: () ({}) }) const waterfallRef ref() // 瀑布流容器引用 const waterfallWidth refnumber(0) // 瀑布流區(qū)域?qū)挾?const imagesLoaded refboolean[]([]) // 圖片是否加載完成 const imagesSize ref{ width: number; height: number }[]([]) // 所有圖片原始尺寸 const imagesProperty ref{ width: number; height: number; top: number; left: number }[]([]) // 瀑布流圖片的寬高和位置信息 const preColumnHeight refnumber[]([]) // 每列的高度 const flag refnumber(0) // 更新瀑布流標(biāo)識(shí) // 瀑布流區(qū)域總寬度 const totalWidth computed(() { if (typeof props.width number) { return ${props.width}px } else { return props.width } }) // 瀑布流區(qū)域總高度 const totalHeight computed(() { return Math.max(...preColumnHeight.value) props.columnGap }) // 每列的圖片寬度 const imageWidth computed(() { return (waterfallWidth.value - (props.columnCount 1) * props.columnGap) / props.columnCount }) // 圖片總數(shù)量 const imagesAmount computed(() { return props.images.length }) watch( () props.images, (to, from) { // 只有當(dāng)圖片數(shù)組長(zhǎng)度變化或圖片文件變化時(shí)才清空?qǐng)D片尺寸數(shù)組并重新初始化 if (to.length ! to.length || to.some((image, index) image.src ! from[index]?.src)) { imagesSize.value [] initWaterfall() } }, { deep: true, flush: post } ) watch( [() props.columnCount, () props.columnGap, () props.width], () { initWaterfall() }, { flush: post } ) onMounted(() { initWaterfall() }) // 窗口寬度改變時(shí)重新計(jì)算瀑布流布局 useResizeObserver(waterfallRef, () { const currentWidth waterfallRef.value.offsetWidth if (props.images.length currentWidth ! waterfallWidth.value) { initWaterfall() } }) // 初始化瀑布流 function initWaterfall() { if (!waterfallRef.value) return waterfallWidth.value waterfallRef.value.offsetWidth preColumnHeight.value Array(props.columnCount).fill(0) imagesProperty.value.splice(0) flag.value updateWaterfall(flag.value) } // 更新瀑布流 async function updateWaterfall(symbol: number): Promiseboolean | void { for (let i 0; i imagesAmount.value; i) { if (symbol flag.value) { if (!imagesSize.value[i]) { await loadImage(props.images[i].src, i) } else { getImagesProperty(i) } } else { return false } } } // 加載圖片并計(jì)算寬高和位置信息 function loadImage(url: string, n: number): void | Promisestring { const loadedImageSize imagesSize.value[n] if (loadedImageSize) { // 圖片已被加載過(guò) getImagesProperty(n) } else { // 圖片未被加載過(guò) return new Promise((resolve, reject) { const image new Image() image.src url // 圖片加載完成后通過(guò) image.naturalWidth 和 image.naturalHeight 獲取圖片原始寬高 image.onload function () { imagesSize.value[n] { // 保存已加載圖片的尺寸信息 width: image.naturalWidth, height: image.naturalHeight } getImagesProperty(n) resolve(loaded) } image.onerror function (err) { reject(new Error(Failed to load image at ${url}, err: ${err})) } }) } } // 獲取并存儲(chǔ)實(shí)際渲染圖片的寬高和位置信息 function getImagesProperty(n: number): void { const loadedImageSize imagesSize.value[n] const height loadedImageSize.height / (loadedImageSize.width / imageWidth.value) imagesProperty.value[n] { width: imageWidth.value, height: height, ...getPosition(n, height) } } // 獲取圖片位置信息 function getPosition(i: number, height: number): { top: number; left: number } { if (i props.columnCount) { preColumnHeight.value[i] props.columnGap height return { top: props.columnGap, left: (imageWidth.value props.columnGap) * i props.columnGap } } else { const top Math.min(...preColumnHeight.value) let index 0 for (let n 0; n props.columnCount; n) { if (preColumnHeight.value[n] top) { index n break } } preColumnHeight.value[index] top props.columnGap height return { top: top props.columnGap, left: (imageWidth.value props.columnGap) * index props.columnGap } } } function onLoaded(index: number): void { imagesLoaded.value[index] true } // 從圖像地址 src 中獲取圖像名稱(chēng) function getImageName(image: Image): string { if (image) { if (image.name) { return image.name } else { const res image.src.split(?)[0].split(/) return res[res.length - 1] } } return } /script template div refwaterfallRef classwaterfall-wrap :style --waterfall-border-radius: ${borderRadius}px; --waterfall-bg-color: ${backgroundColor}; --waterfall-width: ${totalWidth}; --waterfall-height: ${totalHeight}px; Spin classwaterfall-image :stylewidth: ${property.width}px; height: ${property.height}px; top: ${property property.top}px; left: ${property property.left}px; :spinning!imagesLoaded[index] sizesmall indicatordynamic-circle v-bindspinProps v-for(property, index) in imagesProperty :keyindex a classimage-link :class{ link-cursor: images[index].link } :hrefimages[index].link :targetimages[index].target ? images[index].target : _blank img classimage-item :srcimages[index].src :altgetImageName(images[index]) loadonLoaded(index) / /a /Spin /div /template style langless scoped .waterfall-wrap { position: relative; width: var(--waterfall-width); height: var(--waterfall-height); border-radius: var(--waterfall-border-radius); background-color: var(--waterfall-bg-color); .waterfall-image { position: absolute; .image-link { display: block; height: 100%; cursor: default; .image-item { width: 100%; height: 100%; border-radius: var(--border-radius); display: inline-block; vertical-align: bottom; } } .link-cursor { cursor: pointer; } } } /style②在要使用的頁(yè)面引入其中引入使用了以下組件Vue3 柵格GridVue3彈性布局FlexVue3 滑動(dòng)輸入條SliderVue3顏色選擇器ColorPickerscript setup langts import Waterfall from ./Waterfall.vue import { ref, onBeforeMount, reactive } from vue import type { WaterfallImage } from vue-amazing-ui const images refWaterfallImage[]([]) const state reactive({ columnCount: 3, columnGap: 20, backgroundColor: #e6f4ff, borderRadius: 12 }) function loadImages() { for (let i 1; i 10; i) { images.value.push({ name: image-${i}, src: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/${i}.jpg // link: https://cdn.jsdelivr.net/gh/themusecatcher/resources0.1.2/${i}.jpg }) } } onBeforeMount(() { // 組件已完成響應(yīng)式狀態(tài)設(shè)置但未創(chuàng)建DOM節(jié)點(diǎn) loadImages() }) /script template div h1{{ $route.name }} {{ $route.meta.title }}/h1 h2 classmt30 mb10基本使用/h2 Waterfall :imagesimages / h2 classmt30 mb10瀑布流配置器/h2 Row :gutter24 Col :span6 Flex vertical gapmiddle columnCount: Slider :min1 :max6 v-model:valuestate.columnCount / /Flex /Col Col :span6 Flex vertical gapmiddle columnGap: Slider :min10 :max100 v-model:valuestate.columnGap / /Flex /Col Col :span6 Flex vertical gapmiddle borderRadius: Slider :min0 :max100 v-model:valuestate.borderRadius / /Flex /Col Col :span6 Flex vertical backgroundColor: ColorPicker v-model:valuestate.backgroundColor / /Flex /Col /Row Waterfall classmt30 :imagesimages :column-countstate.columnCount :column-gapstate.columnGap :background-colorstate.backgroundColor :border-radiusstate.borderRadius / /div /template