uni-app实现小程序、H5图片轮播预览、双指缩放、双击放大、单击还原、滑动切换功能

news2024/12/16 12:05:51

前言

这次的标题有点长,主要是想要表述的功能点有点多;

简单做一下需求描述

产品要求在商品详情页的头部轮播图部分,可以单击预览大图,同时在预览界面可以双指放大缩小图片并且可以移动查看图片,双击放大,单击还原,左右滑动可以切换预览的图片,非放大情况下单击退出预览(类似于淘宝现在的商详图片预览);要求微信小程序和H5中都实现该功能,时间1.5天;

需求分析

  • 轮播图片点击唤起预览界面(这部分功能已经很早实现了,不做过多的解释),界面中可以定制别的内容;
  • 预览图片双指缩放
  • 预览图片放大之后可以拖动查看图片
  • 双击放大
  • 单击还原
  • 滑动切换图片
  • 单击关闭预览图片,同时索引定位到预览的位置

简单思路

图片点击预览图片这个功能是之前就有的,这次其实是加入了放大缩小手势等,想着直接用uni-app的uni.previewImage它支持图片预览,双击放大,拖动,轮播,而且底层是native的性能很棒,很丝滑;不支持关闭预览定位索引,不支持预览界面定制别的内容,因此没办法直接放弃了;

于是打算原生手写一个,尝试之后发现H5能用,但是很卡顿,小程序没法看;

最后想到了可以用uni-app的movable-area和movable-view,开发一个可以拖动的区域,配合swiper就可以了;正好看了一下uni.previewImage的实现源码,发现在H5端也是用这几个组件实现的源码位置,于是决定参照源码开发一下;

代码

<div :class="['img-preview', modal ? 'slide-down-to-up-opacity' : 'slide-up-to-down-opacity']">
    <swiper class="swiper-container" :current="current" :disable-touch="disableTouch" @change="handleChangeSlide">
      <swiper-item v-for="(img, idx) in picList" :key="idx" :class="{'swiper-slide': true}">
        <movable-area scale-area class="movable-area">
          <movable-view
            direction="all"
            :animation="false"
            :scale-min="1"
            :scale-max="2"
            :damping="30"
            :scale-value="img.scale"
            :scale="true"
            :inertia="false"
            :out-of-bounds="false"
            :class="{'movable-view':true}"
            @touchmove="handleTouchmove($event, idx)"
            @click.stop="handleMovableClick($event, idx)"
            @scale.stop="handleOnScale($event, idx)"
          >
            <img
              :key="award ? img.productImageSpecial : img.picture"
              :src="award ? img.productImageSpecial : img.picture"
              mode="widthFix"
              :class="{'preview-img': true}"
            />
          </movable-view>
        </movable-area> 
      </swiper-item>
    </swiper>
    <div v-if="picList && picList.length > 1" class="product-align-single">
      <div class="product-align-dots">
        <div v-for="(item, idx) in picList" :key="idx" :class="{'product-align-dot': true, 'product-align-dot-active': idx === current}"></div>
      </div>
    </div>
  </div>
