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

前言

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

简单做一下需求描述

产品要求在商品详情页的头部轮播图部分,可以单击预览大图,同时在预览界面可以双指放大缩小图片并且可以移动查看图片,双击放大,单击还原,左右滑动可以切换预览的图片,非放大情况下单击退出预览(类似于淘宝现在的商详图片预览);要求微信小程序和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

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

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

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

相关文章

杭州乘云联合信通院发布《云计算智能化可观测性能力成熟度模型》

原文地址&#xff1a;杭州乘云联合中国信通院等单位正式发布《云计算智能化可观测性能力成熟度模型》标准 2024年12月3日&#xff0c;由全球数字经济大会组委会主办、中国信通院承办的 2024全球数字经济大会 云AI计算创新发展大会&#xff08;2024 Cloud AI Compute Ignite&…

第6章图6.21-6.27-《分析模式》原图和UML图对比

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集

如何在谷歌浏览器中设置广告屏蔽

在数字时代&#xff0c;网络广告无处不在&#xff0c;虽然它们为网站提供了收入来源&#xff0c;但有时也会干扰我们的浏览体验。如果你正在寻找一种方法来减少这些干扰&#xff0c;那么在谷歌浏览器中设置广告屏蔽是一个不错的选择。本文将指导你完成这一过程&#xff0c;并简…

认识网络互联设备(二)

交换机 功能&#xff1a; &#xff08;1&#xff09;通过支持并行通信&#xff0c;提高交换机的信息吞吐量&#xff1b; &#xff08;2&#xff09;将传统的一个大局域网上的用户分若干工作组&#xff0c;每个端口连接一台设备或者连接一个工作组&#xff0c;有效的解决了拥塞情…

数据可视化-2. 条形图

目录 1. 条形图适用场景分析 1.1 比较不同类别的数据 1.2 展示数据分布 1.3 强调特定数据点 1.4 展示时间序列数据的对比 1.5 数据可视化教育 1.6 特定领域的应用 2. 条形图局限性 3. 条形图图代码实现 3.1 Python 源代码 3.2 条形图效果&#xff08;网页显示&#…

AMBA-CHI协议详解(十二)

AMBA-CHI协议详解&#xff08;一&#xff09;- Introduction AMBA-CHI协议详解&#xff08;二&#xff09;- Channel fields / Read transactions AMBA-CHI协议详解&#xff08;三&#xff09;- Write transactions AMBA-CHI协议详解&#xff08;四&#xff09;- Other transac…

【MATLAB第109期】基于MATLAB的带置信区间的RSA区域敏感性分析方法,无目标函数

【MATLAB第108期】基于MATLAB的带置信区间的RSA区域敏感性分析方法&#xff0c;无目标函数 参考第64期文章【MATLAB第64期】【保姆级教程】基于MATLAB的SOBOL全局敏感性分析模型运用&#xff08;含无目标函数&#xff0c;考虑代理模型&#xff09; 创新点&#xff1a; 1、采…

《外国服务区加油站模型:功能与美观的完美结合 caotu66.com》

这个外国服务区加油站模型在设计上独具特色&#xff0c;兼具实用性和美观性。 从整体布局来看&#xff0c;加油站位于服务区的显眼位置。加油站的顶棚采用了现代风格的设计&#xff0c;顶棚的颜色主要是黄色和蓝色&#xff0c;色彩鲜明且具有辨识度。顶棚下方有多个加油柱&…

mybatis-plus超详细讲解

mybatis-plus &#xff08;简化代码神器&#xff09; 地址&#xff1a;https://mp.baomidou.com/ 目录 mybatis-plus 简介 特性 支持数据库 参与贡献 快速指南 1、创建数据库 mybatis_plus 2、导入相关的依赖 3、创建对应的文件夹 4、编写配置文件 5、编写代码 …

数据结构(顺序表)JAVA方法的介绍

前言 在 Java 中&#xff0c;集合类&#xff08;Collections&#xff09;是构建高效程序的核心组件之一&#xff0c;而 List 接口作为集合框架中的重要一员&#xff0c;是一个有序、可重复的元素集合。与 Set 接口不同&#xff0c;List 保证了元素的顺序性&#xff0c;并允许存…

