欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3實(shí)現(xiàn)圖片瀑布流展示效果實(shí)例代碼

 更新時(shí)間:2024年11月26日 08:32:12   作者:小碼哥(xmgcode88)  
這篇文章主要介紹了vue3實(shí)現(xiàn)圖片瀑布流展示效果的相關(guān)資料,該組件可以調(diào)整列數(shù)、支持懶加載、自定義每頁滾動(dòng)數(shù)量、高度和點(diǎn)擊效果,作者展示了組件的效果,并詳細(xì)說明了實(shí)現(xiàn)方法,包括組件的創(chuàng)建和依賴的工具庫,需要的朋友可以參考下

最近在研發(fā)AI副業(yè)項(xiàng)目平臺(tái),然后自己設(shè)計(jì)了一個(gè)瀑布流組件,可以隨意調(diào)整展示的列數(shù)、懶加載、每頁滾動(dòng)數(shù)量、高度、點(diǎn)擊效果等。

一、效果

先看看效果如何,如何隨意調(diào)整4列、5列、6列、N列展示。

二、實(shí)現(xiàn)方法

現(xiàn)建立components/waterfall/index.vue組件

<template>
  <div class="waterfall-container" ref="containerRef" @scroll="handleScroll">
    <div class="waterfall-list">
      <div
        class="waterfall-item"
        v-for="(item, index) in resultList"
        :key="index"
        :style="{
          width: `${item.width}px`,
          height: `${item.height}px`,
          transform: `translate3d(${item.x}px, ${item.y}px, 0)`,
        }"
      >
        <slot name="item" v-bind="item"></slot>
      </div>
      <div v-if="isEnd" class="no-more-data">暫無更多數(shù)據(jù)</div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, onUnmounted, watch } from "vue";
import { throttle, debounce } from "@/utils/waterfall/utils.js";

const props = defineProps({
  gap: {
    type: Number,
    default: 10,
  },
  columns: {
    type: Number,
    default: 3,
  },
  bottom: {
    type: Number,
    default: 0,
  },
  images: {
    type: Array,
    default: () => [],
  },
  fetchMoreImages: {
    type: Function,
    required: true,
  },
  isEnd: {
    type: Boolean,
    default: false,
  },
});

const containerRef = ref(null);
const cardWidth = ref(0);
const columnHeight = ref(new Array(props.columns).fill(0));
const resultList = ref([]);
const loading = ref(false);

const minColumn = computed(() => {
  let minIndex = -1,
    minHeight = Infinity;

  columnHeight.value.forEach((item, index) => {
    if (item < minHeight) {
      minHeight = item;
      minIndex = index;
    }
  });

  return {
    minIndex,
    minHeight,
  };
});

const handleScroll = throttle(() => {
  const { scrollTop, clientHeight, scrollHeight } = containerRef.value;
  const bottom = scrollHeight - clientHeight - scrollTop;
  if (bottom <= props.bottom && !props.isEnd) {
    !loading.value && props.fetchMoreImages();
  }
});

const getList = (list) => {
  return list.map((x, index) => {
    const cardHeight = Math.floor((x.height * cardWidth.value) / x.width);
    const { minIndex, minHeight } = minColumn.value;
    const isInit = index < props.columns && resultList.value.length < props.columns;
    if (isInit) {
      columnHeight.value[index] = cardHeight + props.gap;
    } else {
      columnHeight.value[minIndex] += cardHeight + props.gap;
    }

    return {
      width: cardWidth.value,
      height: cardHeight,
      x: isInit
        ? index % props.columns !== 0
          ? index * (cardWidth.value + props.gap)
          : 0
        : minIndex % props.columns !== 0
        ? minIndex * (cardWidth.value + props.gap)
        : 0,
      y: isInit ? 0 : minHeight,
      image: x,
    };
  });
};

const resizeObserver = new ResizeObserver(() => {
  handleResize();
});

const handleResize = debounce(() => {
  const containerWidth = containerRef.value.clientWidth;
  cardWidth.value =
    (containerWidth - props.gap * (props.columns - 1)) / props.columns;
  columnHeight.value = new Array(props.columns).fill(0);
  resultList.value = getList(resultList.value);
});

