不用swipe插件,用<component>组件实现H5的swipe切换

news/2025/1/1 7:43:38/

不引入 swipe 插件,使用vue自带组件实现swipe滑动切换页面功能

      • 需求场景
        • 1. 引入组件
        • 2. 动态加载页面组件
        • 3. 使用component组件
        • 4. 组件属性及相关事件
        • 5. 触摸事件处理
        • 6. 动画和过渡控制
        • 7. 节流功能
      • 完整代码

需求场景

不引入 swipe 插件,使用vue自带component组件实现H5滑动切换页面
使用Vue的component组件来动态加载和渲染不同的页面组件。以下是详细分析该文件的引入方式、component的功能,以及它们如何协同工作以实现页面切换效果。

1. 引入组件

require.context: 这是Webpack提供的一个特性,用于动态引入模块。它允许你在指定目录中动态加载符合条件的文件。
…/components: 指定要搜索的目录。
false: 表示不搜索子目录。
/page_\d+.vue$/: 正则表达式,用于匹配文件名,确保只引入以 page_007 page_开头并以.vue结尾的文件。

javascript">const pagesContext = require.context('../components', false, /page_\d+\.vue$/);
2. 动态加载页面组件

loadPages: 该方法用于加载所有符合条件的页面组件。
pagesContext.keys(): 返回该目录下所有匹配的文件名数组。
map(): 遍历这些文件名,提取页面编号并创建一个包含组件加载函数的对象。
import(): 动态导入组件,返回一个Promise,成功时返回组件,失败时捕获错误并返回null。
sort(): 根据页面编号对组件进行排序。
map(): 最后返回一个只包含组件加载函数的数组。

javascript">loadPages() {  this.pages = pagesContext.keys()  .map(key => {  const pageNumber = parseInt(key.match(/\d+/)[0], 10);  return {  pageNumber,  component: () => import(`../components/${key.substring(2)}`).catch(err => {  console.error(`Error loading component ${key}:`, err);  return null;   })  };  })  .sort((a, b) => a.pageNumber - b.pageNumber)  .map(page => page.component);  
}
3. 使用component组件

component: Vue提供的内置组件,用于动态渲染不同的组件。
v-if: 控制组件的渲染条件,只有在isTransitioning为false时才渲染当前页面。
: key: 为动态组件提供唯一的键值,以便Vue能够高效地更新和重用组件。
:is: 指定要渲染的组件,这里使用pages[currentPageIndex],动态获取当前页面的组件。
:style: 动态设置样式,控制组件的透明度和缩放效果。

javascript"><component v-if="!isTransitioning" :key="`P_${currentPageIndex + 1}`" :is="pages[currentPageIndex]" class="page"  :style="{ opacity: 1, transform: `scale(${currentScale})` }" :class="[`P_${currentPageIndex + 1}`]" />
4. 组件属性及相关事件

transition: Vue的过渡组件,用于为进入和离开的元素提供过渡效果。通过绑定事件,可以定义过渡的具体行为。
@before-enter: 在元素进入前调用的钩子,可以用于设置初始状态。
@enter: 元素进入时调用的钩子,可以用于设置动画效果。
@leave: 元素离开时调用的钩子,设置离开动画。
@before-leave: 元素离开前调用的钩子。
@after-leave: 元素离开后调用的钩子,通常用于重置状态。

javascript"><transition @before-enter="beforeEnter" @enter="enter" @leave="leave" @before-leave="beforeLeave"  @after-leave="afterLeave">
5. 触摸事件处理

通过@touchstart、@touchmove.prevent和@touchend事件处理,实现了页面的滑动切换。
在touchStart、touchMove和touchEnd方法中,记录触摸的起始和结束位置,计算滑动距离,并根据滑动方向和距离决定是否切换页面。
touchStart(event):
记录触摸开始时的Y坐标。
在页面开始触摸时,检查是否需要展示提示信息(例如this.report),并通过nextTick()确保在DOM更新后执行特定的类移除操作。