泊松编辑 possion editing图像合成笔记

开源地址&#xff1a; GitHub - kono-dada/Reproduction-of-possion-image-editing 掩码必须是矩形框

【Flink-scala】DataStream编程模型之状态编程

DataStream编程模型之状态编程 参考&#xff1a; 1.【Flink-Scala】DataStream编程模型之数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 3.【Flink-scala】DataStream编程模型之窗口计算-触发器-驱逐器 4.【Flink-scal…

Linux实操篇-远程登录/Vim/开机重启

目录 传送门前言一、远程登录1、概念2、ifconfig3、实战3.1、SSH&#xff08;Secure Shell&#xff09;3.2、VNC&#xff08;Virtual Network Computing&#xff09;3.3、RDP&#xff08;Remote Desktop Protocol&#xff09;3.4、Telnet&#xff08;不推荐&#xff09;3.5、FT…

【计算机网络】期末考试预习复习|上

作业讲解 物理层作业 共有4个用户进行CDMA通信。这4个用户的码片序列为&#xff1a; A: (–1 –1 –1 1 1 –1 1 1)&#xff1b;B: (–1 –1 1 –1 1 1 1 –1) C: (–1 1 –1 1 1 1 –1 –1)&#xff1b;D: (–1 1 –1 –1 –1 –1 1 –1) 现收到码片序列&#xff1a;(–1 1 –…

CTFHub-ssrf

技能树--Web--SSRF 内网访问 开启题目 尝试访问位于127.0.0.1的flag.php吧 进入环境 根据提示输入即可 127.0.0.1/flag.php 伪协议读取文件 开启题目 尝试去读取一下Web目录下的flag.php吧 进入环境&#xff0c;根据提示输入 file:///var/www/html/flag.php 鼠标右键查看…

解决PyTorch模型推理时显存占用问题的策略与优化

在将深度学习模型部署到生产环境时&#xff0c;显存占用逐渐增大是一个常见问题。这不仅可能导致性能下降&#xff0c;还可能引发内存溢出错误&#xff0c;从而影响服务的稳定性和可用性。本文旨在探讨这一问题的成因&#xff0c;并提供一系列解决方案和优化策略&#xff0c;以…

Java从入门到工作3 - 框架/工具

3.1、SpringBoot框架结构 在 Spring Boot 或微服务架构中&#xff0c;每个服务的文件目录结构通常遵循一定的约定。以下是一个常见的 Spring Boot 服务目录结构示例&#xff0c;以及各个文件和目录的简要说明&#xff1a; my-service │ ├── src │ ├── main │ │…

电子应用设计方案-56:智能书柜系统方案设计

智能书柜系统方案设计 一、引言 随着数字化时代的发展和人们对知识获取的需求增加&#xff0c;智能书柜作为一种创新的图书管理和存储解决方案&#xff0c;能够提供更高效、便捷和个性化的服务。本方案旨在设计一款功能齐全、智能化程度高的智能书柜系统。 二、系统概述 1. 系…

2024 年贵州技能大赛暨全省第二届数字技术应用职业技能竞赛“信息通信网络运行管理员”赛项--linux安全题

Linux操作系统渗透测试 Nmap -sS -p- ip 扫描 这题有俩种做法&#xff0c;一种用3306端口&#xff0c;另一种用48119端口 用48119端口是最简单的做法 nc 连接这个端口如何修改root密码 ssh连接 这样我们就成功的拿到root权限 1.通过本地PC中渗透测试平台Kali对服务器场景进…

网格剖分算法 铺装填充算法效果

1.原图 图&#xff1a;原图 2.OpenCV提取轮廓 图&#xff1a;提取轮廓线 3.计算凸包和最小外围轮廓 图&#xff1a;计算凸包和最小包围轮廓 4.网格剖分效果 图&#xff1a;网格剖分效果 5.铺装填充效果 图&#xff1a;铺装算法效果 原图--》提取轮廓线--》计算最小外包轮廓--》…