export default {
  name: 'ImgPreview',
  props: {
    // 显示与隐藏
    value: {
      type: Boolean,
      value: false
    },
    imgList: {
      type: Array,
      default() {
        return []
      }
    },
    initIndex: {
      type: Number,
      default: 0
    },
    fullscreen: {
      type: Boolean,
      default: true
    },
    award: {
      type: Boolean,
      default: false
    }
  },
  emits:['close','change-slide'],
  data () {
    return {
      modal: this.value,
      current: this.initIndex,
      arrowIcon: 'https://static1.keepcdn.com/infra-cms/2023/3/7/17/35/553246736447566b58312f38753731477849327742542f44796c385238397273617968664475477a4f6c4d3d/48x48_e33efe885c6a5df9403962315de3681bad220cd2.png',
      scale: 1,
      lastTapTime: 0, // 记录上一次点击时间
      clickTimer: null,
      clickDelay: 300,
      disableTouch: false,
      picList: []
    }
  },
  watch: {
    value: {
      handler(val) {
        this.modal = val
        if (val) {
          this.picList = []
          this.imgList.forEach(item => {
            this.picList.push({
              ...item,
              scale: 1
            })
          })
        }
      },
      immediate:true
    },
  },
  methods: {
    handleOnScale(event, index) {
      const { scale, x, y } = event.detail
      let item = this.picList[index]
      item.scale = scale
      this.$set(this.picList, index, item)
      this.$forceUpdate()
    },
    handleTouchmove(event, index) {
      this.disableTouch = true
      let item = this.picList[index]
      if (item.scale !== 1) {
        this.disableTouch = true
      } else {
        this.disableTouch = false
      }
    },
    handleMovableClick(e, index) {
      console.log(e, '<===========================')
       // 判断双击事件
      let curTime = e.timeStamp
      if (this.lastTapTime > 0) {
        if (curTime - this.lastTapTime < this.clickDelay) {
          this.lastTapTime = curTime
          clearTimeout(this.clickTimer)
          // 双击
          return this.handleMovableDbClick(e, index)
        }
      }
      this.lastTapTime = curTime;
      clearTimeout(this.clickTimer);
      this.clickTimer = setTimeout(() => {
        // 单击
        this.handleMovableOnClick(e, index)
      }, this.clickDelay)
    },
    // 图片单击事件(关闭预览)
    handleMovableOnClick(e, index) {
      this.modal = false
      setTimeout(() => {
        this.$emit('close', false)
      }, 100)
    },
    // 图片双击事件
    handleMovableDbClick(e, index) {
      let item = this.picList[index]
      item.scale = item.scale < 2 ? 2 : 1
      this.$set(this.picList, index, item)
      this.$forceUpdate()
    },
    handleChangeSlide(event) {
      this.current = event.detail?.current || 0
      this.$emit('change-slide', this.current)
      this.resetScale(this.current)
    },
    resetScale(index) {
      this.picList.forEach((element, idx) => {
        if (idx !== index) {
          element.scale = 1
        }
      })
      this.$forceUpdate()
    }
  }
}
<style lang="less" scoped>
.img-preview {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  opacity: 0;
}
.img-preview-bg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 1);
  z-index: 1;
}
.movable-area {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.movable-view {
  height: 100%;
  width: 100%;
}

.img-preview-bg {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 1);
  z-index: 1;
}
.preivew-swiper{
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
  // padding-top: calc((100vh - 100vw) * 0.356);
  position: relative;
  z-index: 2;
}
.preivew-swiper-fullscreen {
  padding-top: calc((100vh - 100vw) * 0.5);
}
.swiper-container {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  z-index: 2;
}
.swiper-wrapper,
.swiper-slide {
  width: 100% !important;
  height: 100%;
  display: flex;
  align-items: center;
}
.swiper-slide-single {
  height: 133.34vw;
}
.preview-img {
  width: 100%;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-height: 100%;
  max-width: 100%;
}
</style>

css不太全,截取了一部分;

主要是movable-view组件的一些属性配置和事件触发;这里有一点需要注意就是在图片放大的情况下移动图片或触发swiper的滑动,这里就出现了一个问题我搞了半天但是还是没有解决;

怎么阻止swiper手动切换

阻止冒泡事件,event.stopPropagation();

uniapp中禁止 event.preventDefault();event.stopPropagation();
在这里插入图片描述
要想阻止冒泡事件只能用事件修饰符;显然事件修饰符不能根据条件修改,这个路不通;

swiper有没有什么可以禁止滑动的属性呢?有的!
disable-touch

查了一下swiper果然有个属性disable-touch;很开心,终于可以根据条件阻止swiper滑动了,当在movable-view中touchmove且scale!==1的时候disable-touch设为true,反之为false;
但是当在小程序中测试时,发现这个属性并不管用,后来发现该属性在小程序中只有初始化时有用,不能做到动态变更;
在这里插入图片描述

swiper-item添加touchmove

网上很火的解决方案都在21年左右的,但是尝试了一下行不通,不好用!

写一个伪类,用一个蒙层盖住swiper
.swiper {
  position: relative;
  &:after {
   content: '';
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
   z-index: 2;
  }
}

这个方法很好用,之前在别的需求中用过,盖住之后拖动肯定就不滑动了,但是现在的需求显然不能这么用,因为movable-view在swiper中需要拖动;

最后效果

小程序的swiper阻止切换没有实现,同时该组件在小程序端明显卡段,动画不流畅,也没有native那种回弹的效果,跟产品商量了一下也对比了一下决定来个AB实验;

  • 小程序端直接用uni.previewImageAPI,毕竟用户就是想放大看看图片,没必要做那么多嵌套,动画流畅,体验敢强最重要;至于关闭定位索引和在弹框slot别的内容这些暂时在小程序端先不做;
  • H5端用自己写的组件如上,因为uni.previewImage在H5端的效果一般,并且不能双击放大,其余的动画流畅度和性能都一样;
  • 暂时先这样了,也没有过多的人力去研究这个H5的动画,也没必要做个引擎之类的;

