UniApp+Vue3实现高性能无限滚动卡片组件:垂直滑动、触摸拖拽与动态导航的完美结合

引言

在移动应用开发中,流畅且吸引人的用户界面对于提升用户体验至关重要。本文将详细介绍如何使用UniApp和Vue3框架构建一个具有垂直方向无限滚动卡片、触摸拖拽支持、同步导航栏和平滑动画效果的高级UI组件。我们将通过代码分析每个功能的实现细节,帮助开发者理解这类复杂交互组件的开发思路。

实现效果

我们开发的组件具有以下特点:

  1. 垂直方向的无限滚动卡片
  2. 流畅的触摸拖拽支持
  3. 右侧同步高亮的导航栏
  4. 卡片缩放动画效果
  5. 自动循环复位机制

效果预览

核心实现

1. 数据结构与状态管理

首先,我们通过Vue3的组合式API来管理组件的状态

// 导入原始规则数据
import rulesJson from "@/json/rules/index.json";// 存储原始规则数据
const originalRulesData = ref(rulesJson.rules);// 创建循环数组,包含三组相同的数据,用于实现无限滚动
const loopRulesData = computed(() => [...originalRulesData.value,...originalRulesData.value,...originalRulesData.value
]);// 单个卡片高度
const cardHeight = 688.43;// 当前显示的实际索引(用于右侧导航)
const currentIndex = ref(0); // 显示索引(用于卡片位置计算)
const displayIndex = ref(originalRulesData.value.length);// Y轴偏移量
const translateY = ref(-originalRulesData.value.length * cardHeight);// 触摸事件相关状态
const startY = ref(0);
const moveY = ref(0);
const isDragging = ref(false);
const enableTransition = ref(true);

这里的关键点是我们创建了一个"循环数组"(loopRulesData),包含三组相同的数据。这是实现无限滚动的核心思路,允许用户在第一组数据之前和第三组数据之后继续滚动,然后在适当的时机无缝重置位置。

2. 垂直方向无限滚动实现

无限滚动的关键在于三个部分:

  1. 使用三倍数据长度的数组
  2. 初始化位置在中间组的起始位置
  3. 当滚动到边界时,无缝重置到中间组的对应位置
<viewclass="cards-container":style="{ transform: `translateY(${translateY}rpx)`,transition: enableTransition ? 'transform 0.3s ease-out' : 'none'}"
><viewv-for="(item, index) in loopRulesData":key="index"class="rule-card":class="{ 'rule-card-active': displayIndex === index }"><!-- 卡片内容 --></view>
</view>
// 触摸结束事件处理
const touchEnd = () => {if (!isDragging.value) return;isDragging.value = false;const diff = translateY.value - moveY.value;const direction = diff > 0 ? -1 : 1; // 确定滑动方向const minSwipeDistance = cardHeight * 0.15; // 最小滑动距离阈值if (Math.abs(diff) > minSwipeDistance) {enableTransition.value = true;displayIndex.value += direction;// 更新实际索引并处理循环let newIndex = currentIndex.value + direction;if (newIndex < 0) {newIndex = originalRulesData.value.length - 1;} else if (newIndex >= originalRulesData.value.length) {newIndex = 0;}currentIndex.value = newIndex;// 执行带动画的滑动translateY.value = -displayIndex.value * cardHeight;const length = originalRulesData.value.length;// 处理无限滚动的边界情况if (displayIndex.value >= length * 2) {// 当滑动到第三部分时,无动画地重置到中间部分setTimeout(() => {enableTransition.value = false;displayIndex.value = length;translateY.value = -length * cardHeight;}, 300);} else if (displayIndex.value < length) {// 当滑动到第一部分时,无动画地重置到中间部分setTimeout(() => {enableTransition.value = false;displayIndex.value = length * 2 - 1;translateY.value = -(length * 2 - 1) * cardHeight;}, 300);}} else {// 如果滑动距离不够,回弹到原位enableTransition.value = true;translateY.value = -displayIndex.value * cardHeight;}
};

