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

embedded/2024/12/30 17:01:35/

不引入 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/embedded/149718.html

相关文章

详细讲解axios封装与api接口封装管理

一、axios封装 axios是基于promise的http客户端&#xff0c;用于浏览器和nodejs发送http请求 &#xff0c;对它进行封装主要是为了统一管理请求配置和处理请求和响应的通用逻辑等。以下是常用的封装逻辑和要点 1&#xff1a;引入axios相关依赖 首先引用项目中的axios库&…

【JavaEE】Spring Web MVC

目录 一、Spring Web MVC简介 1.1 MVC简介1.2 Spring MVC1.3 RequestMapping注解1.3.1 使用1.3.2 RequestMapping的请求设置 1.3.2.1 方法11.3.2.2 方法2 二、Postman介绍 2.1 创建请求2.2 界面如下&#xff1a;2.3 传参介绍 一、Spring Web MVC简介 官方文档介绍&#xff…

设计模式之模板方法模式:咖啡,茶,和代码

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 一、模板方法模式概述 \quad 在我们的日常生活中,经常会遇到这样的场景:不同的事物,却有着相似的处理流程。比如泡茶和冲咖啡,虽然最终的饮品不同…

用 ElementUI 的日历组件 Calendar 自定义渲染

文章目录 需求分析1. 页面渲染2. 获取页面上的开始日期和结束日期3. 总的代码 需求 之前实现过一版用 ElementPlus 的日历组件 Calendar 自定义渲染&#xff0c;是在 Vue3 项目中实现的&#xff0c;现在需求在 Vue2 中也实现一版 分析 1. 页面渲染 <el-calendar v-model&q…

【每日学点鸿蒙知识】Web请求支持Http、PDF展示、APP上架应用搜索问题、APP备案不通过问题、滚动列表问题

1、HarmonyOS Web不支持http请求&#xff0c;只支持https&#xff1f; Web组件http请求失败&#xff0c;改成https是好的&#xff0c;是否可以通过配置打开http的请求 参考文档&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-c…

《类和对象:基础原理全解析(中篇)》

目录 一、 类成员函数的 this 指针1. this 指针的使用2. this 指针的注意事项 二、const 成员函数1. const 关键字的添加位置2. const 成员函数的使用建议 三、析构函数1. 拷贝构造函数的使用2. 使用析构函数的注意事项 四、拷贝构造函数1. 使用默认拷贝构造函数的 Stack 类2. …

从零开始学AI,完成AI 企业知识库的AI问答搭建

1&#xff1a;本地安装一个ollama玩下&#xff0c;ollama下载模型默认路径为C盘&#xff0c;但该盘空间不足。 解决方案&#xff1a;添加系统环境变量OLLAMA_MODELS&#xff0c;设置其值为新的路径。 2&#xff1a;安装完成后&#xff0c;访问http://127.0.0.1:11434/ 查看服务…

WebRTC 环境搭建

主题 本文主要描述webrtc开发过程中所需的环境搭建 环境&#xff1a; 运行环境&#xff1a;ubuntu20.04 Node.js环境搭建 安装编译 Node.js 所需的依赖包: sudo apt-get updatesudo apt-get install -y build-essential libssl-dev下载 Node.js 源码: curl -sL https://…