uni-app、H5实现瀑布流效果封装,列可以自定义


文章目录

  • 前言
  • 一、效果
  • 二、使用代码
  • 三、核心代码
  • 总结


前言

最近做项目需要实现uni-app、H5实现瀑布流效果封装,网上搜索有很多的例子,但是代码都是不够完整的,下面来封装一个uni-app、H5都能用的代码。在小程序中,一个个item渲染可能出现问题,也通过加锁来解决问题。


一、效果

1、下面看一下实现的效果,我这里的商品图片是正方形是固定大小的,如果你想要图片不同效果,也是可以适配的。
请添加图片描述

二、使用代码

1、下面是封装的组件如何使用

<TBody
        refresher
        :data="goodsList"
        :is-end="isEnd"
        :is-loading="isLoading"
        :is-refreshing="isRefreshing"
        @refresh="reset"
        @lower="fetchGoodsNextPage"
      >
        <TTMultiColumnList
          class="bg-#fafafa goods"
          column-gap="16rpx"
          :list="[]"
          :column-size="2"
          @ready="updateColumnOperator"
        >
          <template #default="{ data, index }">
            <view
              class="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)
    return

  try {
    goodsListQuery.offset = undefined
    isLoading.value = true
    const { list, offset } = await fetchGoodsList({ offset: goodsListQuery.offset })
    if (list?.length) {
      goodsList.value = list
      if (list.length < goodsListQuery.limit)
        isEnd.value = true
    }
    else {
      isEnd.value = true
    }
    goodsListQuery.offset = offset
    nextTick(() => {
      columnOperator?.reset(list)
    })
  }
  finally {
    isLoading.value = false
  }
}

//下一页
async function fetchGoodsNextPage() {
  if (isLoading.value || isEnd.value)
    return

  try {
    isLoading.value = true
    isRefreshing.value = true
    const { 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 = offset
    columnOperator?.append(list)
  }
  finally {
    isRefreshing.value = false
    isLoading.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: number
    columnGap: string
    rowGap: 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 H5
  const rect = document
    .querySelector(className)
    ?.getBoundingClientRect()
  resolve(rect?.height || 0 as number)
  // #endif
  // #ifndef H5
  uni.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].height
    const siblingHeight = item.height
    return siblingHeight < height ? i : index
  }, 0)
}

// 将元素一个一个地插入到高度最小的一列
async function gradientAppendToColumn(startIndex: number, list: Array<ListItem>) {
  if (startIndex >= list.length)
    return false

  const 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 item
  return await new Promise((resolve) => {
    nextTick(async () => {
      // #ifndef H5
      // 解决小程序渲染问题
      await new Promise(resolve => nextTick(() => resolve(true)))
      // #endif
      await gradientAppendToColumn(startIndex + 1, list)
      resolve(true)
    })
  })
}

async function appendColumnDataInQueue(list: Array<ListItem>) {
  // 解决小程序渲染问题
  const oldAppendColumnDataPromise = appendColumnDataPromise
  appendColumnDataPromise = 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 = false

  for (let col = 0; col < columns.value.length; col++) {
    if (done)
      break
    const 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 = true
        break
      }
    }
  }
}

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',
    }"
  >
    <view
      v-for="(rows, colIndex) in columns"
      :key="colIndex"
    >
      <view
        :key="`${colIndex}_list`"
        :class="`s_${colIndex}_ccList`"
      >
        <view
          v-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 = unknown

export interface ColumnItem {
  index: number
  data: ListItem
}

export type ColumnOperatorPredictor = (item: ListItem) => boolean

export interface ColumnOperator {
  readonly append: (list: Array<ListItem>) => void
  readonly remove: (predict: ColumnOperatorPredictor) => void
  readonly update: (predict: ColumnOperatorPredictor, data: ListItem) => void
  readonly reset: (list?: Array<ListItem>) => void
}

总结

