一、当手指触摸图片后,图片以全屏形式在手机上显示出来,就是调用了这段代码。
dealWithPicScale(picUrl) 方法做了两件事:
1、接收图片 url,并设置给 image 标签的 src 属性(这里用的微信小程序的 this.setData()方法实现页面渲染,如果要用其他框架,那就改成对应框架的方法)。
2、获取图片实际的尺寸比例,后面缩放图片时会用到。
picSizeRatio (图片尺寸比例,在缩放图片时会用到),
dealWithPicScale(picUrl){
this.setData({
picUrl: picUrl,
picHidden: false
})
wx.getImageInfo({
src: picUrl,
success: (res)=>{
this.picSizeRatio = Math.floor(res.height / res.width);
}
})
},
二、图片全屏显示后,接下来会触摸图片,这就是触摸图片后触发的事件方法。
(historyX ,historyY), 记录触摸屏幕的实际坐标,记录这个坐标的原因是手指在屏幕上滑动时会先触发该事件,后触发手指移动的事件,正好记录下手动滑动屏幕的起始点,后面计算滑动距离时会用到该坐标值。
picTapped(res) 方法做了两件事:
1、记录该事件触发的状态。
2、记录记录手指触摸屏幕的 x y 坐标。
picTapped(res){
this.data.eventState.clicked = true; // 设置状态为 true,表示该事件已触发了
const touches = res.touches;
this.data.eventState.historyX = Math.floor(touches[0].pageX);
this.data.eventState.historyY = Math.floor(touches[0].pageY);
// console.log("图片点击了")
// console.log(res)
},
三、手指在屏幕上滑动时会触发的事件方法
(eventState.x,eventState.y),记录手指在屏幕上滑动的轨迹,然后赋值给四个 pageXXX 参数。这里出现 “单指移动” 和 “双指缩放” 两种情况,在这里做了分支处理(单指移动需要两个参数,双指需要四个)。
picMoved(res) 方法做了两件事:
1、记录手指在屏幕上滑动的轨迹。
2、区分出是 “单指” 还是 “双指” 事件,收集相应参数做分支处理。
picMoved(res){
this.data.eventState.moved = true; // 设置为 true, 表示手指滑动事件触发了
const touches = res.touches;
this.data.eventState.x = Math.floor(touches[0].pageX);
this.data.eventState.y = Math.floor(touches[0].pageY);
let pageX1;
let pageY1;
let pageX2;
let pageY2;
if(touches.length > 1){
for(let i = 0; i < touches.length; i++){
if(i == 0){
pageX1 = Math.floor(touches[i].pageX);
pageY1 = Math.floor(touches[i].pageY);
}
if(i == 1){
pageX2 = Math.floor(touches[i].pageX);
pageY2 = Math.floor(touches[i].pageY);
}
}
this.controlPicScale2(pageX1, pageY1, pageX2, pageY2); // 双指缩放图片走该分支
}else{
pageX1 = Math.floor(touches[0].pageX);
pageY1 = Math.floor(touches[0].pageY);
this.controlPicScale1(pageX1, pageY1); // 单指移动图片走该分支
}
// console.log("图片移动了")
// console.log(res)
},
四、单指移动图片的处理方法
// pageX1, pageY1 是当前手指触摸屏幕的坐标值
controlPicScale1(pageX1, pageY1){
// 双指与单指间会出现串扰,通过该值判断并消除串扰
const doubleTap = this.data.eventState.doubleTap;
const pWidth = this.picWidth; // 收集图片宽
const pHeight = this.picHeight; // 收集图片高
const screenHeight = wx.getSystemInfoSync().windowHeight; // 拾取手机屏幕的高度
const screenWidth = wx.getSystemInfoSync().windowWidth; // 拾取手机屏幕的宽度
// 消除串扰所做的判断
if(doubleTap == 0){
// 判断图片的宽度是否超过手机屏幕的宽度或高度,如果超过了,说明图片被缩放了
if(pWidth > screenWidth || pHeight > screenHeight){
// 收集移动的终点 x 坐标与移动起点 x 坐标之差,用于决定横向移动距离和方向
const width = pageX1 - this.data.eventState.historyX;
// 收集移动的终点 y 坐标与移动起点 y 坐标之差,用于决定纵向移动距离和方向
const height = pageY1 - this.data.eventState.historyY;
// left, top 记录的是图片元素相对于父元素的偏移量(对应于 margin-left 和 margin-top)
// 这里的数值 1.5 是为了加快移动的速度,这个值可根据个人喜好来修改
// tempWidth,tempHeight , 就是计算出的实际要移动图片的距离了
let tempWidth = this.data.eventState.left + Math.round(width * 1.5);
let tempHeight = this.data.eventState.top + Math.round(height * 1.5);
// screenWidth - pWidth 之差决定了图片横向移动的最大距离
// screenHeight - pHeight 之差决定了图片纵向能移动的最大距离
const maxLeft = screenWidth - pWidth;
const maxHeight = screenHeight - pHeight;
// width > 0,说明图片向右移动
if(width > 0){
// tempWidth > 0,说明图片向右移动到极限位置,此时的 margin-left 最值应赋 0
// tempWidth 就是用于设置 margin-left 的
if(tempWidth > 0){
tempWidth = 0;
}
}else{
// tempWidth < maxLeft,说明图片向左移动到极限位置,maxLeft 就是该极限值
// 此时的 tempWidth 与 maxLeft 都是负数
if(tempWidth < maxLeft){
tempWidth = maxLeft;
}
}
// height > 0,说明是在向下移动
if(height > 0){
// tempHeight > 0,说明图片向下移动到了极限位置,此时 margin-top 应赋 0
if(tempHeight > 0){
tempHeight = 0;
}
}else{
// tempHeight < maxHeight,说明图片向上移动到了极限位置
// 此时的 tempHeight 与 maxHeight 都是负数
if(tempHeight < maxHeight){
tempHeight = maxHeight;
}
}
// 将前面算出的横向移动和纵向移动距离渲染到屏幕上,此时图片就动起来了
this.setData({
'eventState.left': tempWidth,
'eventState.top': tempHeight
})
// 渲染工作完成,用新的坐标替换掉旧的坐标,用新的 left, top 值替换旧的,
// 因为手指移动事件是一个在连续触发的事件,所以要不断的用新值替换旧值
this.data.eventState.historyX = pageX1;
this.data.eventState.historyY = pageY1;
this.data.eventState.left = tempWidth;
this.data.eventState.top = tempHeight;
}else{
// 图片没有被缩放的情况下,走这条分支,图片没有被缩放说明不需要移动图片,故,都赋 0
this.setData({
'eventState.left': 0,
'eventState.top': 0
})
}
}
},
五、双指缩放图片的处理方法
// pageX1, pageY1, pageX2, pageY2,这四个参数是当前手指触摸屏幕的坐标
controlPicScale2(pageX1, pageY1, pageX2, pageY2){
this.data.eventState.doubleTap = 2; // 记录状态用于消除与单指事件间的串扰
const width = pageX2 - pageX1; // 两指间的 x 坐标之间的距离(横向)
const height = pageY2 - pageY1; // 两指间的 y 坐标之间的距离(纵向)
// distance1 是前一个两指滑动间的距离
const distance1 = this.data.eventState.distance;
// 运用勾股定理计算当前两指滑动间的距离
const distance2 = Math.floor(Math.sqrt(width * width + height * height));
// distance2 - distance1 之差,可以确认双指的滑动方向,即放大还是缩小图片
const result = distance2 - distance1;
// 收集图片宽高值
let picWidth = this.picWidth;
let picHeight = this.picHeight;
// 收集图片宽高比
const picSizeRatio = this.picSizeRatio;
// 图片横向缩放的像素尺寸,该值可修改,值越大横向缩放速度越快
const widthSize = 10;
// 图片纵向缩放的像素尺寸,该值可修改,值越大纵向缩放速度越快
const heightSize = Math.round(14 * picSizeRatio);
const leftSize = widthSize / 5; // 将横向移动的大小量分成 5 份
const topSize = Math.floor(heightSize / 7); // 将纵向移动的大小量分成 7 份
let left;
let top;
// middleX ,middleY,是缩放图片的原点,即双指缩放图片时的中心点
let middleX = this.data.eventState.middleX;
let middleY = this.data.eventState.middleY;
// 收集当前图片所处的偏移位置,即 margin-left,和 margin-top 的值
const tempLeft = this.data.eventState.left;
const tempTop = this.data.eventState.top;
const screenHeight = wx.getSystemInfoSync().windowHeight; // 拾取手机屏幕高
const screenWidth = wx.getSystemInfoSync().windowWidth; // 拾取手机屏幕宽
const tPiece = Math.round(screenHeight / 7); // 将手机屏幕划成 7 个像素行块
const lPiece = Math.round(screenWidth / 5); // 将手机屏幕划成 5 个像素坚块
// middleX, middleY 的值都为零,说明中心点还未初始化,那么就将双指间的中心点赋予它
// 之所以有这个判断是因为手指移动事件是连续触发的,而拾取中心点只拾取一次就够了
if(middleX == 0 && middleY == 0){
middleX = Math.floor((pageX1 + pageX2) / 2);
middleY = Math.floor((pageY1 + pageY2) / 2);
// 下面的赋值可能是冗余,笔者懒得去思考了,留给你们
this.data.eventState.middleX = middleX;
this.data.eventState.middleY = middleY;
}
// result = distance2 - distance1,用于确定是放大还是缩小图片的事件
// result > 0,说明是放大图片
if(result > 0){
// picWidth 大于 960个像素时,就赋予极限值 960,该值可根据个人喜好修改
if(picWidth >= 960){
picWidth = 960;
left = tempLeft;
top = tempTop;
}else{
// 如果图片宽度在 960 以内,就以图片宽高加上 widthSize, heightSize 的值放大图片
picWidth = picWidth + widthSize;
picHeight = picHeight + heightSize;
// 下面这些代码就是在修改 left,top 的值,目的就是为了在缩放图片时能以双指间的中心点
// 为原点来缩放图片,是基于前面将屏幕横向 7 等份,纵向 5 等份划分后的操作,即将屏幕划分
// 出了 35 个像素块, 中心点落在其中某个块后,就对应的修改 left,top 的大小,达到以中心点为
// 原点的缩放效果
if(middleX <= lPiece){
left = tempLeft - leftSize;
}else if(middleX > lPiece && middleX <= lPiece * 2){
left = tempLeft - leftSize * 2;
}else if(middleX > lPiece * 2 && middleX <= lPiece * 3){
left = tempLeft - leftSize * 3;
}else if(middleX > lPiece * 3 && middleX <= lPiece * 4){
left = tempLeft - leftSize * 4;
}else if(middleX > lPiece * 4 && middleX <= screenWidth){
left = tempLeft - leftSize * 5;
}else{
left = tempLeft;
}
if(middleY <= tPiece){
top = tempTop - topSize;
}else if(middleY > tPiece && middleY <= tPiece * 2){
top = tempTop - topSize * 2;
}else if(middleY > tPiece * 2 && middleY <= tPiece * 3){
top = tempTop - topSize * 3;
}else if(middleY > tPiece * 3 && middleY <= tPiece * 4){
top = tempTop - topSize * 4;
}else if(middleY > tPiece * 4 && middleY <= tPiece * 5){
top = tempTop - topSize * 5;
}else if(middleY > tPiece * 5 && middleY <= tPiece * 6){
top = tempTop - topSize * 6;
}else if(middleY > tPiece * 6 && middleY <= screenHeight){
top = tempTop - topSize * 7;
}else{
top = tempTop;
}
}
}else{
if(picWidth <= screenWidth){
picWidth = screenWidth;
left = tempLeft;
top = tempTop;
}else{
picWidth = picWidth - widthSize;
picHeight = picHeight - heightSize;
if(middleX <= lPiece){
left = tempLeft + leftSize;
}else if(middleX > lPiece && middleX <= lPiece * 2){
left = tempLeft + leftSize * 2;
}else if(middleX > lPiece * 2 && middleX <= lPiece * 3){
left = tempLeft + leftSize * 3;
}else if(middleX > lPiece * 3 && middleX <= lPiece * 4){
left = tempLeft + leftSize * 4;
}else if(middleX > lPiece * 4 && middleX <= screenWidth){
left = tempLeft + leftSize * 5;
}else{
left = tempLeft;
}
if(middleY <= tPiece){
top = tempTop + topSize;
}else if(middleY > tPiece && middleY <= tPiece * 2){
top = tempTop + topSize * 2;
}else if(middleY > tPiece * 2 && middleY <= tPiece * 3){
top = tempTop + topSize * 3;
}else if(middleY > tPiece * 3 && middleY <= tPiece * 4){
top = tempTop + topSize * 4;
}else if(middleY > tPiece * 4 && middleY <= tPiece * 5){
top = tempTop + topSize * 5;
}else if(middleY > tPiece * 5 && middleY <= tPiece * 6){
top = tempTop + topSize * 6;
}else if(middleY > tPiece * 6 && middleY <= screenHeight){
top = tempTop + topSize * 7;
}else{
top = tempTop;
}
}
}
// 计算好图片宽与高、left, top 后渲染页面
this.setData({
picWidth: picWidth,
picHeight: picHeight,
'eventState.left': left,
'eventState.top': top
})
// 保存当前双指间的距离,再调用该方法时会用到
this.data.eventState.distance = distance2;
// 保存当前图片宽高和偏移值
this.picWidth = picWidth;
this.picHeight = picHeight;
this.data.eventState.left = left;
this.data.eventState.top = top;
},
六、触摸结束后会触发的事件方法
touchEnded(res){
// 这个 if 块用于实现图片以全屏显示还是即出全屏显示
if(this.data.eventState.clicked == true
&& this.data.eventState.moved == false
&& this.data.eventState.doubleTap == 0){
this.setData({
picHidden: true
})
// 之所以要执行初始化方法,是因为图片退出全屏显示后必须将与图片相关的所有状态复位
// 初始化文档和图片事件状态
this.initEventState();
this.data.eventState.clicked = false;
}else{
this.data.eventState.moved = false;
}
// 以下这段代码较为麻烦,建议不要动它,直接复制粘贴就好
// 当图片缩小到最小时,使图片恢复到原始位置
const picWidth = this.picWidth;
const picHeiht = this.picHeight;
let left = this.data.eventState.left;
let top = this.data.eventState.top;
const screenHeight = wx.getSystemInfoSync().windowHeight;
const screenWidth = wx.getSystemInfoSync().windowWidth;
// 当前图片的宽高减去屏幕的宽高,就是图片可以左右、上下移动的最大距离
const widthGap = picWidth - screenWidth;
const heightGap = picHeiht - screenHeight;
// 取绝对值是为了便于参与条件判断
let absLeft = Math.abs(left);
let absTop = Math.abs(top);
// 以下三个 if 块说来话长,较为麻烦建议不要动它
if(left > 0 && top < 0 && absTop < heightGap){
absTop = absTop + heightGap;
}
if(left > 0 && top > 0 && absLeft < widthGap){
absLeft = absLeft + widthGap;
}
if(left < 0 && top > 0 && absLeft < widthGap){
absLeft = absLeft + widthGap;
}
// this.data.eventState.doubleTap == 2,之所以有它是因为定时器会开始两次,用它消去冗余
// absLeft > widthGap || absTop > heightGap,这个条件判断与上面三个 if 块息息相关,
// 之所以有上面三个 if 条件块,就是因为这个条件判断不能充分解决问题,会有漏网之鱼
if(this.data.eventState.doubleTap == 2 && (absLeft > widthGap || absTop > heightGap)){
// 定时器里的代码用于控制当图片偏移位置超过了最大偏移量时能使图片弹回到合理位置
// 那 + 1, - 1 的 1,千万别改,改了就全废了
const timer = setInterval(()=>{
if(left > 0 && top > 0){
left = left - 1;
top = top - 1;
}
if(left < 0 && top < 0){
left = left + 1;
top = top + 1;
}
if(left > 0 && top < 0){
left = left - 1;
top = top + 1;
}
if(left < 0 && top > 0){
left = left + 1;
top = top - 1;
}
if(left == 0 && top > 0){
top = top - 1;
}
if(left == 0 && top < 0){
top = top + 1;
}
if(left > 0 && top == 0){
left = left - 1;
}
if(left < 0 && top == 0){
left = left + 1;
}
// 渲染 left, top 值以使图片弹回到合理位置
this.setData({
'eventState.left': left,
'eventState.top': top
})
// 保存状态值
this.data.eventState.left = left;
this.data.eventState.top = top;
const absL = Math.abs(left);
const absT = Math.abs(top);
// 这里是整个功能实现里最复杂的地方,千万别动,因为定时器的关闭有其精确的时间点
if(absL == widthGap && absT == heightGap && left < 0
|| absL == widthGap && absT < heightGap && left < 0 && top < 0
|| absT == heightGap && absL < widthGap && left < 0 && top < 0
|| left == 0 && absT == heightGap && top < 0
|| left == 0 && absT < heightGap && top < 0
|| left == 0 && top == 0
|| top == 0 && absL < widthGap && left < 0
|| top == 0 && absL == widthGap && left < 0){
clearInterval(timer);
}
}, 1);
}
// 这段是为了消除干扰
const doubleTap = this.data.eventState.doubleTap;
if(doubleTap > 0){
this.data.eventState.doubleTap--;
}
// 将双指间的中心点复位
this.data.eventState.middleX = 0;
this.data.eventState.middleY = 0;
// console.log("移动结束了")
// console.log(res)
},
七、重要的代码都展示完了,接下来给出 其它必要的代码
// 初始化方法
initEventState(){
this.picHeight = wx.getSystemInfoSync().windowHeight;
this.picWidth = wx.getSystemInfoSync().windowWidth;
this.data.eventState.clicked = false;
this.data.eventState.moved = false;
this.data.eventState.left = 0;
this.data.eventState.top = 0;
this.data.eventState.distance = 0;
this.data.eventState.middleX = 0;
this.data.eventState.middleY = 0;
this.data.eventState.doubleTap = 0;
this.setData({
picHeight: this.picHeight,
picWidth: this.picWidth,
'eventState.left': 0,
'eventState.top': 0
})
},
// 在 onLoad 里要调用它
onLoad(options) {
this.initEventState();
}
// DATA 块
data: {
picUrl: '',
picHidden: true,
picWidth: '',
picHeight: '',
picSizeRatio: '',
eventState: {
clicked: false,
moved: false,
x: '',
y: '',
historyX: '',
historyY: '',
left: '',
top: '',
distance: '',
middleX: '',
middleY: '',
doubleTap: ''
}
}
// 标签
<view
hidden="{{picHidden}}"
class="control-pic-show"
catch:touchstart="picTapped"
catch:touchmove="picMoved"
catch:touchend="touchEnded">
<view style="width:{{picWidth}}px;height:{{picHeight}}px;margin-left:{{eventState.left}}px;margin-top:{{eventState.top}}px;">
<image src="{{picUrl}}" style="width:100%;height:100%;" mode="aspectFit"></image>
</view>
</view>
<!-- 用于占位,文档隐藏后不至于丢失滚动条高度 -->
<view style="width:100%;height:1px;background-color:{{picHidden == true ? '#FFFFFF;' : '#222222;'}}"></view>
// css
.control-pic-show {
background-color: #222222;
height:100vh;
overflow: hidden;
}