起因
以前开发H5的问题,我们的H5的轮播实现以前是由两种方案的:
- photoswipe
- swiper
目前使用最多的是swiper,无论是页面内的轮播,还是点击图片预览的轮播,但还是有点问题,无法自适应长图。同样,photoswipe也有相同的问题。
- 无法自适应长图
- 代码繁琐
问题分析
通常使一张长图能够上下滑动,是不需要进行任何操作的,但是你是一个轮播图,它们都是会有一个overflow:hidden,这就造成了长图无法滚动;
那么就有了一个初步的想法,判断图片的高度,如果图片的高度小于屏幕的高度,则垂直剧中。反之,顶部对齐;
实现
首先一个图片的数组,其中包括长图及横向展示图片,进行最基础的渲染:
这里我们就要介绍一个属性
screen(有关用户屏幕的信息),这个属性是可以直接使用的;
- screen.width
- screen.height
- screen.availWidth(访问者屏幕的宽度,以像素计,减去诸如窗口工具条之类的界面特征)
- screen.availHeight
// startPosition 当前显示图片的索引
// 表示当前正在展示的图片的索引
this.activeIndex = this.startPosition
计算出图片需要向左移动的距离
this.translateX = this.activeIndex * -screen.width
对应的dom结构
<div
class="image-preview__swiper-wrapper"
:style=
"{
transform: 'translate3d(' + translateX +'px, 0px, 0px)',
'transition-duration': duration + 's'
}"
>...</div>
接下来就是遍历urls数组中的每一个元素
将true添加到center数组中,表示在图片加载时默认设置为垂直居中
将true添加到loading数组中,表示在图片加载时默认设置为正在加载。
// 监听图片加载,加载完成后取消loading状态
for (let i = 0; i < this.urls.length; i++) {
const img = new Image()
// 初始默认配置
this.center.push(true)
this.loading.push(true)
img.onload = (e) => {
...
}
img.src = this.urls[i]
}
在onload中等到图片加载完成,根据比例公式图片的高度/图片的宽度 = 屏幕的高度/屏幕的宽度,将图片与屏幕的宽高进行等比转化,图片转换后的高度小于屏幕高度则垂直居中,反之顶部对齐。
const height = e.target.height * screen.width / e.target.width
this.center.splice(i, 1, height < screen.height)
this.loading.splice(i, 1, false)
对应的dom结构
<div
v-for="(src, index) in urls"
:key="index"
class="image-preview__swiper-slider"
:class="{ 'is-active': activeIndex === index, 'image-preview__center': center[index] }"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd">
<div class="image-preview__loading" v-if="loading[index]">
<img class="loading__image" src="./loading.png" alt="">
</div>
<img v-else style="width: 100%" class="image-preview__image" :src="src" />
</div>
css
.image-preview__swiper-slider {
background-color: #000;
overflow-y: auto;
flex-shrink: 0;
width: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch
}
.image-preview__center {
display: flex;
align-items: center;
justify-content: center;
}
photoswipe
当我准备接着写的时候,看photoswipe的官方文档,发现photoswipe支持传递四个参数,其中第三个参数是支持传递dom的。
var imgArr = [];
if (this.imgList instanceof Array) {
for (var i in this.imgList) {
let str = `<div style="height: 100%; width: 100%;overflow: scroll;"><img style="height: auto; width: 100%;" src="${this.imgList[i]}"/></div>`;
imgArr.push({html: str});
}
} else {
let str = `<div style="height: 100%; width: 100%;overflow: scroll;"><img style="height: auto; width: 100%;" src="${this.imgList}"/></div>`;
imgArr.push({html: str});
}
let options = {
optionName: 'option value',
history: false,
focus: false,
showAnimationDuration: 0,
hideAnimationDuration: 0,
fullscreenEl: false,
zoomEl: false,
shareEl: false,
tapToToggleControls: false,
tapToClose: true
};
self.gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, imgArr, options);
self.gallery.init();
}
完善轮播
切换
主要使用的方法
@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"
首先touchstart,通过返回的e.touch长度,判断你的是单指还是多指操作。
如果你是单指操作,记录下触摸点在屏幕中的x坐标和y坐标。
如果是多指操作,记录下触碰点位数组
if (e.touches.length === 1) {
this.beginTouch.time = new Date().getTime();
this.beginTouch.screenX = e.touches[0].screenX;
this.beginTouch.screenY = e.touches[0].screenY;
} else {
this.beginMultiTouch[0] = e.touches[0];
this.beginMultiTouch[1] = e.touches[1];
}
接下来在移动过程中,通过touchmove,获取手指头滑动的位置,并记录一下偏移的方向。
this.offsetX = e.touches[0].screenX - this.beginTouch.screenX;
this.offsetY = e.touches[0].screenY - this.beginTouch.screenY;
如果Y轴偏移大于X轴偏移时,移动方向为Y轴,且在手指离开之前不再更新;
if (!this.moveDirection) {
this.moveDirection = Math.abs(this.offsetX) > Math.abs(this.offsetY) ? "x" : "y";
}
之后渲染样式
this.translateX = this.activeIndex * -screen.width + this.offsetX
当图片为第一张或者第二张的时候,禁止向左或者向右滑动。刚一开始是直接判断下标activeIndex,然后return。
if (this.activeIndex === 0 && this.offsetX > 0) return
if (this.activeIndex === this.urls.length - 1 && this.offsetX < 0) return
优化:
if (this.activeIndex === 0 && this.offsetX > 0) {
this.translateX = this.activeIndex * -screen.width + this.offsetX * 0.4
return
}
if (this.activeIndex === this.urls.length - 1 && this.offsetX < 0) {
this.translateX = this.activeIndex * -screen.width + this.offsetX * 0.4
return
}
滑动结束,手指移开,触发touchend,如果滑动的距离超过屏幕宽度的一半。
向左滑动:this.activeIndex -= 1
向右滑动:this.activeIndex += 1
计算整体偏移:
this.translateX = this.activeIndex * -screen.width
放大
在刚一开始我们就通过touchstart记录了是多指点击还是单指,默认多指的时候是放大、缩小的。
touchstart获取触屏的位置
handleTouchStart (e) {
this.beginMultiTouch[0] = e.touches[0]
this.beginMultiTouch[1] = e.touches[1]
}
touchmove获取滑动的位置
this.moveMultiTouch[0] = e.touches[0]
this.moveMultiTouch[1] = e.touches[1]
js 计算两点间距离公式AB²=(x1-x2)²+(y1-y2)²
twoPointDistance(p1,p2){
let dep = Math.sqrt(Math.pow((p1.x - p2.x), 2) + Math.pow((p1.z - p2.z), 2));
return dep;
}
let scale = this.getDistance(this.moveMultiTouch[0], this.moveMultiTouch[1]) / this.getDistance(this.beginMultiTouch[0], this.beginMultiTouch[1])
scale = this.scaleDatum * scale
if (scale < 1) {
this.scale = scale < 0.8 ? 0.8 : scale
} else {
this.scale = scale > 4 ? 4 : scale
}
图片放大操作
const dom = document.querySelectorAll('.image-preview__image')[this.activeIndex]
dom.style.width = `${this.scale * 100}%`
当手指离开,触发touchend,如果是多指,且scale小于1,就把比例设置成原始值,所有值清空。
if (this.scale < 1) {
this.scale = 1
this.scaleDatum = 1
const dom = document.querySelectorAll('.image-preview__image')[this.activeIndex]
dom.style.width = `${this.scale * 100}%`
}
this.offsetX = 0
this.offsetY = 0
this.moveDirection = ''
优化
- 图片放大的时候,不支持滑动到下一张
if (this.scale > 1 || this.moveDirection === 'y') return
- 记录手指点击到离开的间隔,如果挪移距离很小,就代表关闭图片
handleTouchStart (e) {
this.beginTouch.time= new Date().getTime()
}
handleTouchEnd (e) {
this.touchgap = new Date().getTime() - this.beginTouch.time
}
if (Math.abs(this.offsetX) < 2 && Math.abs(this.offsetY) < 2 && this.touchgap < 300) {
this.visible = false
}