const init = () => {
  if (containerRef.value) {
    const containerWidth = containerRef.value.clientWidth;
    cardWidth.value =
      (containerWidth - props.gap * (props.columns - 1)) / props.columns;
    resultList.value = getList(props.images);
    resizeObserver.observe(containerRef.value);
  }
};

watch(() => props.images, (newImages) => {
  const newList = getList(newImages);
  resultList.value = [...resultList.value, ...newList];
});

onMounted(() => {
  init();
});

onUnmounted(() => {
  containerRef.value && resizeObserver.unobserve(containerRef.value);
});
</script>

<style lang="scss">
.waterfall {
  &-container {
    width: 100%;
    height: 100%;
    overflow-y: scroll;
    overflow-x: hidden;
  }

  &-list {
    width: 100%;
    position: relative;
  }
  &-item {
    position: absolute;
    left: 0;
    top: 0;
    box-sizing: border-box;
    transition: all 0.3s;
  }
  .no-more-data {
    text-align: center;
    padding: 20px;
    color: #999;
    font-size: 14px;
  }
}
</style>

其中@/utils/waterfall/utils.js如下

// 用于模擬接口請(qǐng)求
export const getRemoteData = (data = '獲取數(shù)據(jù)', time = 2000) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(`模擬獲取接口數(shù)據(jù)`, data)
            resolve(data)
        }, time)
    })
}

// 獲取數(shù)組隨機(jī)項(xiàng)
export const getRandomElement = (arr) => {
    var randomIndex = Math.floor(Math.random() * arr.length);
    return arr[randomIndex];
}

// 指定范圍隨機(jī)數(shù)
export const getRandomNumber = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

// 節(jié)流
export const throttle = (fn, time) => {
    let timer = null
    return (...args) => {
        if (!timer) {
            timer = setTimeout(() => {
                timer = null
                fn.apply(this, args)
            }, time)
        }
    }
}
// 防抖
export const debounce = (fn, time) => {
    let timer = null
    return (...args) => {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(this, args)
        }, time)
    }
}

調(diào)用組件

<template>
  <div>
    <div class="page-dall">
      <el-row>
        <el-col :span="6">
          <div class="inner">
            <div class="sd-box">
              <h2>DALL-E 創(chuàng)作中心</h2>
              <div>
                <el-form label-position="left">
                  <div style="padding-top: 10px">
                    <el-form-item :label-style="{ color: 'white' }" label="圖片尺寸">
                      <template #default>
                        <div>
                          <el-select v-model="selectedValue" @change="updateSize" style="width:176px">
                            <el-option label="1024*1024" value="1024*1024"/>
                            <el-option label="1972*1024" value="1972*1024"/>
                            <el-option label="1024*1972" value="1024*1972"/>
                          </el-select>
                        </div>
                      </template>
                    </el-form-item>
                  </div>

                  <div style="padding-top: 10px">
                    <div class="param-line">
                        <el-input
                            v-model="dalleParams.prompt"
                            :autosize="{ minRows: 4, maxRows: 6 }"
                            type="textarea"
                            ref="promptRef"
                            placeholder="請(qǐng)?jiān)诖溯斎肜L畫提示詞,系統(tǒng)會(huì)自動(dòng)翻譯中文提示詞,高手請(qǐng)直接輸入英文提示詞"
                        />
                      </div>
                  </div>
                </el-form>
              </div>
              <div class="submit-btn">
                <el-button color="#ffffff" :loading="loading" :dark="false" round @click="generate">
                  立即生成
                </el-button>
              </div>
            </div>
          </div>
        </el-col>
        <el-col :span="18">
          <div class="inner">
            <div class="right-box">
              <h2>創(chuàng)作記錄</h2>
              <div>
                <el-form label-position="left">
                   <div class="container">
                    <WaterFall :columns="columns" :gap="10" :images="images" :fetchMoreImages="fetchMoreImages" :isEnd="isEnd">
                      <template #item="{ image }">
                        <div class="card-box">
                          <el-image :src="image.url" @click="previewImg(image)" alt="waterfall image" fit="cover" style="width: 100%; height: 100%;cursor:pointer;" loading="lazy"></el-image>
                          



                        
                        </div>
                      </template>
                    </WaterFall>
                  </div>
                </el-form>
              </div>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
    <el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''"  :url-list="[previewURL]"/>
  </div>
