更新:这是一年前写的练手的前端项目,没想到这么多的评论和私聊,有好多没有写清楚的地方重新再完善一下。目前效果只在h5上进行测试成功,其他地方未测试,请见谅。

可以发现uniapp自带的图片预览不能完全适应淘宝轮播需求

uni.previewImage({
	current:0,
	urls:imgsArray
});

新增:模板数据格式(自行修改拓展):需要获取到type类型,详细内容如下

// sort排序方式需要的话就添加上吧,看业务需求和后端返回的数据顺序和格式了
this.list =[
{url:"https://s1.ax1x.com/2022/05/11/OUw2HP.jpg",type:'jpg',sort:1},
{url:"https://cloud.video.taobao.com/play/u/712371218/p/2/e/6/t/1/352489605202.mp4",type:'mp4',sort:2},
{url:"https://s1.ax1x.com/2022/05/11/OUrgxg.jpg",type:'jpg',sort:2},
{url:"https://cloud.video.taobao.com/play/u/712371218/p/2/e/6/t/1/353144490876.mp4",type:'mp4',sort:2},
{url:"https://s1.ax1x.com/2022/05/11/OUrRMQ.png",type:'png',sort:3}
];

实现轮播的需求:
根据插件市场上的插件将图片和视频做一个判断,加入到swiper-item基本可以满足我们的需求
创建一个组件页面,内容基本不用改动。
我这儿就新建一个pages/detail/index.vue页面,将如下内容拷贝到新页面中

<template>
	<view class="previewImage"  @touchmove.stop.prevent>
		<swiper class="swiper" :current="index" @change="swiperChange" :disable-touch="swiper" :circular="false">
			<swiper-item v-for="(img, i) in imgs"  :key="'swiper-item-'+i" :id="'swiper-item-'+i">
				<view class="marea">
					<view
						:id="'movable-view-'+i"
						:key="'movable-view-'+i"
						class="mview"
						@change="movableChange"
					>
						<image
							@tap.stop="previewImages(index)"
							v-if="img.type == 'png' || img.type == 'jpg'|| img.type == 'svg' || img.type == 'jpeg'"
							:id="'image-'+i"
							:key="'movable-view'+i"
							class="image"
							:src=""
							:data-index="i"
							:data-src=""
							mode="aspectFit"
						/>
						<video 
							@tap.stop
							v-else
							:id="'video-'+i"
							:key="'movable-view'+i"
							class="image"
							:data-index="i"
							:enable-progress-gesture="false"
							:src=""
							:data-src=""
							show-progress
							objectFit="cover">
						</video>
					</view>
				</view>
			</swiper-item>
		</swiper>
		<view class="page" v-if="imgs.length > 0">
			<text class="text">{{ index + 1 }} / {{ imgs.length }}</text>
		</view>
	</view>
</template>

<script>
export default {
	name: 'my-swiper', //插件名称
	props: {
		imgs: {
			//图片列表
			type: Array,
			required: true,
			default: () => {
				return [];
			}
		},
	},
	data() {
		return {
			swiper:false,//是否禁用
			index: 0, //当前页
			time: 0, //定时器
			scale: 1 //缩放比例
		};
	},
	methods: {
		// 父子组件中传值,实现轮播和预览轮播
		previewImages(index){
			this.$emit("previewImages",index)
		},

		//图片改变
		swiperChange(e) {
			let myVideo = document.getElementById("video-"+this.index);
			
			if(myVideo !== null){
				let videoCtx = uni.createVideoContext("video-"+this.index)
				videoCtx.pause();
			}
						
			this.index = e.target.current; //更新当前图片index

			//this.swiper=true;
		},
		
		//移动变化
		movableChange(e) {
			
			/* if(this.old.scale <= 1){
				this.swiper=false;
			}else if(e.detail.x===0){
				this.swiper=false;
			} */
		},
	}
};
</script>

<!--使用scss,只在本组件生效-->
<style lang="scss" scoped>
.previewImage {
	top: 0;
	left: 0;
	width: 100%;
	height: 466rpx;
	user-select: none;
	background-color: #000000;
	.swiper {
		width: 100%;
		height: 100%;
		.marea {
			height: 100%;
			width: 100%;
			position: fixed;
			overflow: hidden;
			.mview {
				z-index: 9;
				display: flex;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: 100%;
				.image {
					width: 100%;
					height: 100%;
				}
			}
		}
	}
	.page {
		z-index: 2;
		position: absolute;
		width: 100%;
		bottom: 60rpx;
		text-align: center;
		.text {
			position: relative;
			top: 40rpx;
			color: #fff;
			font-size: 26rpx;
			padding: 3rpx 16rpx;
			user-select: none;
		}
	}

}
</style>

页面中如何去使用该页面组件?

