介绍
微信自带组件movable-area/movable-view可以实现图片的移动及缩放功能,不过其缩放中心是图片的中心(仅可通过transform-origin调节)
我们希望能够以触摸点作为缩放中心进行缩放,同时为了获得更多的控制,我们选择自己实现此功能
需求
1.图片在一定范围内移动
2.图片以触摸点作为缩放中心进行缩放
实现
wxml布局
通过修改transform的属性,达到移动和缩放的效果
1 2 3 4 5 6 7 8 9 10 11 12
| <view class="container"> <view id="map-area" class="map-area" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"> <image id="map-bg" class="map-bg" src="/images/map-indoor-bg.jpg" style="transform: translateX({{mapLeft}}px) translateY({{mapTop}}px) translateZ(0px) scale({{mapScale}}); will-change: transform;" ></image> </view> </view>
|
wxss(使用了scss)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| page { width: 100%; height: 100%; }
.container { width: 100%; height: 100%; .map-area { width: 100%; height: 100%; overflow: hidden; background-color: yellow; $map-width: rpx(1700); $map-height: rpx(1400); .map-bg { transform-origin: 0 0; // 缩放中心设置为(0,0),便于程序计算 width: $map-width; height: $map-height; box-sizing: border-box; border: 1px solid red; } } }
|
js部分(…表示缩略了部分代码)
需要记录如下信息,供移动和缩放计算使用,同时在加载时获取map-bg和map-area的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| Page({ data: { // map-area的初始大小 mapAreaTop: 0, mapAreaWidth: 0, mapAreaHeight: 0, // map-bg的初始大小 originalMapWidth: 0, originalMapHeight: 0, // 缩放后的map-bg的信息 mapScale: 1, mapWidth: 0, mapHeight: 0, // 移动后的map-bg的信息 mapLeft: 0, mapTop: 0, // 触摸触发时,map-bg的信息 initMapScale: 1, initTouchX: 0, // 手指距离map-bg左上角(0,0)的距离 initTouchY: 0, initMapLeft: 0, initMapTop: 0, initGap: 0 // 两指初始间距 }, onLoad: function () { // 获取map-area大小 wx.createSelectorQuery().select('#map-area').boundingClientRect((rect) => { this.data.mapAreaWidth = rect.width // 节点的宽度 this.data.mapAreaHeight = rect.height // 节点的高度 this.data.mapAreaTop = rect.top // 节点的top // P.S. 如果map-area不紧贴左边缘,还需要获取rect.left,并在获取touchX时减去,以保证缩放中心位置准确 }).exec() // 获取map-bg大小 wx.createSelectorQuery().select('#map-bg').boundingClientRect((rect) => { this.data.mapWidth = rect.width // 节点的宽度 this.data.mapHeight = rect.height // 节点的高度 this.data.originalMapWidth = rect.width // 节点的宽度 this.data.originalMapHeight = rect.height // 节点的高度 }).exec() } ... })
|
图片移动
经过实验,我们发现当触摸点增加时会触发touchStart事件,当触摸点减少时会触发touchEnd事件。
所以我们创建一个触摸初始化函数,用于记录单指或双指触摸的初始状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Page({ ... // 初始化单指触摸 oneTouchStart: function (e) { // 获取触摸位置 let touchX = e.touches[0].pageX let touchY = e.touches[0].pageY - this.data.mapAreaTop // 赋值 this.setData({ 'initTouchX': touchX, 'initTouchY': touchY, 'initMapLeft': this.data.mapLeft, 'initMapTop': this.data.mapTop }) } ... })
|
之后在touchStart/touchEnd事件增加初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Page({ ... touchStart: function (e) { // 处理单指移动 if (e.touches.length === 1) { this.oneTouchStart(e) } ... }, touchEnd: function (e) { // 处理单指移动 if (e.touches.length === 1) { this.oneTouchStart(e) } ... } ... })
|
在touchMove事件中处理图片的移动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| Page({ ... touchMove: function (e) { // 处理单指移动 if (e.touches.length === 1) { // 获取触摸位置 let touchX = e.touches[0].pageX let touchY = e.touches[0].pageY - this.data.mapAreaTop // 移动的距离 let mapMovedLeft = touchX - (this.data.initTouchX - this.data.initMapLeft) let mapMovedTop = touchY - (this.data.initTouchY - this.data.initMapTop) // 限制map-bg左上角(0,0)移动距离 let limitRight = 0 let limitBottom = 0 let limitLeft = -(this.data.mapWidth - this.data.mapAreaWidth) let limitTop = -(this.data.mapHeight - this.data.mapAreaHeight) if (mapMovedLeft > limitRight) { mapMovedLeft = limitRight // 在到达边缘时实时刷新触摸点初始位置,借此使反向移动时图片能立即移动 this.setData({ 'initMapLeft': mapMovedLeft, 'initTouchX': touchX }) } if (mapMovedTop > limitBottom) { mapMovedTop = limitBottom this.setData({ 'initMapTop': mapMovedTop, 'initTouchY': touchY }) } if (mapMovedLeft < limitLeft) { mapMovedLeft = limitLeft this.setData({ 'initMapLeft': mapMovedLeft, 'initTouchX': touchX }) } if (mapMovedTop < limitTop) { mapMovedTop = limitTop this.setData({ 'initMapTop': mapMovedTop, 'initTouchY': touchY }) } // 赋值 this.setData({ 'mapLeft': parseInt(mapMovedLeft), 'mapTop': parseInt(mapMovedTop) }) } ... } ... })
|
图片缩放
创建缩放(双指触摸)初始化函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Page({ ... // 初始化双指触摸 twoTouchStart: function (e) { // 获取两指距离 let xGap = e.touches[1].pageX - e.touches[0].pageX let yGap = e.touches[1].pageY - e.touches[0].pageY let gap = Math.sqrt(xGap * xGap + yGap * yGap) // 获取触摸中心 let touchX = (e.touches[0].pageX + e.touches[1].pageX) / 2 let touchY = (e.touches[0].pageY + e.touches[1].pageY) / 2 - this.data.mapAreaTop // 赋值 this.setData({ 'initGap': gap, 'initMapScale': this.data.mapScale, 'initMapLeft': this.data.mapLeft, 'initMapTop': this.data.mapTop, 'initTouchX': touchX, 'initTouchY': touchY }) }, ... })
|
在touchStart/touchEnd事件增加初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Page({ ... touchStart: function (e) { ... // 处理双指缩放 if (e.touches.length === 2) { this.twoTouchStart(e) } }, touchEnd: function (e) { ... // 处理双指缩放 if (e.touches.length === 2) { this.twoTouchStart(e) } } ... })
|
在touchMove事件中处理图片的缩放
这里我们需要处理一下偏移,以保证缩放中心是触摸位置,下图红线部分则是我们需要计算出的偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| Page({ ... touchMove: function (e) { ... // 处理双指动作 if (e.touches.length === 2) { // 获取两指距离 let xGap = e.touches[1].pageX - e.touches[0].pageX let yGap = e.touches[1].pageY - e.touches[0].pageY let gap = Math.sqrt(xGap * xGap + yGap * yGap) // 获取触摸中心 let touchX = parseInt((e.touches[0].pageX + e.touches[1].pageX) / 2) let touchY = parseInt((e.touches[0].pageY + e.touches[1].pageY) / 2 - this.data.mapAreaTop) // 计算实时缩放比例 let mapScale = this.data.initMapScale + (gap - this.data.initGap) * 0.01 console.log(this.data.initGap, gap, ',', this.data.initMapScale, mapScale) // 限制缩小/放大的比例 let limitMinWidthScale = this.data.mapAreaWidth / this.data.originalMapWidth // 宽度最小能缩放的比例 let limitMinHeightScale = this.data.mapAreaHeight / this.data.originalMapHeight // 高度最小能缩放的比例 let limitMinSettingScale = 0.1 // 自定义最小缩放比例 let limitMinScale = Math.max(limitMinWidthScale, limitMinHeightScale, limitMinSettingScale) // 取三者最大值 let limitMaxSettingScale = 10 // 自定义最大缩放比例 if (mapScale < limitMinScale) { mapScale = limitMinScale } if (mapScale > limitMaxSettingScale) { mapScale = limitMaxSettingScale } // 计算缩放后宽高 let mapWidth = this.data.originalMapWidth * mapScale let mapHeight = this.data.originalMapHeight * mapScale // 计算距离触摸中心的偏移 let initMapWidth = this.data.originalMapWidth * this.data.initMapScale let initMapHeight = this.data.originalMapHeight * this.data.initMapScale // 原Left - (现在Width - 初始Width)* (初始触摸点到初始Left距离 / 初始宽度) let mapMovedLeft = this.data.initMapLeft - ((mapWidth - initMapWidth) * (this.data.initTouchX - this.data.initMapLeft) / initMapWidth) let mapMovedTop = this.data.initMapTop - ((mapHeight - initMapHeight) * (this.data.initTouchY - this.data.initMapTop) / initMapHeight) // 限制map-bg左上角(0,0)移动距离 let limitRight = 0 let limitBottom = 0 let limitLeft = -(mapWidth - this.data.mapAreaWidth) let limitTop = -(mapHeight - this.data.mapAreaHeight) if (mapMovedLeft > limitRight) { mapMovedLeft = limitRight } if (mapMovedTop > limitBottom) { mapMovedTop = limitBottom } if (mapMovedLeft < limitLeft) { mapMovedLeft = limitLeft } if (mapMovedTop < limitTop) { mapMovedTop = limitTop } // 赋值 this.setData({ 'mapScale': mapScale, 'mapWidth': mapWidth, 'mapHeight': mapHeight, 'mapLeft': parseInt(mapMovedLeft), 'mapTop': parseInt(mapMovedTop) }) } } })
|
优化安卓端体验
经过实际测试发现安卓端存在卡顿现象,增加限流函数,以提高体验
同时将wxml改成bindtouchmove=”throttleTouchMove”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Page({ ... onLoad: function () { // 设置限流函数 this.throttleTouchMove = this.throttle(this.touchMove, 20, 40) ... }, // 限流函数 throttle: function (func, wait, mustRun) { let timeout let startTime = new Date()
return function () { let context = this let args = arguments let curTime = new Date() clearTimeout(timeout) if (curTime - startTime >= mustRun) { // 如果达到了规定的触发时间间隔,触发 handler func.apply(context, args) startTime = curTime } else { // 没达到触发间隔,重新设定定时器 timeout = setTimeout(func, wait, ...args) } } } ... })
|