</template>

<script lang="ts" setup>
import { ElUpload, ElImage, ElDialog, ElRow, ElCol, ElButton, ElIcon, ElTag, ElInput, ElSelect, ElTooltip, ElForm, ElFormItem, ElOption ,ElImageViewer} from "element-plus";
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
import feedback from "~~/utils/feedback";
import { useUserStore } from '@/stores/user';
import WaterFall from '@/components/waterfall/index.vue';
import * as xmgai from "~~/api/ai";

// 獲取圖片前綴
const config = useRuntimeConfig();
const filePrefix = config.public.filePrefix;

const router = useRouter();

const selectedValue = ref('1024*1024');

const previewURL = ref("")

const loading = ref(false);


// 請(qǐng)求參數(shù)
const dalleParams = reactive({
  size:"1024*1024",
  prompt: ""
});

// 創(chuàng)建繪圖任務(wù)
const promptRef = ref(null);


const updateSize = () => {
  dalleParams.size = selectedValue.value;
};


const generate = async () => {
  loading.value = true;
  if (dalleParams.prompt === '') {
    promptRef.value.focus();
    loading.value = false;
    return feedback.msgError("請(qǐng)輸入繪畫提示詞!");
    
  }

  const ctdata = await xmgai.dalle3(dalleParams);
  console.info("ctdata",ctdata);
  if (ctdata.code === 0) {
    feedback.msgError(ctdata.msg);
    loading.value = false;
    return [];
  }

  if (ctdata.code === 1) {
    // 獲取新生成的圖片地址
    const newImage = {
      url: filePrefix +  ctdata.data,
      width: 300 + Math.random() * 300,
      height: 400 + Math.random() * 300,
    };

    // 將新圖片插入到 images 數(shù)組的開頭
     // 將新圖片插入到 images 數(shù)組的開頭
    images.value = [newImage, ...images.value];

    // 將 WaterFall 組件的滾動(dòng)條滾動(dòng)到頂部
    nextTick(() => {
      const waterfallContainer = document.querySelector('.waterfall-container');
      if (waterfallContainer) {
        waterfallContainer.scrollTop = 0;
      }
    });

    feedback.msgSuccess(ctdata.msg);
    loading.value = false;
  }

};

const images = ref([]);
const pageNo = ref(1);
const pageSize = ref(10);
const isEnd = ref(false);

// 請(qǐng)求參數(shù)
const paramsCreate = reactive({
  aiType: "dalle3",
  pageNo: pageNo.value,
  pageSize: pageSize.value,
});

const fetchImages = async () => {
  const ctdata = await xmgai.aiList(paramsCreate);
  if (ctdata.code === 0) {
    feedback.msgError(ctdata.msg);
    return [];
  }

  if (ctdata.code === 1) {
    const data = ctdata.data.lists;
    if (data.length === 0) {
      isEnd.value = true;
      return [];
    }
    paramsCreate.pageNo++;
    return data.map(item => ({
      ...item, // 保留所有原始字段
      url: filePrefix + item.localUrls,
      width: 300 + Math.random() * 300,
      height: 400 + Math.random() * 300,
    }));
  }
};

const fetchMoreImages = async () => {
  if (isEnd.value) {
    return; // 如果已經(jīng)沒有更多數(shù)據(jù)了,直接返回
  }
  const newImages = await fetchImages();
  images.value = [...newImages];
};


// 列數(shù)設(shè)置
const columns = ref(4); // 你可以在這里修改列數(shù)
//放大預(yù)覽
const previewImg = (item) => {
  console.info("item",item.url);
  previewURL.value = item.url
}



onMounted(async () => {
  const initialImages = await fetchImages();
  images.value = initialImages;
});

</script>

<style scoped>
.page-dall {
  background-color: #0c1c9181;
  border-radius: 10px; /* 所有角的圓角大小相同 */
  border: 1px solid #3399FF;
}

.page-dall .inner {
  display: flex;
}

.page-dall .inner .sd-box {
  margin: 10px;
  background-color: #222542b4;
  width: 100%;
  padding: 10px;
  border-radius: 10px;
  color: #ffffff;
  font-size: 14px;
}

.page-dall .inner .sd-box h2 {
  font-weight: bold;
  font-size: 20px;
  text-align: center;
  color: #ffffff;
}