<template>
	<view class="screen-swiper detail-boby">
		<mySwiper v-show="previeShow" :imgs="list" ref="myswiper" @previewImages="previewImages"></mySwiper>
	</view>  
</template>
<script>
import mySwiper from '@/components/my-swiper/my-swiper.vue';
export default{
	components: {mySwiper}, //注册插件
	data() {
		return{
			list: [],
		}
	},
	onLoad: function () {
		//为了演示方便,这儿直接写死的图床中的数据,正常应该是请求后端传递过来的
		this.list = [
		{url:"https://s1.ax1x.com/2022/05/11/OUw2HP.jpg",type:'jpg',sort:1},
		{url:"https://cloud.video.taobao.com/play/u/712371218/p/2/e/6/t/1/352489605202.mp4",type:'mp4',sort:2},
		{url:"https://s1.ax1x.com/2022/05/11/OUrgxg.jpg",type:'jpg',sort:2},
		{url:"https://cloud.video.taobao.com/play/u/712371218/p/2/e/6/t/1/353144490876.mp4",type:'mp4',sort:2},
		{url:"https://s1.ax1x.com/2022/05/11/OUrRMQ.png",type:'png',sort:3}
		];
	},
	methods: {
		// 预览轮播
		previewImages(index){
			if (this.list[index].type !== "mp4") {
				// this.previeShow = false;
				var stateObj = {};
				let that = this;
				let onjs = JSON.stringify(this.list)
				uni.navigateTo({
					url: '/components/kxj-previewImage/kxj-previewImage?onjs='+onjs+'&index='+index+'&id='+this.id,
					events: {
						// 监听子组件传过来的值
						changePreviewIndex(index){
							that.$refs.myswiper.index = index;
						},
					}
				});
			}
		},
	}
}
</script>

以上代码只是实现了轮播图的效果,但是需求还需要点击预览,所以接下来请看
为了实现返回可以取消预览而不是突兀的返回到上一级,这儿新定义一个路由页面,图片地址连接使用json路由参数的方式进行传参

// 图片点击预览轮播
previewImages(index){
	// 当为音频形式的时,点击进行音频播放,不处理
	if (this.list[index].type !== "mp4") {
		// this.previeShow = false;
		var stateObj = {};
		let that = this;
		let onjs = JSON.stringify(this.list)
		uni.navigateTo({
			url: '/components/kxj-previewImage/kxj-previewImage?onjs='+onjs+'&index='+index+'&id='+this.id,
			events: {
				// 监听子组件传过来的值,这儿为了实现联动效果,
				// 即:预览翻到某一图片/视频,轮播图同样滚动到相关的地方
				changePreviewIndex(index){
					that.$refs.myswiper.index = index;
				},
			}
		});
	}
},

kxj-previewImage组件内容主要是在插件市场中安装的,找了一下没有找到,现在应该有更优雅的组件了,先把我使用的代码和位置沾出来吧:components/kxj-previewImage/kxj-previewImage.vue,同样的看需求来改,数据格式一样的话可以不用修改里面的代码

<template>
	<view class="previewImage" :style="{ 'background-color': 'rgba(0,0,0,' + opacity + ')' }" v-if="show" @tap="close" @touchmove.stop.prevent>
		<text @tap.stop="close" class="lg text-gray cuIcon-close close"></text>
		<swiper class="swiper" :current="index" @change="swiperChange" :disable-touch="swiper" :circular="circular">
			<swiper-item v-for="(img, i) in imgs" :key="'swiper-item-'+i" :id="'swiper-item-'+i">
				<movable-area class="marea" scale-area>
					<movable-view
						:id="'movable-view-'+i"
						:key="'movable-view-'+i"
						class="mview"
						direction="all"
						:out-of-bounds="false"
						:inertia="true"
						damping="90"
						friction="2"
						scale="true"
						scale-min="1"
						scale-max="4"
						:scale-value="scale"
						@scale="onScale"
						@change="movableChange"
					>
						<image
							@tap.stop
							v-if="img.type == 'png' || img.type == 'jpg'|| img.type == 'svg' || img.type == 'jpeg'"
							:id="'image-'+i"
							:key="'movable-view'+i"
							class="image"
							:src="img.url"
							:style="{ transform: 'rotateZ(' + deg + 'deg)' }"
							:data-index="i"
							:data-src="img.url"
							mode="widthFix"
							@touchmove="handletouchmove"
							@touchstart="handletouchstart"
							@touchend="handletouchend"
						/>
						<video 
							@tap.stop
							v-else
							:id="'video-'+i"
							:key="'movable-view'+i"
							class="image"
							:style="{ transform: 'rotateZ(' + deg + 'deg)' }"
							:data-index="i"
							@touchmove="handletouchmove"
							@touchstart="handletouchstart"
							@touchend="handletouchend"
							:enable-progress-gesture="false"
							:src="img.url"
							:data-src="img.url"
							show-progress
							objectFit="cover">
						</video>
					</movable-view>
				</movable-area>
			</swiper-item>
		</swiper>
		<view class="page" v-if="imgs.length > 0">
			<text class="text">{{ index + 1 }} / {{ imgs.length }}</text>
		</view>
		<!-- <view class="save" v-if="saveBtn" @click.stop.prevent="save"><text class="text">保存</text></view> -->
		<!-- <view class="rotate" v-if="rotateBtn" @click.stop.prevent="rotate"><text class="text">旋转</text></view> -->
		<view class="desc" v-if="descs.length > 0 && descs.length == imgs.length && descs[index].length > 0">{{ descs[index] }}</view>
	</view>