参考

  • 移动端单指移动和双指缩放的实现
  • uniapp(移动端)图片双指缩放、单指拖动、双击缩放
  • dcloudio/uni-app
  • uniapp使用 movable-area movable-view 实现图片双指缩放、鼠标单击缩小双击放大、图片及标记点功能
  • 微信小程序swiper禁止用户滑动
  • 苛学加/previewImage

如果有需要增加图片旋转或者长按事件等可以参考这个,可以结合一下看看;就到这里吧;预览图有同学需要可以找我要,我看见就会回复!拜拜~~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据地图怎么做?推荐这款数据可视化地图生成器

在数字化与信息化高速发展的今天&#xff0c;企业迎来了前所未有的发展机遇&#xff0c;规模迅速扩张&#xff0c;市场版图不断延伸。然而&#xff0c;伴随着这种快速的发展&#xff0c;一个不容忽视的问题逐渐浮出水面——如何精准高效地掌握分布在各地的分公司、业务点乃至整…

hive—常用的日期函数

目录 1、current_date 当前日期 2、now() 或 current_timestamp() 当前时间 3、datediff(endDate, startDate) 计算日期相差天数 4、months_between(endDate, startDate) 日期相差月数 5、date_add(startDate, numDays) 日期加N天 6、date_sub(startDate, numDays) 日期减…

【原生js案例】如何让你的网页实现图片的按需加载

按需加载&#xff0c;这个词应该都不陌生了。我用到你的时候&#xff0c;你才出现就可以了。对于一个很多图片的网站&#xff0c;按需加载图片是优化网站性能的一个关键点。减少无效的http请求&#xff0c;提升网站加载速度。 感兴趣的可以关注下我的系列课程【webApp之h5端实…

大模型运用-Prompt Engineering(提示工程)

什么是提示工程 提示工程 提示工程也叫指令工程&#xff0c;涉及到如何设计、优化和管理这些Prompt&#xff0c;以确保AI模型能够准确、高效地执行用户的指令&#xff0c;如&#xff1a;讲个笑话、java写个排序算法等 使用目的 1.获得具体问题的具体结果。&#xff08;如&…

相位小数偏差(UPD)估计基本原理

PPP中的一个关键性难题在于非差模糊度固定&#xff0c;成功固定非差模糊度可以使 PPP 的收敛速度和定位精度得到显著提升 。 相位小数偏差 (UPD) 是致使相位模糊度失去整数特性的主要因素&#xff0c;精确估计并校正 UPD 是实现非差模糊度固定的重要前提&#xff0c;也是实现…

ES倒排索引实现? ES 索引文档过程?ES并发下读写一致?

ES倒排索引实现&#xff0c; ES 索引文档过程&#xff0c;ES并发下读写一致 一、Elasticsearch的基本概念二、Elasticsearch的工作原理简述三、Elasticsearch的应用场景四、面试问题问题1&#xff1a;Elasticsearch中的分片和副本是如何工作的&#xff1f;分片和副本的大小和数…

【h5py】 提取mat文件中的HDF5格式的数据

h5py 提取mat文件中的HDF5格式的数据 使用纯Python查看数据配合Matlab后&#xff0c;使用Python查看数据 一、使用纯Python查看文件数据内容 原理&#xff1a;当HDF5存储的是struct类型数据&#xff0c;解析时要像一棵树&#xff0c;我们需要逐层次的去解析&#xff0c;直到…

【数据分享】2013-2023年我国省市县三级的逐年CO数据(免费获取\excel\shp格式)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2023年的省市县三级的逐年PM2.5数据、2000-2023年的省市县三级的逐年PM10数据、2013-2023年的省市县三级的逐年SO2数据、2000-2023年省市县三级的逐年O3数据和2008-2023年我国省市县三级的逐…

《九重紫》逐集分析鉴赏第一集(下)

主标题&#xff1a;《九重紫》一起追剧吧 副标题&#xff1a;《九重紫》逐集分析鉴赏第一集&#xff08;下&#xff09;/《九重紫》逐集分析鉴赏1 接上回分解&#xff0c;窦昭和宋墨都安置城外万佛寺 交谈没一会儿&#xff0c;天还未亮&#xff0c;兵临寺下 记住这个人&…

Summarizing and Understanding Large Graphs

