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

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.dtcms.com/a/287291.html

相关文章:

  • CSS面试题及详细答案140道之(81-100)
  • 深度学习图像分类数据集—六十种植物病害分类
  • 微信小程序171~180
  • 嵌入式硬件中电感的基本原理与实现详解
  • 六种经典排序算法:从原理到 Java 实现
  • LVS的简介以及架构
  • 闲庭信步使用图像验证平台加速FPGA的开发:第二十四课——图像直方图和RGB图像叠加的FPGA实现
  • 【超详细笔记】概率:中心极限定理的直观理解——样本均值为何趋近正态
  • OpenCV 官翻 2 - 图像处理
  • 聊聊接口测试Postman环境与变量的深度应用
  • 黑马点评系列问题之p70postman报错“服务器异常”
  • Windows11下编译好的opencv4.8-mingw,可下载后直接用
  • 如何解决AttributeError: ‘NoneType‘ object has no attribute问题
  • 深入解析Linux文件重定向原理与dup2系统调用
  • Selenium 中 findElement 方法全解析:定位网页元素的 7 种方式
  • opencv圖片標注
  • 【办公类-107-02】20250719视频MP4转gif(削减MB)
  • 【Project】kafka+flume+davinci广告点击实时分析系统
  • LangGraph教程6:LangGraph工作流人机交互
  • 黄山派lvgl8学习笔记(1)画一个一个全白的背景
  • 【C++】入门阶段
  • Spring Cloud Gateway与Envoy Sidecar在微服务请求路由中的架构设计分享
  • 最新版vscode 连接ubuntu 18.04 保姆级教程
  • 前端面试专栏-工程化:27.工程化实践(CI/CD、代码规范)
  • Jenkins 实现项目的构建和发布
  • Rust Web 全栈开发(十):编写服务器端 Web 应用
  • 触发器的创建
  • Django母婴商城项目实践(七)- 首页数据业务视图
  • QT窗口(8)-QFileDiag
  • 大模型 Function Call 的实现步骤及示例详解