</template>

<script>
	import baseUrl from '@/utils/config.js';
export default {
	name: 'ksj-previewImage', //插件名称
	props: {
		// imgs: {
		// 	//图片列表
		// 	type: Array,
		// 	required: true,
		// 	default: () => {
		// 		return [];
		// 	}
		// },
		descs: {
			//描述列表
			type: Array,
			required: false,
			default: () => {
				return [];
			}
		},
		//透明度,0到1之间。
		opacity: {
			type: Number,
			default: 0.8
		},
		//保存按键
		saveBtn: {
			type: Boolean,
			default: true
		},
		//旋转按键
		rotateBtn: {
			type: Boolean,
			default: true
		},
		//循环预览
		circular:{
			type: Boolean,
			default: false
		}
	},
	onLoad(option) {
		// this.eventChannel = this.getOpenerEventChannel()
		let json = JSON.parse(option.onjs);
		// console.log(json);
		this.imgs = json;
		this.id = parseInt(option.id)
		// console.log(option.index)
		this.open(parseInt(option.index))
	},
	data() {
		return {
			imgs: [],
			swiper:false,//是否禁用
			show: true, //显示状态
			index: 0, //当前页
			deg: 0, //旋转角度
			time: 0, //定时器
			interval: 1000, //长按事件
			scale: 1 ,//缩放比例,
			id:0,
		};
	},
	computed: {
		baseUrl() {
			return baseUrl
		}
	},
	methods: {
		//比例变化
		onScale(e) {
			
		},
		substringShow (param) {
			if(param !== undefined){
				return param.substring(param.lastIndexOf('images/') + 7)
			}
		},
		//长按事件相关内容---------开始-------------------
		//接触开始
		handletouchstart(e) {
			var tchs = e.touches.length;
			if (tchs != 1) {
				return false;
			}
			this.time = setTimeout(() => {
				this.onLongPress(e);
			}, this.interval);
			return false;
	
		},
		//清除定时器
		handletouchend() {
			clearTimeout(this.time);
			if (this.time != 0) {
				//处理点击时间
			}
			return false;
		},
		//清除定时器
		handletouchmove() {
			clearTimeout(this.time);
			this.time = 0;
		},
		// 处理长按事件
		onLongPress(e) {
			var src = e.currentTarget.dataset.src;
			var index = e.currentTarget.dataset.index;
			var data = { src: src, index: index };
			this.$emit('longPress', data);
		},
		//长按事件相关内容---------结束-------------------

		//图片改变
		swiperChange(e) {
			let myVideo = document.getElementById("video-"+this.index);
			
			if(myVideo !== null){
				let videoCtx = uni.createVideoContext("video-"+this.index)
				videoCtx.pause();
			}
						
			this.index = e.target.current; //更新当前图片index
			
			this.$nextTick(function() {
				this.scale = 1;
			})
			this.getOpenerEventChannel().emit("changePreviewIndex",this.index);
			//this.deg = 0; //旋转角度
			//this.swiper=true;
		},
		
		//移动变化
		movableChange(e) {
			
			/* if(this.old.scale <= 1){
				this.swiper=false;
			}else if(e.detail.x===0){
				this.swiper=false;
			} */
		},
		

		//保存
		save(e) {
			var _this = this;
			var src = this.imgs[this.index];
			//#ifdef MP-WEIXIN
			//提前向用户发起授权请求
			uni.authorize({
				scope: 'scope.writePhotosAlbum',
				success() {
					console.log('kxj-previewImage:允许储存');
					_this.downloadImg(src);
				}
			});
			//#endif

			//#ifdef APP-PLUS
			this.downloadImg(src);
			//#endif

			//#ifdef H5
			//非同源图片将直接打开
			var abtn = document.createElement('a');
			abtn.href = src;
			abtn.download = '';
			abtn.target = '_blank';
			abtn.click();
			//#endif
		},

		//下载并保存文件
		downloadImg(src) {
			//下载图片文件
			uni.showLoading({
				title: '大图提取中'
			});
			uni.downloadFile({
				url: src,
				success: function(res) {
					console.log('kxj-previewImage:下载成功');
					uni.hideLoading();
					uni.saveImageToPhotosAlbum({
						filePath: res.tempFilePath,
						success: () => {
							uni.showToast({
								title: '已保存至相册',
								duration: 1000
							});
						}
					});
				},
				fail: function() {
					uni.hideLoading();
					uni.showToast({
						title: '图片下载失败',
						icon: 'none',
						duration: 1000
					});
				}
			});
		},

		//旋转
		rotate(e) {
			this.deg = this.deg == 270 ? 0 : this.deg + 90;
		},
		//打开
		open(e) {
			if (e === null || e === '') {
				console.log('kxj-previewImage:打开参数无效');
				return;
			}

			if (!isNaN(e)) {
				if(e>=this.imgs.length){
					console.log('kxj-previewImage:打开参数无效');
				}else{
					this.index = e;
				}
			} else {
				var index = this.imgs.indexOf(e);
				if(index===-1){
					this.imgs = [e];
					this.index = 0;
					console.log('kxj-previewImage:未在图片地址数组中找到传入的图片,已为你自动打开单张预览模式')
				}else{
					this.index = this.imgs.indexOf(e);
				}
			}
			// console.log('kxj-previewImage:当前预览图片序号'+this.index);
			this.show = true;
		},
		//关闭
		close(e) {
			// this.show = false;
			// this.index = 0; //当前页
			this.deg = 0; //旋转角度
			this.time = 0; //定时器
			this.interval = 1000; //长按事件
			this.scale = 1; //缩放比例
			
			// this.$emit("colseToShow");
			let canNavBack = getCurrentPages();
			  if(canNavBack && canNavBack.length>1) {  
			    uni.navigateBack({  
			      delta: 1  
			    });  
			  } else {  
			    uni.redirectTo({
					url:'/pages/detail/index?id='+ this.id
				});
			  }
		}
	}
};
</script>