.page-dall .inner .right-box {
  margin: 10px;
  background-color: #222542b4;
  width: 100%;
  padding: 10px;
  border-radius: 10px;
  color: #ffffff;
  font-size: 14px;
}

.page-dall .inner .right-box h2 {
  font-weight: bold;
  font-size: 20px;
  text-align: center;
  color: #ffffff;
}

.submit-btn {
  padding: 10px 15px 0 15px;
  text-align: center;
}

::v-deep(.el-form-item__label) {
  color: white !important;
}

.container {
  height: 600px;
  border: 2px solid #000;
  margin-top: 10px;
  margin-left: auto;
  margin-right: auto; /* 添加居中處理 */
}

.card-box {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 4px;
  overflow: hidden;
}

.card-box img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.card-box .remove {
  display: none;
  position: absolute;
  right: 10px;
  top: 10px;
}

.card-box:hover .remove {
  display: block;
}
</style>

總結(jié) 

到此這篇關(guān)于vue3實(shí)現(xiàn)圖片瀑布流展示效果的文章就介紹到這了,更多相關(guān)vue3圖片瀑布流展示內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解vue+webpack+express中間件接口使用

    詳解vue+webpack+express中間件接口使用

    這篇文章主要介紹了詳解vue+webpack+express中間件接口使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • Vue實(shí)現(xiàn)contenteditable元素雙向綁定的方法詳解

    Vue實(shí)現(xiàn)contenteditable元素雙向綁定的方法詳解

    contenteditable是所有HTML元素都有的枚舉屬性,表示元素是否可以被用戶編輯。本文將詳細(xì)介紹如何實(shí)現(xiàn)contenteditable元素的雙向綁定,需要的可以參考一下
    2022-05-05
  • vue實(shí)現(xiàn)簡單的購物車小案例

    vue實(shí)現(xiàn)簡單的購物車小案例

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)簡單的購物車小案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • Vue路由history模式解決404問題的幾種方法

    Vue路由history模式解決404問題的幾種方法

    這篇文章主要介紹了Vue路由history模式解決404問題的幾種方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • 詳解vue axios用post提交的數(shù)據(jù)格式

    詳解vue axios用post提交的數(shù)據(jù)格式

    這篇文章主要介紹了詳解vue axios用post提交的數(shù)據(jù)格式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-08-08
  • vue響應(yīng)式原理與雙向數(shù)據(jù)的深入解析

    vue響應(yīng)式原理與雙向數(shù)據(jù)的深入解析

    Vue 最獨(dú)特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。下面這篇文章主要給大家介紹了關(guān)于vue響應(yīng)式原理與雙向數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • Vue3.2單文件組件setup的語法糖與新特性總結(jié)

    Vue3.2單文件組件setup的語法糖與新特性總結(jié)

    ue3上線已經(jīng)很久了,許多小伙伴應(yīng)該都已經(jīng)使用過vue3了,下面這篇文章主要給大家介紹了關(guān)于Vue3.2單文件組件setup的語法糖與新特性總結(jié)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • 關(guān)于Vue中echarts響應(yīng)式頁面變化resize()的用法介紹

    關(guān)于Vue中echarts響應(yīng)式頁面變化resize()的用法介紹

    Vue項(xiàng)目中開發(fā)數(shù)據(jù)大屏,使用echarts圖表根據(jù)不同尺寸的屏幕進(jìn)行適配,resize()可以調(diào)用echarts中內(nèi)置的resize函數(shù)進(jìn)行自適應(yīng)縮放,本文將給大家詳細(xì)介紹resize()的用法,需要的朋友可以參考下
    2023-06-06
  • vue項(xiàng)目中使用iconfont方式

    vue項(xiàng)目中使用iconfont方式

    這篇文章主要介紹了vue項(xiàng)目中使用iconfont方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Vue 調(diào)試訪問本地后端接口配置

    Vue 調(diào)試訪問本地后端接口配置

    記錄一下本地測試前端的時(shí)候怎么訪問本地后端接口,文中給大家提到了vue如何做調(diào)試后臺(tái)接口的配置和proxy的工作原理以及為什么能解決跨域,感興趣的朋友跟隨小編一起看看吧
    2023-06-06

最新評(píng)論