这就是uni-app、H5实现瀑布流效果封装,希望能帮助到你,有什么问题可以私信给我。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/61570.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Godot 4 源码分析 - Path2D与PathFollow2D

学习演示项目dodge_the_creeps&#xff0c;发现里面多了一个Path2D与PathFollow2D 研究GDScript代码发现&#xff0c;它主要用于随机生成Mob var mob_spawn_location get_node(^"MobPath/MobSpawnLocation")mob_spawn_location.progress randi()# Set the mobs dir…

【机器学习】编码、创造和筛选特征

在机器学习和数据科学领域中&#xff0c;特征工程是提取、转换和选择原始数据以创建更具信息价值的特征的过程。假设拿到一份数据集之后&#xff0c;如何逐步完成特征工程呢&#xff1f; 文章目录 一、特性类型分析1.1 数值型特征1.2 类别型特征1.3 时间型特征1.4 文本型特征1.…

Android Studio安装AI编程助手Github Copilot

csdn原创谢绝转载 简介 文档链接 https://docs.github.com/en/copilot/getting-started-with-github-copilot 它是个很牛B的编程辅助工具&#xff0c;装它&#xff0c;快装它&#xff0e; 支持以下IDE: IntelliJ IDEA (Ultimate, Community, Educational)Android StudioAppC…

数据库操作系列-Mysql, Postgres常用sql语句总结

文章目录 1.如果我想要写一句sql语句&#xff0c;实现 如果存在则更新&#xff0c;否则就插入新数据&#xff0c;如何解决&#xff1f;MySQL数据库实现方案: ON DUPLICATE KEY UPDATE写法 Postgres数据库实现方案:方案1&#xff1a;方案2&#xff1a;关于更新&#xff1a;如何实…

【云原生】K8S二进制搭建一

目录 一、环境部署1.1操作系统初始化 二、部署etcd集群2.1 准备签发证书环境在 master01 节点上操作在 node01与02 节点上操作 三、部署docker引擎四、部署 Master 组件4.1在 master01 节点上操 五、部署Worker Node组件 一、环境部署 集群IP组件k8s集群master01192.168.243.1…

【雕爷学编程】MicroPython动手做(31)——物联网之Easy IoT

1、物联网的诞生 美国计算机巨头微软(Microsoft)创办人、世界首富比尔盖茨&#xff0c;在1995年出版的《未来之路》一书中&#xff0c;提及“物物互联”。1998年麻省理工学院提出&#xff0c;当时被称作EPC系统的物联网构想。2005年11月&#xff0c;国际电信联盟发布《ITU互联网…

在 Ubuntu 上安装 Docker 桌面

Ubuntu 22.04 (LTS) 安装 Docker 桌面 要成功安装 Docker Desktop&#xff0c;您必须&#xff1a; 满足系统要求拥有 64 位版本的 Ubuntu Jammy Jellyfish 22.04 (LTS) 或 Ubuntu Impish Indri 21.10。对于非 Gnome 桌面环境&#xff0c;必须安装 gnome-terminal&#xff1a;…

机器学习笔记 - YOLO-NAS 最高效的目标检测算法之一

一、YOLO-NAS概述 YOLO(You Only Look Once)是一种对象检测算法,它使用深度神经网络模型,特别是卷积神经网络,来实时检测和分类对象。该算法首次在 2016 年由 Joseph Redmon、Santosh Divvala、Ross Girshick 和 Ali Farhadi 发表的论文《You Only Look Once: Unified, Re…

Excel·VBA表格横向、纵向相互转换

如图&#xff1a;对图中区域 A1:M6 横向表格&#xff0c;转换成区域 A1:C20 纵向表格&#xff0c;即 B:M 列转换成每2列一组按行写入&#xff0c;并删除空行。同理&#xff0c;反向操作就是纵向表格转换成横向表格 目录 横向转纵向实现方法1转换结果 实现方法2转换结果 纵向转横…

ThreadLocal有内存泄漏问题吗

