更新:这是一年前写的练手的前端项目,没想到这么多的评论和私聊,有好多没有写清楚的地方重新再完善一下。目前效果只在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; */
}