<!--使用scss,只在本组件生效-->
<style lang="scss" scoped>
.previewImage {
	z-index: 9999;
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background-color: #000000;
	user-select: none;
	.swiper {
		width: 100%;
		height: 100%;
		z-index: 9999;
		.marea {
			z-index: 9999;
			height: 100%;
			width: 100%;
			position: fixed;
			overflow: hidden;
			.mview {
				display: flex;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: auto;
				min-height: 100%;
				.image {
					width: 100%;
					max-height: 70vh;
				}
			}
		}
	}
	
	.close{
		position: absolute;
		z-index: 20;
		top: 20px;
		left: 20px;
		font-size: 30px;
	}

	.page {
		position: absolute;
		width: 100%;
		bottom: 20rpx;
		text-align: center;
		.text {
			color: #fff;
			font-size: 26rpx;
			background-color: rgba(0, 0, 0, 0.5);
			padding: 3rpx 16rpx;
			border-radius: 20rpx;
			user-select: none;
		}
	}
	.save {
		position: absolute;
		left: 10rpx;
		width: 120rpx;
		height: 56rpx;
		bottom: 10rpx;
		text-align: center;
		padding: 10rpx;
		.text {
			background-color: rgba(0, 0, 0, 0.5);
			color: #fff;
			font-size: 30rpx;
			border-radius: 20rpx;
			border: 1rpx solid #f1f1f1;
			padding: 6rpx 22rpx;
			user-select: none;
		}
		.text:active {
			background-color: rgba(100, 100, 100, 0.5);
		}
	}
	.rotate {
		position: absolute;
		right: 10rpx;
		width: 120rpx;
		height: 56rpx;
		bottom: 10rpx;
		text-align: center;
		padding: 10rpx;
		.text {
			background-color: rgba(0, 0, 0, 0.5);
			color: #fff;
			font-size: 30rpx;
			border-radius: 20rpx;
			border: 1rpx solid #f1f1f1;
			padding: 6rpx 22rpx;
			user-select: none;
		}
		.text:active {
			background-color: rgba(100, 100, 100, 0.5);
		}
	}
	.desc {
		position: absolute;
		top: 0;
		width: 100%;
		padding: 5rpx 10rpx;
		text-align: center;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		background-color: rgba(0, 0, 0, 0.5);
		color: #fff;
		font-size: 28rpx;
		letter-spacing: 3rpx;
		user-select: none;
	}
}
</style>

预览效果:

注意:
使用colorui框架的时候,需要注释colorui/mian.css约在2589行

.swiper-item video {
	width: 100%;
	display: block;
	height: 100%;
	margin: 0;
	// 当时好像是因为视频选择问题。
	/* pointer-events: none; */
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