touchMove(event):
记录触摸移动时的Y坐标,并计算出滑动的距离。
根据滑动的方向(向上或向下)和当前页面索引,更新当前页面的透明度和缩放,该过程通过以下条件判断:
向下滑动(distance < 0)时,若当前不是第一页,更新currentOpacity和currentScale,使页面在下滑时逐渐透明和放大。
向上滑动(distance > 0)时,若当前不是最后一页,更新currentOpacity和currentScale,使页面在上滑时逐渐透明和放大。

touchEnd(event):
计算触摸结束时的滑动距离。
根据滑动的距离和预设的阈值决定是否切换页面:
如果向上滑动且距离超过阈值且当前页面不是最后一页,触发切换到下一页的方法throttledNextPage。
如果向下滑动且距离超过阈值且当前页面不是第一页,触发切换到上一页的方法throttledPreviousPage。
如果没有滑动足够远,则重置页面的透明度和缩放,恢复为完全不透明和正常大小。

6. 动画和过渡控制

beforeEnter(el): 设置进入前的状态(如透明度为0,缩放为0.8),为进入动画做准备。

enter(el, done): 在元素进入时触发,应用CSS属性来完成动画,并调用done()表示动画结束。

leave(el, done): 在元素离开时触发,设置退出的动画效果(如透明度为0,缩放为0.8),并在动画结束后调用done()。

beforeLeave(): 在元素离开前添加动画类名,确保离开效果的应用。

afterLeave(): 完成过渡后重置状态(如重置isTransitioning、currentOpacity、currentScale),并在下一帧中添加新的进入动画类名,使下一个页面进入时使用。

7. 节流功能

throttle(fn, delay): 用于限制方法调用的频率,防止在短时间内多次调用导致的性能问题。返回一个新的函数,只有在指定的时间间隔(delay)后才能再次执行,使得nextPage和previousPage的调用得以被节流处理。

完整代码

