当前位置: 首页 > java >正文

Uniapp之自定义图片预览

场景

在一些场景,uniapp 的原生图片预览无法满足要求,比如自定义图片预览元素。另外原生的图片预览无法阻止截屏问题。

示例

<template><preview-img-popup ref="preImgPopup" img-field="imageUrl" @menu="onPreImgMenu"></preview-img-popup>
</template>
<script>
export default {data() {return {allDownload: false}},onBackPress() {// 返回时判断是否关闭图片预览,否则关闭并阻止返回if (this.$refs.preImgPopup.isOpen()) {this.$refs.preImgPopup.close();return true;}return false;},methods: {previewImage(url, dataList) {this.$refs.preImgPopup.open(dataList, url)},onPreImgMenu(data) {const _this = this;const allDownload = this.allDownload;uni.showActionSheet({itemList: [allDownload ? '保存图片' : '作者禁止下载'],itemColor: allDownload ? '#000' : 'rgb(243,140,140)',success: (res) => {if (!allDownload) {uni.showToast({title: '作者禁止下载',icon: "none",})return}if (res.tapIndex === 0) {_this.$refs.preImgPopup.save(data.imageUrl).then((res) => {console.log(res)uni.showToast({title: '图片已保存到相册',icon: "none",})}).catch((err) => {console.error('图片保存失败', err);})}}})}}
}
</script>

源码

<script>
export default {name: 'PreviewImgPopup',props: {// 图片字段,当传入urls为对象数组时需要指定imgField: {type: String},// 是否显示右上角的菜单showMenu: {type: Boolean,default: false}},data() {return {urls: [],currentIndex: 0,showVal: false,// 每张图片独立的缩放状态imageStates: [],minScale: 1,maxScale: 3,// swiper控制swiperDisabled: false,// movable-view的尺寸movableWidth: 100,movableHeight: 100,scaleValue: 1}},computed: {preNum() {return `${this.currentIndex + 1}/${this.urls.length}`},// 当前图片的状态currentImageState() {return this.imageStates[this.currentIndex] || {scale: 1, translateX: 0, translateY: 0}},// 当前图片的缩放比例scale() {return this.currentImageState.scale},// 当前图片的X位移translateX() {return this.currentImageState.translateX},// 当前图片的Y位移translateY() {return this.currentImageState.translateY}},methods: {open(urls, indexOrUrl = 0) {this.showVal = truethis.urls = urls || []// 初始化每张图片的状态this.imageStates = this.urls.map(() => ({scale: 1,translateX: 0,translateY: 0}))const maxIndex = this.urls.length - 1if (typeof indexOrUrl === 'string') {// 如果是字符串,则根据图片地址查找let index = -1;for (let i = 0; i < this.urls.length; i++) {const item = this.urls[i]if (this.imgField) {if (item[this.imgField] === indexOrUrl) {index = ibreak}} else {if (item === indexOrUrl) {index = ibreak}}}if (index !== -1) {this.currentIndex = index} else {this.currentIndex = 0}} else {this.currentIndex = indexOrUrl > maxIndex ? maxIndex : indexOrUrl}},isOpen() {return this.showVal},close() {this.showVal = false},onClose() {this.showVal = falsethis.$emit('close')},onSwiperChange(e) {this.currentIndex = e.detail.currentif (this.imageStates[this.currentIndex].scale > 1) {this.swiperDisabled = true}},onImageTap() {this.close()},getImgUrl(data) {if (this.imgField) {return data[this.imgField]}return data},handleMenuClick() {this.$emit('menu', this.urls[this.currentIndex])},/*** 保存图片* @param url 图片地址* @return {Promise<string>}*/save(url) {return new Promise((resolve, reject) => {uni.downloadFile({url: url,success(res) {if (res.tempFilePath) {uni.saveImageToPhotosAlbum({filePath: res.tempFilePath,success(re) {resolve(re.path)},fail(err) {reject(err)}})}},fail(err) {reject(err)}})})},// 重置指定图片的缩放状态resetScale(index = this.currentIndex) {if (this.imageStates[index]) {this.scaleValue = 1this.minScale = 1this.imageStates[index].scale = 1this.imageStates[index].translateX = 0this.imageStates[index].translateY = 0this.$nextTick(() => {this.minScale = 0.6})}},// movable-view事件处理onMovableChange(e, index) {if (index === this.currentIndex && this.imageStates[index]) {const x = this.imageStates[index].translateX = e.detail.xthis.imageStates[index].translateY = e.detail.yconst systemInfo = uni.getSystemInfoSync()const scale = this.imageStates[index].scaleconst winWidth = systemInfo.windowWidthconst scleWidth = (winWidth - 1) * scaleif (scale > 1) {if (x < 0) {if (winWidth - x >= scleWidth) {this.swiperDisabled = false} else {this.swiperDisabled = true}} else if (x >= 0) {this.swiperDisabled = false} else {this.swiperDisabled = true}}}},onMovableScale(e, index) {if (index === this.currentIndex && this.imageStates[index]) {const scale = this.imageStates[index].scale = e.detail.scaleif (scale > 1) {this.swiperDisabled = true} else {this.swiperDisabled = false}// 缩放时的swiper控制:只有在缩放<=1时才允许swiper// 放大时的精确边界检测由onMovableChange处理if (e.detail.scale <= 1) {this.swiperDisabled = false}// 注意:不在这里设置 swiperDisabled = true,让onMovableChange来精确控制}},onTouchEnd(e, index) {return;// uni.$u.throttle(() => {//   if (index === this.currentIndex && this.imageStates[index]) {//     const scale = this.imageStates[index].scale//     console.log('scale', scale)////     // 如果缩放小于1,自动恢复到默认大小//     if (scale < 1) {//       setTimeout(() => {//         this.resetScale(index)//       }, 100)//     }//   }// }, 500)}},mounted() {uni.setNavigationBarColor({frontColor: '#ffffff',backgroundColor: '#000'})}
}
</script><template><u-popup :show="showVal" mode="center" @close="onClose" bgColor="#000" :safeAreaInsetBottom="false"closeOnClickOverlay safe-area-inset-top><view class="preview-img-container"><!-- 顶部工具栏 --><slot name="top"><view class="top-tools"><view class="pre-num">{{ preNum }}</view><view class="menu"><u-icon name="more-dot-fill" color="#fff" size="20px" @click="handleMenuClick"></u-icon></view></view></slot><!-- 图片轮播区域 --><swiper class="img-swiper" :current="currentIndex" @change="onSwiperChange" :indicator-dots="false"indicator-active-color="#fff" indicator-color="rgba(255, 255, 255, .3)" :autoplay="false":circular="false":disable-touch="swiperDisabled" :disable-programmatic-animation="swiperDisabled"><swiper-item v-for="(img, index) in urls" :key="index" class="swiper-item"><movable-area class="movable-area" :scale-area="true"><movable-view class="movable-view" :scale="true" direction="all" :scale-min="minScale" :scale-max="maxScale":inertia="true" :out-of-bounds="false" :damping="100" :scale-value="scaleValue":x="imageStates[index] ? imageStates[index].translateX : 0":y="imageStates[index] ? imageStates[index].translateY : 0"@change="(e) => onMovableChange(e, index)"@scale="(e) => onMovableScale(e, index)" @tap="onImageTap"@touchend="(e) => onTouchEnd(e, index)"><image :src="getImgUrl(img)" class="preview-img" mode="aspectFit"/></movable-view></movable-area></swiper-item></swiper><slot name="bottom"></slot></view></u-popup>
</template><style scoped lang="scss">
.preview-img-container {width: 100vw;height: 100vh;position: relative;display: flex;flex-direction: column;
}.top-tools {position: absolute;top: 0;left: 0;right: 0;z-index: 10;padding: 60rpx 40rpx 40rpx;box-sizing: border-box;display: flex;justify-content: space-between;align-items: center;background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 70%, transparent 100%);.pre-num {color: #fff;font-size: 16px;font-weight: 500;}
}.img-swiper {width: 100%;height: 100%;.swiper-item {width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;}.movable-area {width: 100%;height: 100%;}.movable-view {width: 100%;height: 100%;}.preview-img {width: 100%;height: 100%;max-width: 100%;max-height: 100%;}
}
</style>
http://www.xdnf.cn/news/15745.html

相关文章:

  • Linux --进程信号
  • 初识C++——开启新旅途
  • 【51单片机学习】LED、独立按键
  • ENSP路由综合实验 + 思科(cisco)/华为(ensp)链路聚合实验
  • C++中的vector(2)
  • 基于Python的口腔正畸健康教育聊天机器人开发与评估研究
  • PyCharm + AI 辅助编程
  • 深度学习图像分类数据集—六十种植物病害分类
  • 基于单片机宠物喂食器/智能宠物窝/智能饲养
  • Typecho博客Ajax评论功能实现全攻略
  • 车载诊断架构 --- OEM对于DTC相关参数得定义
  • FastAPI遇上GraphQL:异步解析器如何让API性能飙升?
  • 【iOS】编译和链接、动静态库及dyld的简单学习
  • 5.组合模式
  • Node.js net.Socket.destroy()深入解析
  • 4.循环结构:让电脑做重复的事情
  • 探秘边缘安全架构设计要点解析
  • Redis 如何保证高并发与高可用
  • 【计算机网络架构】树型架构简介
  • 车载传统ECU---MCU软件架构设计指南
  • Netty网络聊天室及扩展序列化算法
  • 2025年睿抗机器人开发者大赛CAIP-编程技能赛(省赛)-RoboCom 世界机器人开发者大赛-本科组
  • FreeRTOS学习笔记之软件定时器
  • 【初识数据结构】CS61B中的基本图算法:DFS, BFS, Dijkstra, A* 算法及其来历用法
  • Java-77 深入浅出 RPC Dubbo 负载均衡全解析:策略、配置与自定义实现实战
  • CS231n-2017 Lecture3线性分类器笔记
  • 时序数据库选型实战:Apache IoTDB技术深度解析
  • 用逻辑回归(Logistic Regression)处理鸢尾花(iris)数据集
  • 移除debian升级后没用的垃圾
  • 电商商品综合排序:从需求分析到实时计算的全方位指南