文章目录
- 前言
- 一、效果
- 二、使用代码
- 三、核心代码
- 总结
前言
最近做项目需要实现uni-app、H5实现瀑布流效果封装,网上搜索有很多的例子,但是代码都是不够完整的,下面来封装一个uni-app、H5都能用的代码。在小程序中,一个个item渲染可能出现问题,也通过加锁来解决问题。
一、效果
1、下面看一下实现的效果,我这里的商品图片是正方形是固定大小的,如果你想要图片不同效果,也是可以适配的。
二、使用代码
1、下面是封装的组件如何使用
<TBodyrefresher:data="goodsList":is-end="isEnd":is-loading="isLoading":is-refreshing="isRefreshing"@refresh="reset"@lower="fetchGoodsNextPage"><TTMultiColumnListclass="bg-#fafafa goods"column-gap="16rpx":list="[]":column-size="2"@ready="updateColumnOperator"><template #default="{ data, index }"><viewclass="items_content">//这个是你的商品item,自己封装<TTGoodsCellPure:key="index":obj="data"arrangement="imageCenter"@click-item="onClickItem"/></view></template></TTMultiColumnList></TBody>
2、关键是updateColumnOperator方法,需要请求数据的时候把数据放进去渲染。
const goodsListQuery = {limit: 30,offset: undefined as string | undefined,
}
const isLoading = ref(false)
const goodsList = ref<Array<any>>([])
const isEnd = ref(false)
const isRefreshing = ref(false)// 获取商品列表
async function fetchGoodsList(options: { offset?: string; limit?: number } = {}) {const { offset, limit = goodsListQuery.limit } = options//接口自己替换自己的const { data } = await $apis.xxxxxx({categoryId: categoryId.value === -1 ? undefined : categoryId.value,keyword: '',offset,limit,})return { offset: data?.offset, list: data?.list ?? [] }
}// 获取商品列表
async function fetchGoodsPage() {if (isLoading.value || isEnd.value)returntry {goodsListQuery.offset = undefinedisLoading.value = trueconst { list, offset } = await fetchGoodsList({ offset: goodsListQuery.offset })if (list?.length) {goodsList.value = listif (list.length < goodsListQuery.limit)isEnd.value = true}else {isEnd.value = true}goodsListQuery.offset = offsetnextTick(() => {columnOperator?.reset(list)})}finally {isLoading.value = false}
}//下一页
async function fetchGoodsNextPage() {if (isLoading.value || isEnd.value)returntry {isLoading.value = trueisRefreshing.value = trueconst { list, offset } = await fetchGoodsList({ offset: goodsListQuery.offset })if (list?.length) {goodsList.value.push(...list)if (list.length < goodsListQuery.limit)isEnd.value = true}else {isEnd.value = true}goodsListQuery.offset = offsetcolumnOperator?.append(list)}finally {isRefreshing.value = falseisLoading.value = false}
}
三、核心代码
1、核心代码TTMultiColumnList代码
<script lang="ts" setup>
import type { Ref } from 'vue'
import { getCurrentInstance, nextTick, onMounted, ref } from 'vue'
import type { ColumnItem, ColumnOperator, ColumnOperatorPredictor, ListItem } from '@/utils/multiColumn'const props = withDefaults(defineProps<{list: Array<ListItem>columnSize: numbercolumnGap: stringrowGap: string}>(),{columnSize: 2,columnGap: 'normal',rowGap: 'normal',},
)const emit = defineEmits<{(e: 'ready', operator: ColumnOperator): void
}>()function range(count: number) {return Array.from({ length: count }, (_, i) => i)
}function getEmptyColumns(columnSize: number) {return range(columnSize).map(() => [])
}let appendColumnDataPromise = Promise.resolve(true)
const columns = ref<Array<Array<ColumnItem>>>(getEmptyColumns(props.columnSize))
const ctx = getCurrentInstance()
const columnRefs: Ref<Array<() => Promise<number>>> = computed(() => columns.value.map((_, i) => () => new Promise((resolve, reject) => {const className = `.s_${i}_ccList`// #ifdef H5const rect = document.querySelector(className)?.getBoundingClientRect()resolve(rect?.height || 0 as number)// #endif// #ifndef H5uni.createSelectorQuery().in(ctx).select(className).boundingClientRect().exec(([rect]) => {resolve(rect.height as number)})// #endif
})))// 获取高度最小一列的索引
async function getMinHeightColumnIndex(): Promise<number> {const columnHeights = await Promise.all(columnRefs.value.map(async (getHeight, index) => ({ height: await getHeight(), index })))return columnHeights.reduce((index, item, i) => {const height = columnHeights[index].heightconst siblingHeight = item.heightreturn siblingHeight < height ? i : index}, 0)
}// 将元素一个一个地插入到高度最小的一列
async function gradientAppendToColumn(startIndex: number, list: Array<ListItem>) {if (startIndex >= list.length)return falseconst targetColumnIndex = await getMinHeightColumnIndex()const item = { index: startIndex, data: list[startIndex] }const targetColumn = columns.value[targetColumnIndex]if (Array.isArray(targetColumn))targetColumn.push(item)else columns.value[targetColumnIndex] = [item]// render next itemreturn await new Promise((resolve) => {nextTick(async () => {// #ifndef H5// 解决小程序渲染问题await new Promise(resolve => nextTick(() => resolve(true)))// #endifawait gradientAppendToColumn(startIndex + 1, list)resolve(true)})})
}async function appendColumnDataInQueue(list: Array<ListItem>) {// 解决小程序渲染问题const oldAppendColumnDataPromise = appendColumnDataPromiseappendColumnDataPromise = new Promise((resolve) => {const cb = () => {appendColumnData(list).then(() => resolve(true)).catch(() => resolve(false))}oldAppendColumnDataPromise.then(() => cb()).catch(() => cb())})return appendColumnDataPromise
}async function appendColumnData(list: Array<ListItem>): Promise<boolean> {return await new Promise((resolve) => {nextTick(async () => {await gradientAppendToColumn(0, list)resolve(true)})})
}// 重置
async function resetColumnData(list?: Array<ListItem>): Promise<void> {if (list) {await appendColumnDataInQueue([])columns.value = getEmptyColumns(props.columnSize)await appendColumnDataInQueue(list)}
}// 移除元素
function removeColumnData(fn: (v: any) => boolean) {const staled = [] as Array<{ row: number; col: number }>columns.value.forEach((cols, colIndex) => {cols.forEach((d, rowIndex) => {if (fn(d.data))staled.push({ row: rowIndex, col: colIndex })})})staled.forEach(({ row, col }) => {columns.value[col].splice(row, 1)})
}// 更新元素
function updateColumnData(fn: ColumnOperatorPredictor, data: ListItem) {let done = falsefor (let col = 0; col < columns.value.length; col++) {if (done)breakconst rows = columns.value[col]for (let row = 0; row < rows.length; row++) {if (fn(rows[row].data)) {rows[row] = { index: rows[row].index, data }done = truebreak}}}
}onMounted(() => resetColumnData(props.list))emit('ready', {append: appendColumnDataInQueue,reset: resetColumnData,remove: removeColumnData,update: updateColumnData,
})
</script><template><view:style="{'display': 'grid','grid-template-columns': `repeat(${columns.length}, 1fr)`,'column-gap': props.columnGap,'row-gap': props.rowGap,'padding-left': '18rpx','padding-right': '18rpx','margin-top': '16rpx',}"><viewv-for="(rows, colIndex) in columns":key="colIndex"><view:key="`${colIndex}_list`":class="`s_${colIndex}_ccList`"><viewv-for="(row, rowIndex) in rows":key="`${colIndex}_${rowIndex}`"><slot:data="row.data":index="row.index":column-index="colIndex":row-index="rowIndex"/></view></view></view></view>
</template>
2、核心代码multiColumn代码
export type ListItem = unknownexport interface ColumnItem {index: numberdata: ListItem
}export type ColumnOperatorPredictor = (item: ListItem) => booleanexport interface ColumnOperator {readonly append: (list: Array<ListItem>) => voidreadonly remove: (predict: ColumnOperatorPredictor) => voidreadonly update: (predict: ColumnOperatorPredictor, data: ListItem) => voidreadonly reset: (list?: Array<ListItem>) => void
}
总结
这就是uni-app、H5实现瀑布流效果封装,希望能帮助到你,有什么问题可以私信给我。