您现在的位置是:首页 >学无止境 >Vue3瀑布流(Waterfall)网站首页学无止境

Vue3瀑布流(Waterfall)

theMuseCatcher 2023-05-22 04:00:02
简介Vue3瀑布流(Waterfall)

Vue2瀑布流(Waterfall) 

可自定义设置以下属性:

  • 图片数组(images),类型:Array<{title: string, src: string}>,默认 []

  • 要划分的列数(columnCount),类型:number,默认 3

  • 各列之间的间隙(columnGap),类型:number,单位px,默认 30

  • 瀑布流区域的总宽度(width),类型:number | string,默认 '100%'

  • 瀑布流区域背景填充色(backgroundColor),类型:string,默认 '#F2F4F8'

  • 瀑布流排列方式(mode)类型:string,默认 'JS',可选:JS(js计算) CSS(css布局)

效果如下图:

JS计算模式(mode: 'JS')

CSS布局(mode: 'CSS')

①创建瀑布流组件Waterfall.vue:

<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue'
/*
  mode: JS
  使用js进行计算,新的图片每次都添加在最短那列的末尾
  mode: CSS
  纯CSS,实现简单,但图片顺序是每列从上往下排列
*/
interface Image {
  title: string // 图片名称
  src: string // 图片地址
}
interface Props {
  images: Image[] // 图片数组
  columnCount?: number // 要划分的列数
  columnGap?: number // 各列之间的间隙
  width?: string|number // 瀑布流区域的总宽度
  backgroundColor?: string // 瀑布流区域背景填充色
  mode?: string // 瀑布流排列方式,可选:JS(js计算) CSS(css布局)
}
const props = withDefaults(defineProps<Props>(), {
  images: () => [],
  columnCount: 3,
  columnGap: 30,
  width: '100%',
  backgroundColor: '#F2F4F8',
  mode: 'JS'
})
const totalWidth = computed(() => {
  if (typeof props.width === 'number') {
    return props.width + 'px'
  } else {
    return props.width
  }
})

const imagesProperty = ref<any[]>([])
const preColumnHeight = ref<number[]>([]) // 每列的高度
const waterfall = ref()
const imageWidth = ref()
const height = computed(() =>{
  return Math.max(...preColumnHeight.value) + props.columnGap
})
watch(
() => props.images,
(to) => {
  if (to.length && props.mode === 'JS') {
    onPreload()
  }
})
onMounted(() => {
  if (props.images.length && props.mode === 'JS') {
    onPreload()
  }
})

function getPosition (i: number, height: number) { // 获取图片位置信息(top,left)
  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)
    var index = 0
    for (let n = 0; n < preColumnHeight.value.length; 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 onLoad (url: string, i: number) {
  return new Promise((resolve) => {
    const image = new Image()
    image.src = url
    image.onload = function () { // 图片加载完成时执行,此时可通过image.width和image.height获取到图片原始宽高
      var height = image.height / (image.width / imageWidth.value)
      imagesProperty.value[i] = { // 存储图片宽高和位置信息
        width: imageWidth.value,
        height: height,
        ...getPosition(i, height)
      }
      resolve('load')
    }
  })
}
async function onPreload () { // 计算图片宽高和位置(top,left)
  // 计算每列的图片宽度
  imageWidth.value = (waterfall.value.offsetWidth - (props.columnCount + 1) * props.columnGap) / props.columnCount
  const len = props.images.length
  imagesProperty.value.splice(len)
  for (let i = 0; i < len; i++) {
    await onLoad(props.images[i].src, i)
  }
}
</script>
<template>
  <div v-if="mode==='JS'" v-bind="$attrs" class="m-waterfall-js" ref="waterfall" :style="`background-color: ${backgroundColor}; width: ${totalWidth}; height: ${height}px;`">
    <img
      class="u-img"
      v-for="(property, index) in imagesProperty"
      :key="index"
      :style="`width: ${imageWidth}px; top: ${property && property.top}px; left: ${property && property.left}px;`"
      :src="images[index].src"
      :title="images[index].title"
      :alt="images[index].title" />
  </div>
  <div v-if="mode==='CSS'" v-bind="$attrs" class="m-waterfall-css" :style="`background: ${backgroundColor}; width: calc(${totalWidth} - ${2 * props.columnGap}px); padding: ${columnGap}px; column-count: ${columnCount}; column-gap: ${columnGap}px;`">
    <div class="m-img" :style="`margin-bottom: ${columnGap}px;`" v-for="(item, index) in images" :key="index">
      <img class="u-img" :src="item.src" :title="item.title" :alt="item.title" />
    </div>
  </div>
</template>
<style lang="less" scoped>
.m-waterfall-css {
  .m-img {
    .u-img {
      width: 100%;
      vertical-align: bottom;
    }
  }
}
.m-waterfall-js {
  position: relative;
  .u-img {
    position: absolute;
    display: inline-block;
    object-fit: contain;
    vertical-align: bottom;
  }
}
</style>

②在要使用的页面引入: 

<script setup lang="ts">
import { Waterfall } from './Waterfall.vue'
import { ref, onBeforeMount } from 'vue'
import { getImageUrl } from '@/utils/util'

const images = ref<any[]>([])

function loadImages () {
  for (let i = 1; i <= 10; i++) {
    images.value.push({
      title: `image-${i}`,
      src: getImageUrl(i)
    })
  }
  console.log(images.value)
}
onBeforeMount(() => { // 组件已完成响应式状态设置,但未创建DOM节点
  loadImages()
})
</script>
<template>
  <div>
    <h2 class="mb10">Waterfall 瀑布流基本使用 (默认使用JS计算进行布局展示)</h2>
    <Waterfall
      :images="images"
      :columnCount="3"
      :columnGap="30"
      :width="1100"
      mode="JS"
      backgroundColor="#F2F4F8" />
    <h2 class="mt30 mb10">瀑布流使用CSS布局展示 (mode: CSS)</h2>
    <Waterfall
      :images="images"
      :columnCount="3"
      :columnGap="30"
      :width="1100"
      mode="CSS"
      backgroundColor="#F2F4F8" />
  </div>
</template>
<style lang="less" scoped>
</style>
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。