实现功能参考:微信小程序自定义组件实现图片单指拖动、双指缩放效果 有修改。
一、明确需求:
- 图片要以AspectFit填充样式展示在其父控件内(即宽和高中比较长的那一条边充满,另外一条边等比放大或缩小)。
- 单指拖动图片,图片跟随手指移动。
- 双指缩放图片,要以【两手指的中间点为原点】进行放大和缩小。
- 单/双指操作松手后的回弹
-
图片scale<1(即宽和高均小于父控件),则直接还原(scale=1)
-
图片scale==1(即图片只被移动过,没有进行缩放),则将图片移回中心(水平居中&&垂直居中)
-
图片scale>1
-
如果图片宽/高(比较短的那一边)没有超过父控件则图片需要水平居中/垂直居中
-
如果图片宽/高(比较短的那一边)没有超过父控件则图片需要水平居中/垂直居中
-
长度已经超过父控件的边(无论宽或高),且图片被拉出父控件的区域,则图片要移回区域内
(底部空白同理 不贴示例图)
二、实现思路:
给图片加拖动事件,我们需要知道它什么时候被点击、被拖拽和拖拽结束。
通过修改imageView的margin-left和margin-top来改变图片的位置以实现图片的单指移动效果和所有操作结束松手后的回弹效果。
通过修改imageView的宽和高以实现图片放大缩小的效果。且需要同时修改其margin-left和margin-top使图片能以【两手指的中间点为原点】缩放。
三、实现
由于我们用到margin来改变图片的位置,所以给imageView设置样式的时候就不是单单写个AspectFit这么简单了。需要计算出图片为了展示出AspectFit的效果所需的宽高和margin。Talk is cheap, show you the code.
<!--pages/Character/CharacterRecog.wxml-->
<scroll-view scroll-y="true" scroll-x="true" class = "imageScroll" style="width:{{img_view_width}}px;height:{{img_view_height}}px">
<image src="{{recogImg}}" class="img" mode="aspectFit"
catchload="_imgLoadEvent"
catchtouchstart='_touchStartEvent'
catchtouchmove='_touchMoveEvent'
catchtouchend='_touchEndEvent'
catchtouchcancel='_touchEndEvent'
style="width: {{ imgWidth }}px;height: {{ imgHeight }}px;margin-top:{{marginTop}}px;margin-left:{{marginLeft}}px;"></image>
</scroll-view>
/* pages/Character/CharacterRecog.wxss */
.imageScroll {
width: 100%;
height: 430rpx;
position: fixed;
background-color: #f0f1f3;
}
.img {
display: block;
background-color: #f0f1f3;
text-align: center;
}
接下来是交互事件
// pages/Character/CharacterRecog.js
var lastTouchPoint = { x: 0, y: 0 };
var newDist = 0;
var oldDist = 0;
Page({
/**
* 图片加载完成方法
*/
_imgLoadEvent: function (event) {
lastTouchPoint = { x: 0, y: 0 };
},
/**
* 触摸开始事件
*/
_touchStartEvent: function () {
lastTouchPoint = { x: 0, y: 0 }
oldDist = 0
},
/**
* 手指移动事件
*/
_touchMoveEvent: function (e) {
//单指移动事件
if (e.touches.length == 1) {
if (lastTouchPoint.x == 0 && lastTouchPoint.y == 0) {
lastTouchPoint.x = e.touches[0].clientX
lastTouchPoint.y = e.touches[0].clientY
} else {
var xOffset = e.touches[0].clientX - lastTouchPoint.x
var yOffset = e.touches[0].clientY - lastTouchPoint.y
this.setData({
marginTop: this.data.marginTop + yOffset,
marginLeft: this.data.marginLeft + xOffset,
})
lastTouchPoint.x = e.touches[0].clientX
lastTouchPoint.y = e.touches[0].clientY
}
// console.log(this.data.marginTop)
}
//双指缩放事件
if (e.touches.length == 2) {
if (oldDist == 0) {
oldDist = this._spacing(e);
} else {
newDist = this._spacing(e);
if (newDist > oldDist + 1) {
this._zoom(newDist / oldDist, e);
oldDist = newDist;
}
if (newDist < oldDist - 1) {
this._zoom(newDist / oldDist, e);
oldDist = newDist;
}
}
}
},
/**
* 触摸事件结束
*/
_touchEndEvent: function () {
//开始回弹
this._reboundAnimation();
},
/**
* 计算x轴上的双指中心点比例
*/
_calcXRatio: function (event) {
var xRatio = ((event.touches[0].clientX + event.touches[1].clientX) / 2 - this.data.marginLeft) / this.data.imgWidth
return xRatio
},
/**
* 计算y轴上的双指中心点比例
*/
_calcYRatio: function (event) {
var yRatio = ((event.touches[0].clientY + event.touches[1].clientY) / 2 - this.data.marginTop) / this.data.imgHeight
return yRatio
},
/**
* 双指缩放
*/
_zoom: function (f, event) {
var xRatio = this._calcXRatio(event)
var yRatio = this._calcYRatio(event)
if (this.data.imgWidth <= this.data.view_width && f < 1) {
var ratio = this.data.view_width / this.data.imgWidth
this.setData({
imgWidth: this.data.imgWidth * ratio,
imgHeight: this.data.imgHeight * ratio
})
return;
}
if (this.data.imgHeight <= this.data.view_height && f < 1) {
var ratio = this.data.view_height / this.data.imgHeight
this.setData({
imgWidth: this.data.imgWidth * ratio,
imgHeight: this.data.imgHeight * ratio
})
return;
}
this.setData({
//此处的ratio为双指中心点在图片的百分比
marginLeft: this.data.marginLeft + xRatio * this.data.imgWidth * (1 - f),
marginTop: this.data.marginTop + yRatio * this.data.imgHeight * (1 - f),
imgWidth: this.data.imgWidth * f,
imgHeight: this.data.imgHeight * f,
})
// console.log(this.data.marginTop)
},
/**
* 计算两指间距
*/
_spacing: function (event) {
var x = event.touches[0].clientX - event.touches[1].clientX;
var y = event.touches[0].clientY - event.touches[1].clientY;
return Math.sqrt(x * x + y * y);
},
/**
* 边界的回弹动画
*/
_reboundAnimation: function () {
if (this.data.imgWidth / this.data.srcWidth < 1) {
//缩放比例已经小于1了,直接还原
this.setData({
marginLeft: this.data.srcMarginLeft,
marginTop: this.data.srcMarginTop,
imgWidth: this.data.srcWidth,
imgHeight: this.data.srcHeight,
})
return ;
}
//图片已铺满宽或高
if (this.data.imgWidth > this.data.img_view_width && this.data.marginLeft > 0) {
//图片宽度已铺满scrollViewWidth且被拉至最左边
this.setData({
marginLeft: 0
})
}
if (this.data.imgWidth > this.data.img_view_width && (this.data.marginLeft + this.data.imgWidth) < this.data.img_view_width) {
//图片宽度已铺满scrollViewWidth且被拉至最右边
this.setData({
marginLeft: this.data.img_view_width - this.data.imgWidth
})
}
if (this.data.imgHeight > this.data.img_view_height && this.data.marginTop > 0) {
//图片高度已铺满scrollViewHeight且被拉至最顶部
this.setData({
marginTop: 0
})
}
if (this.data.imgHeight > this.data.img_view_height && (this.data.marginTop + this.data.imgHeight) < this.data.img_view_height) {
//图片高度已铺满scrollViewHeight且被拉至最底部
this.setData({
marginTop: this.data.img_view_height - this.data.imgHeight
})
}
//图片未铺满宽或高
if (this.data.imgHeight <= this.data.img_view_height) {
//图片高未铺满scrollViewHeight,垂直居中
this.setData({
marginTop: (this.data.img_view_height - this.data.imgHeight) * 0.5
})
}
if (this.data.imgWidth <= this.data.img_view_width) {
//图片宽未铺满scrollViewWidth,水平居中
this.setData({
marginLeft: (this.data.img_view_width - this.data.imgWidth) * 0.5
})
}
},
/**
* 页面的初始数据
*/
data: {
rpxR: "",
img_view_width: "",//scrollview的宽
img_view_height: "",//scrollview的高
marginTop: 0,//图片aspectFit显示时顶部的间距
marginLeft: 0,//图片aspectFit显示时左边的间距
srcMarginTop: 0,//图片未被缩放时顶部的间距
srcMarginLeft: 0,//图片未被缩放时左边的间距
srcWidth: 0,//图片未被缩放时的高
srcHeight: 0,//图片未被缩放时的宽
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
//根据传入的值设置title
wx.setNavigationBarTitle({
title: "通用文字识别"
})
//给标题和按钮文字赋值
this.setData({
rpxR: getApp().globalData.rpxR,
exampleImageName: options.image
})
this.setData({
recogImg: this.data.exampleImageName
})
/**
* 这里计算图片以AspectFill方式填充所需的宽、高、margin-left、margin-top数值
*/
wx.getImageInfo({
src: this.data.exampleImageName,
complete: (res) => {
let imageWidth = res.width;
let imageHeight = res.height;
let scrollWidth = getApp().globalData.clientWidth;
let scrollHeight = 430 / this.data.rpxR;
//图片显示在view中的宽高度
var scaleWidth = 0;
var scaleHeight = 0;
//计算图片居中显示需要的margin
var mLeft = 0;
var mTop = 0;
if (imageWidth / imageHeight > scrollWidth / scrollHeight) {
//图片比较宽,宽度填充
mLeft = 0;
mTop = (scrollHeight - (scrollWidth / imageWidth) * imageHeight) * 0.5;
scaleWidth = scrollWidth;
scaleHeight = imageHeight * (scrollWidth / imageWidth);
} else {
//图片比较长,长度填充
mTop = 0;
mLeft = (scrollWidth - (scrollHeight / imageHeight) * imageWidth) * 0.5;
scaleHeight = scrollHeight;
scaleWidth = imageWidth * (scrollHeight / imageHeight);
}
// console.log(mLeft)
// console.log(mTop)
this.setData({
img_view_width: scrollWidth,
img_view_height: scrollHeight,
imgWidth: scaleWidth,
imgHeight: scaleHeight,
marginLeft: mLeft,
srcMarginLeft: mLeft,
marginTop: mTop,
srcMarginTop: mTop,
srcWidth: scaleWidth,
srcHeight: scaleHeight,
})
}
})
},
})