这段代码的精髓在于:

  • 用户看到的是平滑的动画滚动
  • 但在滚动到边界后,我们在动画完成时(通过setTimeout配合动画持续时间)禁用过渡效果,并立即重置位置
  • 重置操作对用户不可见,因此创造了无限滚动的错觉

3. 触摸拖拽支持

触摸拖拽通过三个关键事件实现:touchstart、touchmove和touchend:

// 触摸开始事件处理
const touchStart = (e: TouchEvent) => {isDragging.value = true;enableTransition.value = false; // 关闭过渡动画以实现流畅拖动startY.value = e.touches[0].clientY;moveY.value = translateY.value;
};// 触摸移动事件处理
const touchMove = (e: TouchEvent) => {if (!isDragging.value) return;const currentY = e.touches[0].clientY;const diff = (currentY - startY.value) * 1.8; // 1.8为移动速度系数translateY.value = moveY.value + diff;
};

这里的关键点:

  • 在touchStart中禁用过渡动画,确保拖拽感觉更自然
  • 在touchMove中计算手指移动距离,并实时更新translateY
  • 使用系数1.8放大移动效果,使滚动感觉更加灵敏

4. 右侧同步导航栏

右侧导航栏需要与当前显示的卡片保持同步:

<view class="right-nav"><viewv-for="(item, index) in originalRulesData":key="index"class="nav-item":class="{ 'nav-item-active': currentIndex === index }"@click="handleNavClick(index)">{{ item.title }}</view>
</view>
// 右侧导航点击处理
const handleNavClick = (index: number) => {currentIndex.value = index;// 直接跳转到中间部分的对应位置displayIndex.value = originalRulesData.value.length + index;translateY.value = -displayIndex.value * cardHeight;
};

导航逻辑的关键点:

  • 使用currentIndex来跟踪实际的数据索引(而不是循环数组中的索引)
  • 在用户滑动卡片时更新currentIndex
  • 点击导航时,直接跳转到中间组的对应卡片位置

5. 卡片缩放动画效果

缩放动画通过CSS类和条件渲染实现

<viewclass="rule-card":class="{ 'rule-card-active': displayIndex === index }"
><!-- 卡片内容 -->
</view>
.rule-card {width: 447.76rpx;height: 688.43rpx;padding: 30rpx;position: relative;border-radius: 50rpx;opacity: 0.4;box-sizing: border-box;margin-bottom: 0;transition: all 0.3s;transform: scale(0.8);background-color: transparent;
}.rule-card-active {opacity: 1;transform: scale(1);
}

动画效果的关键点:

  • 非活跃卡片应用了scale(0.8)缩小效果和降低透明度
  • 活跃卡片恢复正常大小和完全不透明
  • 使用CSS过渡(transition: all 0.3s)使变化平滑
  • 通过比较displayIndex和循环列表的索引来确定哪张卡片是当前活跃的

6. 自动循环复位

循环复位是无限滚动的关键部分,在touchEnd事件中实现:

// 省略前面的代码...const length = originalRulesData.value.length;// 处理无限滚动的边界情况if (displayIndex.value >= length * 2) {// 当滑动到第三部分时,无动画地重置到中间部分setTimeout(() => {enableTransition.value = false;displayIndex.value = length;translateY.value = -length * cardHeight;}, 300);} else if (displayIndex.value < length) {// 当滑动到第一部分时,无动画地重置到中间部分setTimeout(() => {enableTransition.value = false;displayIndex.value = length * 2 - 1;translateY.value = -(length * 2 - 1) * cardHeight;}, 300);}

复位机制的关键点:

  • 使用requestAnimationFrame和setTimeout确保在动画完成后再进行重置
  • 禁用过渡动画(enableTransition = false)使重置操作对用户不可见
  • 根据滚动方向决定重置到哪个位置

完整代码解析

以下是组件模板的关键部分:

<template><view class="page-wrapper"><!-- 背景图和顶部图片 --><image class="bg-image" src="@/static/ruleBg.jpg" mode="aspectFill" /><image class="top-image" src="@/static/ruleTop.jpg" mode="aspectFit" /><image class="go-back" @click="handleGoBack" src="@/static/goback.png" /><!-- 主要内容区域 --><view class="content-container"><!-- 左侧卡片区域,绑定触摸事件 --><viewclass="left-content"@touchstart="touchStart"@touchmove="touchMove"@touchend="touchEnd"><!-- 卡片容器,应用动态样式实现滚动效果 --><viewclass="cards-container":style="{ transform: `translateY(${translateY}rpx)`,transition: enableTransition ? 'transform 0.3s ease-out' : 'none'}"><!-- 渲染循环数组中的所有卡片 --><viewv-for="(item, index) in loopRulesData":key="index"class="rule-card":class="{ 'rule-card-active': displayIndex === index }"><imageclass="rule-card-bg"src="@/static/ruleCoverBg.png"mode="scaleToFill"/><view class="rule-content"><view class="rule-title">{{ item.title }}</view><view class="rule-desc">{{ item.content }}</view><button class="play-btn" open-type="share"><image src="@/static/playThis.png" mode="aspectFit" /></button></view></view></view></view><!-- 右侧导航 --><view class="right-nav"><viewv-for="(item, index) in originalRulesData":key="index"class="nav-item":class="{ 'nav-item-active': currentIndex === index }"@click="handleNavClick(index)">{{ item.title }}</view></view></view></view>
</template>

性能优化

这个组件实现中的几个性能优化点值得注意:

  1. 硬件加速:使用 transform: translateZ(0)-webkit-transform: translateZ(0) 触发GPU加速

    .cards-container {transform: translateZ(0);-webkit-transform: translateZ(0);will-change: transform;backface-visibility: hidden;-webkit-backface-visibility: hidden;
    }
  2. 平滑过渡:使用cubic-bezier曲线优化过渡动画

    transition: transform 0.25s cubic-bezier(0.33, 1, 0.68, 1);
  3. 性能提示:使用will-change属性告知浏览器元素属性将要发生变化

    will-change: transform;
  4. 滚动阈值:设置最小滑动距离阈值,避免微小移动触发滚动

    const minSwipeDistance = cardHeight * 0.15;

适配与样式细节

组件的样式设计也有一些值得关注的细节:

/* 防止底部出现生硬的白色边界 */
.left-content::after {content: "";position: absolute;left: 0;right: 0;bottom: 0;height: 100rpx;background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));pointer-events: none;
}/* 激活状态的导航项样式 */
.nav-item-active {width: 243rpx;height: 104rpx;background: #ffffff;box-shadow: 0rpx 0rpx 22rpx 0rpx #ffae00;border-radius: 11rpx 0rpx 0rpx 11rpx;opacity: 1;font-size: 34rpx;
}

 这些细节包括:

  • 使用渐变遮罩使卡片与背景过渡更自然
  • 为活跃导航项添加阴影和尺寸变化,增强视觉反馈
  • 使用圆角和适当的间距提升整体美观度

无限滚动卡片的核心实现思路详解

无限滚动的本质与思路

无限滚动的本质是创造一种"无尽内容"的视觉错觉,让用户感觉可以无限地向一个方向滚动,而实际上是在有限的内容中循环。以下是实现这种效果的核心思路:

1. 三段式数据结构

无限滚动的基础是创建一个包含三倍数据量的视图:

  • 前段数据:原始数据的一个完整副本
  • 中段数据:原始数据的一个完整副本(主要显示区域)
  • 后段数据:原始数据的一个完整副本

这种结构允许用户在任何方向上都有足够的内容可以滚动。

2. 起始定位策略

组件初始化时,将视图定位到中段数据的起始位置。这确保用户无论向上还是向下滚动,都有一段完整的内容可以查看。

3. "幻觉"制造机制