对于ThreadLocal的原理不了解或者连Java中的引用类型都不了解的可以看一下我的之前的一篇文章Java中的引用和ThreadLocal_鱼跃鹰飞的博客-CSDN博客 我这里也简单总结一下: 1. 每个Thread里都存储着一个成员变量&#xff0c;ThreadLocalMap 2. ThreadLocal本身不存储数据&…

Jenkins 自动化部署实例讲解,另附安装教程!

【2023】Jenkins入门与安装_jenkins最新版本_丶重明的博客-CSDN博客 也可以结合这个互补看 前言 你平常在做自己的项目时&#xff0c;是否有过部署项目太麻烦的想法&#xff1f;如果你是单体项目&#xff0c;可能没什么感触&#xff0c;但如果你是微服务项目&#xff0c;相…

Android的Handler消息通信详解

目录 背景 1. Handler基本使用 2. Handler的Looper源码分析 3. Handler的Message以及消息池、MessageQueue 4. Handler的Native实现 4.1 MessageQueue 4.2 Native结构体和类 4.2.1 Message结构体 4.2.2 消息处理类 4.2.3 回调类 4.2.5 ALooper类 5. 总结&…

【千题百解】华为机试题:求最小公倍数

“所有命运馈赠的礼物,都已在暗中标好了价格” 👨🏻‍💻作者:鳄鱼儿 🍀个人简介 👨🏻‍🎓计算机专业硕士研究生 🦨阿里云社区专家博主 🌙CSDN博客专家 & Java领域优质创作者 题目 解题 Java实现 注意a和b相乘时可能超过int最大值。 import java.uti

python调用pytorch的clip模型时报错

使用python调用pytorch中的clip模型时报错&#xff1a;AttributeError: partially initialized module ‘clip’ has no attribute ‘load’ (most likely due to a circular import) 目录 现象解决方案一、查看项目中是否有为clip名的文件二、查看clip是否安装成功 现象 clip…

命令模式(Command)

命令模式是一种行为设计模式&#xff0c;可将一个请求封装为一个对象&#xff0c;用不同的请求将方法参数化&#xff0c;从而实现延迟请求执行或将其放入队列中或记录请求日志&#xff0c;以及支持可撤销操作。其别名为动作(Action)模式或事务(Transaction)模式。 Command is …

Spring Cloud Eureka 和 zookeeper 的区别

CAP理论 在了解eureka和zookeeper区别之前&#xff0c;我们先来了解一下这个知识&#xff0c;cap理论。 1998年的加州大学的计算机科学家 Eric Brewer 提出&#xff0c;分布式有三个指标。Consistency&#xff0c;Availability&#xff0c;Partition tolerance。简称即为CAP。…

初识性能测试

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 什么是性能测试&#xff1f;为什么要做性能测试&#xff1f;性能测试常见术语及性能测试衡量指标并发用户数响应时间/平均响应…

华为Mate30报名鸿蒙 HarmonyOS 4.0.0.108 系统更新

华为 Mate 30 系列于 2019 年 11 月 1 日上市&#xff0c;包括 Mate 30 4G / 5G、Mate 30 Pro 4G / 5G、保时捷设计版 Mate30 共五款机型。华为 Mate 30 系列 5G 版搭载麒麟 990 5G 处理器&#xff0c;同时支持 SA 及 NSA 5G 双模&#xff0c;适配三大运营商的 5G / 4G / 3G / …

Mac显示隐藏文件夹

1、设置隐藏文件可见 defaults write com.apple.finder AppleShowAllFiles TRUE 2、killall Finder killall Finder

opencv的Mask操作,选择图片中感兴趣的区域

最近做目标检测任务的时候&#xff0c;需要对固定区域的内容进行检测&#xff0c;要用到opencv的mask操作&#xff0c;选择图片固定的区域 代码 import cv2 import numpy as npimg cv2.imread(data/images/smoking.png)# 弹出一个框 让你选择ROI | x,y是左上角的坐标 x,y,w,…