javascript"><template><div class="swipe-container"  @touchstart="touchStart"@touchmove.prevent="touchMove" @touchend="touchEnd"><transition @before-enter="beforeEnter" @enter="enter" @leave="leave" @before-leave="beforeLeave"@after-leave="afterLeave"><component v-if="!isTransitioning" :key="`P_${currentPageIndex + 1}`" :is="pages[currentPageIndex]" class="page":style="{ opacity: currentOpacity, transform: `scale(${currentScale})` }" :class="[`P_${currentPageIndex + 1}`]" /></transition></div>
</template><script>
const pagesContext = require.context('../components', false, /page_\d+\.vue$/); // 从 components 文件夹中引入所有 page_xx.vue 文件   
import 'animate.css'
import { mapState } from 'vuex'export default {data() {return {currentPageIndex: 0, // 当前页面索引  startY: 0, // 触摸开始的 Y 坐标  endY: 0, // 触摸结束的 Y 坐标  isTransitioning: false, // 用于控制是否正在过渡  currentOpacity: 1, // 当前页面透明度  --  控制页面透明度pages: [], // 页面内容  throttleDelay: 120, // 设置节流的延迟时间 currentScale: 1, // 初始化为 1,表示正常大小  ---   transform: `scale(${currentScale})`threshold: 500, // 阈值};},computed: {...mapState('user', ['report']),},methods: {loadPages() {// 使用 reduce() 方法来创建页数组,同时提取页码  this.pages = pagesContext.keys().map(key => {const pageNumber = parseInt(key.match(/\d+/)[0], 10); // 提取数字部分  return {pageNumber,component: () => import(`../components/${key.substring(2)}`).catch(err => {console.error(`Error loading component ${key}:`, err);return null; // 返回 null 或一个默认页面组件  })};}).sort((a, b) => a.pageNumber - b.pageNumber) // 按页码排序  .map(page => page.component); // 只返回组件  },touchStart(event) {if (this.report?.code) {this.$toast(this.report?.message)return}const touch = event.touches[0];this.startY = touch.clientY; // 记录触摸开始的 Y 坐标  this.$nextTick(() => {const el = document.querySelector(`.P_${this.currentPageIndex + 1}`)if (!el) returnel.classList.remove('animate__animated', 'animate__fadeIn');})},touchMove(event) {if (this.report?.code) {this.$toast(this.report?.message)return}const touch = event.touches[0];this.endY = touch.clientY; // 记录触摸结束的 Y 坐标  const distance = this.startY - this.endY; // 计算滑动距离  // 更新透明度   this.currentOpacityif (this.currentPageIndex > 0 && distance < 0) {// 仅在不是第一页或正在向下滑动时 this.currentOpacity = Math.max(0, 1 - Math.abs(distance) / this.threshold);this.currentScale = Math.min(1.5, 1 + Math.abs(distance) / this.threshold); // 最大放大至 1.5  } else if (this.currentPageIndex === 0 && distance < 0) {// 当在第一页向下滑动也保持变化  this.currentOpacity = Math.max(0, 1 - Math.abs(distance) / this.threshold);this.currentScale = Math.max(.95, 1 - Math.abs(distance) / this.threshold); // 最小保持为 1  }if (this.currentPageIndex < this.pages.length - 1 && distance > 0) {// 仅在不是最后一页或正在向上滑动时 this.currentOpacity = Math.max(0, 1 - Math.abs(distance) / this.threshold);this.currentScale = Math.min(1.5, 1 + Math.abs(distance) / this.threshold); // 最大放大至 1.5  } else if (this.currentPageIndex === this.pages.length - 1 && distance > 0) {// 当在最后一页向上滑动也保持变化  this.currentOpacity = Math.max(0, 1 - Math.abs(distance) / this.threshold);this.currentScale = Math.max(.95, 1 - Math.abs(distance) / this.threshold); // 最小保持为 1  }},touchEnd(event) {const distance = this.startY - this.endY; // 计算滑动距离  const threshold = 30; // 控制滑动触发的阈值  // 判断滑动方向并决定是否切换页面  if (this.endY > 0 && distance > threshold && this.currentPageIndex < this.pages.length - 1) {// 向上滑动,切换到下一页,确保不是最后一页  this.throttledNextPage();} else if (distance < -threshold && this.currentPageIndex > 0) {// 向下滑动,切换到上一页,确保不是第一页  this.throttledPreviousPage();} else {// 如果没有滑动足够远,恢复透明度  this.currentOpacity = 1; // 恢复为完全不透明  this.currentScale = 1; // 恢复为正常大小  }this.startY = this.endY = 0; // 初始化,避免遗留值 而导致点击时切换页面},nextPage() {if (this.currentPageIndex < this.pages.length - 1) {this.isTransitioning = true; // 开始过渡  this.currentPageIndex++; // 切换到下一页  }},previousPage() {if (this.currentPageIndex > 0) {this.isTransitioning = true; // 开始过渡  this.currentPageIndex--; // 切换到上一页  }},beforeEnter(el) {// 获取子元素  const t_El = el.querySelector('.transform_');// if (t_El) {//   console.log("找到的子元素:", t_El);//   // t_El.style.opacity = 0;//   // t_El.style.transform = 'scale(0.8)';// } el.style.opacity = 0;el.style.transform = 'scale(0.8)';},enter(el, done) {el.offsetHeight; // 触发重排  el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';el.style.opacity = 1;el.style.transform = 'scale(1)';done();},leave(el, done) {el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';el.style.opacity = 0;el.style.transform = 'scale(0.8)';done();},beforeLeave() {const el = document.querySelector(`.P_${this.currentPageIndex + 1}`);if (el) {el.classList.add('animate__animated', 'animate__fadeOut');}},afterLeave() {this.isTransitioning = false; // 过渡完成后,重置状态  this.currentOpacity = 1; // 重置透明度  this.currentScale = 1; // 确保过渡完成后重置缩放  this.$nextTick(() => {const el = document.querySelector(`.P_${this.currentPageIndex + 1}`)if (!el) returnel.classList.add('animate__animated', 'animate__fadeIn');})},// 加个节流  throttle(fn, delay) {let lastTime = 0;return function (...args) {const currentTime = Date.now();if (currentTime - lastTime >= delay) {lastTime = currentTime;fn.apply(this, args);}};},},created() {// 动态加载页面组件  this.loadPages();// 使用节流函数  this.throttledNextPage = this.throttle(this.nextPage, this.throttleDelay);this.throttledPreviousPage = this.throttle(this.previousPage, this.throttleDelay);}
};  
</script>
<style lang="scss">
@import '../css/index.scss';
</style>
<style lang="scss" scoped>
.swipe-container {position: relative;width: 100%;height: 100vh;overflow: hidden;  
}.page_ {background: #fff;
}.page {position: absolute;width: 100%;height: 100%;transition: opacity 0.5s ease, transform 0.5s ease;}
</style>

http://www.ppmy.cn/news/1559162.html

相关文章

论文略读: TransTab: Learning Transferable Tabular Transformers Across Tables

2022 neurips motivation&#xff1a;有若干个有标签的表格&#xff0c;但是每个表格的列名&#xff08;代表的属性&#xff09;都不大一样 怎样把这些表格都利用起来&#xff0c;学习一个普适的embedding——>利用列名

BUSCO:基因组组装质量和完整性评估

在完成基因组组装和注释后&#xff0c;我们需要先评估基因组组装注释的质量&#xff0c;再做进一步的分析。昨天我们介绍了注释工具Augustus&#xff08;文章&#xff1a;Augustus&#xff1a;精准预测与注释真核生物基因&#xff09;&#xff0c;今天给大家介绍一款评估基因组…

每天40分玩转Django:Django静态文件

Django静态文件 一、今日学习内容概述 学习模块重要程度主要内容静态文件配置⭐⭐⭐⭐⭐基础设置、路径配置CDN集成⭐⭐⭐⭐⭐CDN配置、资源优化静态文件处理⭐⭐⭐⭐压缩、版本控制部署优化⭐⭐⭐⭐性能优化、缓存策略 二、基础配置 # settings.py import os# 静态文件配置…

免费 IP 归属地接口

免费GEOIP&#xff0c;查询IP信息&#xff0c;支持IPV4 IPV6 ,包含国家地理位置&#xff0c;维度&#xff0c;asm,邮编 等&#xff0c;例如 例如查询1.1.1.1 http://geoip.91hu.top/?ip1.1.1.1 返回json 对象

4.采用锁操作并支持等待功能的线程安全队列

分析 书接上文 修改push()似乎并不困难:在函数末尾加上对data_cond.notify_one()的调用即可&#xff0c;与代码清单1(第一篇文章)一样。事情其实没那么简单&#xff0c;我们之所以采用精细粒度的锁&#xff0c;目的是尽可能提高并发操作的数量。如果在notify_one()调用期间&a…

React 生命周期完整指南

React 生命周期完整指南 1. 生命周期概述 1.1 React 16.3 之前的生命周期 初始化阶段 constructorcomponentWillMountrendercomponentDidMount 更新阶段 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate 卸载阶段 componentWil…

LossMaskMatrix损失函数掩码矩阵

文章目录 1. 理论2. python 1. 理论 A [ 1 2 0 0 2 3 4 0 ] → B [ 1 1 0 0 1 1 1 0 ] → C [ 0.225 0.610 0 0 0.089 0.242 0.657 0 ] \begin{equation} A\begin{bmatrix} 1&2&0&0\\\\ 2&3&4&0\end{bmatrix}\to B\begin{bmatrix} 1&1&0&am…

字符串函数和结构题内存对齐

图下为函数使用&#xff1a; #include <ctype.h>int main() {int ret isdigit(Q);printf("%d\n", ret);return 0; }int main() {printf("%c\n", toupper(a));printf("%c\n", tolower(A));return 0; }