无限滚动的核心是一个精巧的"幻觉"制造机制,包含以下步骤:

  • 可见滚动阶段:当用户滑动时,组件正常执行带有动画效果的滚动
  • 边界检测:持续监测是否滚动到了前段或后段数据的边界
  • 位置重置:一旦到达边界(用户看到的是前段或后段数据),等待当前滚动动画完成
  • 禁用动画过渡:关键的一步,临时关闭所有过渡动画效果
  • 瞬间跳转:将内容位置瞬间重置到中段数据中的对应位置
  • 恢复动画设置:重新启用过渡动画,为下一次滚动做准备

4. 双重索引系统

为了管理这种复杂的滚动逻辑,需要维护两个不同的索引:

  • 现实索引:追踪用户当前查看的是哪一条实际数据(0到原始数据长度-1)
  • 显示索引:追踪在三倍数组中的实际位置(用于计算偏移量)

当用户滚动时,两个索引都会更新,但使用不同的规则:现实索引循环变化,显示索引可以超出原始数据范围。

5. 临界点重置逻辑

当显示索引达到临界点时,触发重置逻辑:

  • 前段边界:如果滚动到前段数据内,在适当时机将位置重置到中段数据的末尾对应位置
  • 后段边界:如果滚动到后段数据内,在适当时机将位置重置到中段数据的起始对应位置

关键是这个重置过程必须是不可见的,用户不应察觉到任何"跳跃"。

6. 时机控制精确性

重置操作的时机控制至关重要:

  • 必须在当前滚动动画完全结束后进行
  • 必须在下一帧渲染前完成
  • 重置过程必须禁用所有视觉过渡效果
  • 重置完成后立即恢复过渡效果

7. 导航同步机制

当用户使用导航直接跳转时,无限滚动系统需要:

  • 立即更新现实索引
  • 始终跳转到中段数据的对应项
  • 避免不必要的边界检测和重置

这确保了导航操作的稳定性和一致性。

为什么说是"无限"?

这种滚动被称为"无限"是因为:

  1. 感知上的无限:从用户感知角度看,内容可以无限向任一方向滚动,没有起点或终点
  2. 循环无尽:内容会不断循环显示,永不结束
  3. 无感知边界:用户无法感知到内容的重置点,体验上没有"边界"的概念
  4. 数学上的循环:虽然实际数据量有限,但通过位置重置技术创造了一个拓扑上的环形结构

无限滚动的本质启示

从更深层次理解,无限滚动实际上是一个"环形数据结构"在线性界面上的映射:

  • 物理世界中,有限长度的内容不可能无限滚动
  • 但通过创造"跳转点"并隐藏跳转过程,我们将线性结构变成了环形结构
  • 用户在这个环上移动,永远不会到达"尽头"

这种实现方式的精妙之处在于,它不需要实际创建无限量的数据,而只需要有限的数据和精心设计的视觉错觉机制,就能创造出无限内容的体验。

无限滚动的思想本质是:通过在用户不察觉的情况下,巧妙地操纵视图位置,使有限变得无限。

总结

本文详细介绍了如何使用UniApp和Vue3实现一个具有垂直无限滚动、触摸拖拽、同步导航和动画效果的卡片界面。关键实现技术包括:

  1. 无限滚动:通过三倍数组和边界重置技术实现流畅的无限滚动
  2. 触摸交互:结合touchstart、touchmove和touchend事件实现自然的拖拽体验
  3. 视觉反馈:使用缩放和透明度变化为用户提供明确的视觉反馈
  4. 性能优化:利用CSS3硬件加速和动画优化提升滚动性能
  5. 状态同步:维护多个状态变量确保卡片滚动和导航选择同步

这种组件适用于各类需要展示规则、介绍或产品信息的应用场景,通过流畅的交互极大提升了用户体验。开发者可以根据自身需求调整卡片样式、动画效果和交互细节,打造更加个性化的滚动界面。

通过理解这些实现细节,开发者可以将类似的交互模式应用到自己的UniApp项目中,创造出更加吸引人的移动应用界面。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 热题 100----1.两数之和