Summarizing and Understanding Large Graphs【总结和理解大规模图】 ☆ 研究背景 大规模图的理解和可视化是一个重要的开放性问题现有的社区发现和聚类方法无法很好地总结图的特征需要一种能够发现和描述图中重要结构的方法 ★ 成果简介 提出了VoG(Vocabulary-based summar…

人工智能系统

介绍人工智能 的基础书 点击这里 1.1 深度学习的历史&#xff0c;现状与发展 本章将介绍深度学习的由来&#xff0c;现状和趋势&#xff0c;让读者能够了解人工智能系统之上的深度学习负载的由来与趋势&#xff0c;为后面理解深度学习系统的设计和权衡形成初步的基础。我们在后…

康佳Android面试题及参考答案(多张原理图)

JVM 内存分布和分代回收机制是什么? JVM 内存主要分为以下几个区域。 堆(Heap)是 JVM 管理的最大的一块内存区域,主要用于存放对象实例。所有线程共享堆内存,在堆中又分为年轻代(Young Generation)和老年代(Old Generation)。年轻代又分为 Eden 区和两个 Survivor 区(…

(css)element中el-select下拉框整体样式修改

(css)element中el-select下拉框整体样式修改 重点代码&#xff08;颜色可行修改&#xff09; // 修改input默认值颜色 兼容其它主流浏览器 /deep/ input::-webkit-input-placeholder {color: rgba(255, 255, 255, 0.50); } /deep/ input::-moz-input-placeholder {color: rgba…

论文概览 |《Sustainable Cities and Society》2024.12 Vol.116

本次给大家整理的是《Sustainable Cities and Society》杂志2024年12月第116期的论文的题目和摘要&#xff0c;一共包括52篇SCI论文&#xff01; 论文1 Enhancing road traffic flow in sustainable cities through transformer models: Advancements and challenges 通过变压…

【电源专题】开关转换器的三种过流保护方案

开关转换器内部集成功率开关,使限流保护成为基本功能。常用限流方案有三种:恒流限流、折返限流和打嗝模式限流。 恒流限流 对于恒流限流方案,当发生过载情况时,输出电流保持恒定值(ILIMIT)。因此,输出电压会下降。这种方案通过逐周期限流实现,利用流经功率开关的峰值电感…

网络与安全

文章目录 网络协议OSI七层模型TCP/IP协议族TCP协议UDP协议HTTP协议HTTPS协议 SocketSocket编程粘包与拆包 网络安全常见网络攻击及防护放火墙网络加密技术 跨域问题 网络协议 网络协议是计算机网络中设备和系统之间进行数据交换的规则和约定。它定义了数据的格式、传输方式、处…

鸿蒙项目云捐助第六讲鸿蒙App应用的首页导航资讯推荐功能的实现

鸿蒙项目云捐助第六讲鸿蒙App应用的首页导航资讯推荐功能的实现 前面的教程中已实现了启动页&#xff0c;登录页&#xff0c;注册页及首页的部分功能。这里有一些朋友提出问题&#xff0c;如何设置登录页面的背景图片。这里稍带说一个这个功能的实现。 一、登录页面的背景图片…

SpringCloud微服务实战系列:03spring-cloud-gateway业务网关灰度发布

目录 spring-cloud-gateway 和zuul spring webflux 和 spring mvc spring-cloud-gateway 的两种模式 spring-cloud-gateway server 模式下配置说明 grayLb://system-server 灰度发布代码实现 spring-cloud-gateway 和zuul zuul 是spring全家桶的第一代网关组件&#x…

【蓝桥杯每日一题】推导部分和——带权并查集

推导部分和 2024-12-11 蓝桥杯每日一题 推导部分和 带权并查集 题目大意 对于一个长度为 ( N ) 的整数数列 A 1 , A 2 , ⋯ , A N A_1, A_2, \cdots, A_N A1​,A2​,⋯,AN​ &#xff0c;小蓝想知道下标 ( l ) 到 ( r ) 的部分和 ∑ i l r A i A l A l 1 ⋯ A r \sum_{…

[代码随想录17]二叉树之最大二叉树、合并二叉树、二搜索树中的搜索、验证二叉搜索树。

前言 二叉树的题目还是要会一流程构造函数之类的。其中还有回溯的思想 题目链接 654. 最大二叉树 - 力扣&#xff08;LeetCode&#xff09; 一、最大二叉树 思路&#xff1a;还是考察构造二叉树&#xff0c;简单来说就是给你一个数组去构建一个二叉树&#xff0c;递归来解决就…