LeetCode 热题 100----1.两数之和 题目描述 我的解法 语言&#xff1a;js 思路就是&#xff1a;用双重循环去找哪两个数字相加等于target&#xff0c;目前的时间复杂度为O(n2)&#xff0c;之后右优化思路再更新。

华为云 | 快速搭建DeepSeek推理系统

DeepSeek&#xff08;深度求索&#xff09;作为一款国产AI大模型&#xff0c;凭借其高性能、低成本和多模态融合能力&#xff0c;在人工智能领域崛起&#xff0c;并在多个行业中展现出广泛的应用潜力。 如上所示&#xff0c;在华为云解决方案实践中&#xff0c;华为云提供的快速…

本地部署阿里万象2.1文生视频模型(Wan2.1-T2V)完全指南

在生成式AI技术爆发式发展的今天,阿里云开源的万象2.1(Wan2.1)视频生成模型,为创作者提供了从文字/图像到高清视频的一站式解决方案。本文针对消费级显卡用户,以RTX 4060 Ti 16G为例,详解本地部署全流程与性能调优方案,涵盖环境配置、多模型选择策略、显存优化技巧及实战…

FPGA标准库-Open Logic

在现代技术发展的浪潮中&#xff0c;开源项目已经成为了推动技术创新和发展的核心力量。无论是人工智能、区块链、云计算&#xff0c;还是传统的嵌入式开发、操作系统&#xff0c;开源项目都在其中扮演着至关重要的角色。它们不仅促进了技术的快速迭代&#xff0c;也为全球开发…

React antd的datePicker自定义,封装成组件

一、antd的datePicker自定义 需求&#xff1a;用户需要为日期选择器的每个日期单元格添加一个Tooltip&#xff0c;当鼠标悬停时显示日期、可兑换流量余额和本公会可兑流量。这些数据需要从接口获取。我需要结合之前的代码&#xff0c;确保Tooltip正确显示&#xff0c;并且数据…

【算法题解答·一】二分法

【算法题解答一】二分法 接上文 【算法方法总结一】二分法的一些技巧和注意事项 二分法相关题目如下&#xff1a; 34.在排序数组中查找元素第一和最后一个位置 使用 左闭右闭&#xff0c;[left,right]关键在于 nums[mid] target 的部分找 第一个 target 的过程中&#xff0…

Non-Homophilic Graph Pre-Training and Prompt Learning

Non-Homophilic Graph Pre-Training and Prompt Learning KDD25 ​#paper/⭐#​ 目的&#xff1a;对异配图进行prompt ‍ ​​ 方法 邻居节点的综合嵌入 s v 1 ∣ V ( S v ) ∣ ∑ u ∈ V ( S v ) h u ⋅ s i m ( h u , h v ) , \mathbf{s}_{v}\frac{1}{|V(S_{v})|}\su…

BUU44 [BJDCTF2020]ZJCTF,不过如此1 [php://filter][正则表达式get输入数据][捕获组反向引用][php中单双引号]

题目&#xff1a; 我仿佛见到了一位故人。。。也难怪&#xff0c;题目就是ZJCTF 按要求提交/?textdata://,I have a dream&filenext.php后&#xff1a; ......不太行&#xff0c;好像得用filephp://filter/convert.base64-encode/resourcenext.php 耶&#xff1f;那 f…

掌握 findIndex、push 和 splice:打造微信小程序的灵活图片上传功能✨

文章目录 ✨ 掌握 findIndex、push 和 splice&#xff1a;打造微信小程序的灵活图片上传功能 &#x1f31f;示例场景&#xff1a;小程序图片上传&#x1f33c; 认识 findIndex定义语法在代码中的应用示例当前行为 &#x1f680; 认识 push定义语法在代码中的应用示例特点 ✂️ …

山西青年杂志山西青年杂志社山西青年编辑部2025年第3期目录

青年争鸣 教师发展中心行动转向的价值意蕴分析框架研究与启示 于宝证;李军红;郑钰莹;何易雯; 产教融合视角下职业本科工商管理专业人才培养模式探析 杜芯铭; 青年教育研究 教育数字化背景下高职院校的课堂教学研究 张晨; 统筹职业教育、高等教育、继续教育协同…

神经网络:AI的网络神经

神经网络&#xff08;Neural Networks&#xff09;是深度学习的基础&#xff0c;是一种模仿生物神经系统结构和功能的计算模型。它由大量相互连接的节点&#xff08;称为神经元&#xff09;组成&#xff0c;能够通过学习数据中的模式来完成各种任务&#xff0c;如图像分类、语音…

计算机视觉|ViT详解:打破视觉与语言界限

一、ViT 的诞生背景 在计算机视觉领域的发展中&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直占据重要地位。自 2012 年 AlexNet 在 ImageNet 大赛中取得优异成绩后&#xff0c;CNN 在图像分类任务中显示出强大能力。随后&#xff0c;VGG、ResNet 等深度网络架构不…

储油自动化革命,网关PROFINET与MODBUS网桥的无缝融合,锦上添花

储油行业作为能源供应链的关键环节&#xff0c;其自动化和监控系统的可靠性和效率至关重要。随着工业4.0的推进&#xff0c;储油设施越来越多地采用先进的自动化技术以提高安全性、降低成本并优化运营。本案例探讨了如何通过使用稳联技术PROFINET转MODBUS模块网关网桥&#xff…

解锁Egg.js:从Node.js小白到Web开发高手的进阶之路

一、Egg.js 是什么 在当今的 Web 开发领域&#xff0c;Node.js 凭借其事件驱动、非阻塞 I/O 的模型&#xff0c;在构建高性能、可扩展的网络应用方面展现出独特的优势 &#xff0c;受到了广大开发者的青睐。它让 JavaScript 不仅局限于前端&#xff0c;还能在服务器端大展身手&…

python:pymunk + pygame 模拟六边形中小球弹跳运动

向 chat.deepseek.com 提问&#xff1a;编写 python 程序&#xff0c;用 pymunk, 有一个正六边形&#xff0c;围绕中心点缓慢旋转&#xff0c;六边形内有一个小球&#xff0c;六边形的6条边作为墙壁&#xff0c;小球受重力和摩擦力、弹力影响&#xff0c;模拟小球弹跳运动&…

学习 Wireshark 分析 Android Netlog

Android 设备抓取的日志中,netlog 文件夹包含.cap文件可以使用Wireshark工具查看网络日志。 Wireshark 分析 DNS 步骤 在使用Wireshark分析网路日志时,要检查DNS解析是否正常,可以按照以下步骤操作: 识别DNS查询和回应 使用过滤器 udp.port == 53 来查看所有DNS相关的流量…

OpenHarmony启动系统-U-Boot简介和源码下载与编译

OpenHarmony系统启动流程简述 设备上电后&#xff0c;OpenHarmony系统大致经历以下3个阶段&#xff1a; 1.BootRom代码引导加载UBoot&#xff1b; 2.UBoot启动初始化硬件资源&#xff0c;引导并加载系统内核(Linux内核)&#xff1b; 3.Kernel(LiteOs,Linux内核)启动、加载驱动…

论文笔记-NeurIPS2017-DropoutNet

论文笔记-NeurIPS2017-DropoutNet: Addressing Cold Start in Recommender Systems DropoutNet&#xff1a;解决推荐系统中的冷启动问题摘要1.引言2.前言3.方法3.1模型架构3.2冷启动训练3.3推荐 4.实验4.1实验设置4.2在CiteULike上的实验结果4.2.1 Dropout率的影响4.2.2 实验结…

ctf网络安全赛题

CTF简介 CTF&#xff08;Capture The Flag&#xff09;中文一般译作夺旗赛&#xff0c;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。发展…

一周学会Flask3 Python Web开发-WTForms表单验证

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们可以通过WTForms表单类属性的validators属性来实现表单验证。 常用的WTForms验证器 验证器说明DataRequired(messageNo…