移动端双指缩放,单指拖动div

4,020 阅读4分钟
  • 对于双指缩放,我其实用到了css的transform属性,搭配值matrix(scale(X), skew(X), skew(Y), scale(Y), translate(X), translate(Y));
  • transform属性它默认是从元素中心点开始向外扩展缩放,且不需要浏览器重绘,由GPU运算渲染,所以我将它的tansform-origin 设置为从左上角开始放大或缩小
transform-origin: 0 0;
  • 缩放比例由双指之间的距离来得出,重要的是要处理:双指落到手机上时中心 如何 位移到 手指拉开或者收缩的两指中心点,这里要注意:div放大缩小 已经改变为 从左上角开始
  • 我定义了 startScale -- 双指开始放到屏幕上div的缩放倍数默认为1 leftNum -- 单次双指离开屏幕后div的左侧偏移量(下次开始操作时左侧偏移量),topNum -- 单次双指离开屏幕后div的上侧偏移量(下次开始操作时上侧偏移量),endScale -- 单次双指离开屏幕后div放大缩小的倍数默认为1(下次开始操作时缩放倍数) 注意:div从始至终在文档上的占位大小并没有变过

微信图片_20211226214732.png

//leftNum 和 topNum是偏移量  我们开始设置它们 都为 0
function getOrigin(e){
    if(e.touches.length == 2){
            // 双指
            const org = {
                    x: (e.touches[0].pageX - 2 * leftNum + e.touches[1].pageX) / 2,
                    y: (e.touches[0].pageY - 2 * topNum + e.touches[1].pageY) / 2,
                    distance: Math.sqrt(Math.pow(e.touches[0].pageX - e.touches[1].pageX, 2) + Math.pow(e.touches[0].pageY - e.touches[1].pageY, 2))
            }
            return org;
    }else if(e.touches.length == 1){
            //单指
            return {
                    x: e.touches[0].pageX,
                    y: e.touches[0].pageY
            }
    }
}

绿点代表开始双指中心点位,我们不做处理的话,它放大后,那个点位会移到蓝色点那儿,黑点是现在我们双指在屏幕上的中心点(位移后), 所以我们要做的就是 让 蓝点跑到黑点那儿,

  • 我们开始获取到绿色点位坐标 (不需要我贴代码吧兄弟们,就是两个手指的pageX加起来除以二,pageY加起来除以二)
  • 然后我们再求出先后的距离(根据直角三角形勾股定理)
Math.sqrt(Math.pow(e.touches[0].pageX - e.touches[1].pageX, 2) + Math.pow(e.touches[0].pageY - e.touches[1].pageY, 2))
  • 然后我们可以得到放大或者缩小的倍数,从而得到蓝点的位置,再通过蓝点的位置减去黑点的位置得到偏移量
//获取双指刚放到屏幕的中心点(绿点)
orgData = getOrigin(e);
// 获取新的两指中心点(黑点)
const nowOrg = getOrigin(e);
// 获取放大后的中心点位置(蓝点)
const obj = {
   x: orgData * scale,
   y: orgData * scale
}
const scale = nowOrg.distance / orgData.distance; //缩放的倍数
  • 我们现在可以得出偏移量
leftNumfalse = nowOrg.x + leftNum - obj.x; //得出双指离开时需要缩放的倍数
topNumfalse = nowOrg.y + topNum - obj.y; //得出双指离开时需要缩放的倍数
endScale = scale * startScale; //得出双指离开时需要缩放的倍数 -- 当前缩放的倍数 * 开始操作的倍数
//这里注意一下:为什么我要再加上 leftNum 跟 topNum, 因为假如上次我们处理过,那么这两个变量会有值, 并且如果放大的话 div 会向 左上 移动, 仔细想想,我们的目的是让放大后的点位移到缩放后的两指中心点
parentNode.style.transform = `matrix(${scale * startScale}, 0, 0, ${scale * startScale}, ${nowOrg.x + leftNum - obj.x}, ${nowOrg.y + topNum - obj.y})`;
  • 现在操作手指离开屏幕的操作:把变化的内容记录下来
parentNode.addEventListener('touchend', (e) => {
        parentNode.style.transform = `matrix(${endScale}, 0, 0, ${endScale}, ${leftNumfalse}, ${topNumfalse})`;
        leftNum = leftNumfalse; //如果放大并溢出屏幕的话 这个值为负值
        topNum = topNumfalse; //如果放大并溢出屏幕的话 这个值为负值
        startScale = endScale; //这时候我们要把开始倍数 设置为 缩放后的倍数用来下次缩放
})
  • 现在第一次放大后 我们的布局差不多这样

微信图片_20211226225422.jpg

  • 好了,我们开始看图说话吧,哈哈哈哈哈 中间中性笔矩形为手机屏幕,左上方虚线矩形为第一次变换后div的样子,现在我们把双指放到屏幕上 中心点为A点, 放大后 两指中心点为B点, 现在有人问C点是什么呀??嗐,又忘了嘛?我们的div是从左上角开始缩放的呀,并且占的位置还是最开始的位置,就是初始位置,上面提到了,所以要让C走到B,思考 c的坐标怎么来,B的坐标又怎么来(这个直接可以取到 通过上面 方法 当然可以直接获取到 这里要注意:B点的坐标包括偏移量从虚线框框到B点的距离 也就是多了leftNum 跟 topNumc点的坐标可以通过A点的坐标乘以缩放的倍数得到 所以上面的transform偏移量需要再加上leftNum跟topNum 因为C点的坐标是到实线框的距离
  • 因此得到上一步的代码 把上一次缩放的倍数再乘以这次缩放的倍数,大致逻辑就这样,可能我想复杂了
  • 单指拖动的话比较简单,不用处理缩放大小 只关心偏移量就可以,这里我直接监听的pageX跟pageY变化的距离
// 获取单指放到屏幕的位置
SingleFinger = getOrigin(e);
// move时新节点
const nowObj = {
    x: e.touches[0].pageX,
    y: e.touches[0].pageY
}
parentNode.style.transform = `matrix(${endScale}, 0, 0, ${endScale}, ${nowObj.x - SingleFinger.x + leftNum}, ${nowObj.y - SingleFinger.y + topNum})`;
leftNumfalse = nowObj.x - SingleFinger.x + leftNum;
topNumfalse = nowObj.y - SingleFinger.y + topNum;
  • 这里附上我的代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title></title>
	<style>
		:root{
			--foo: red;
			--border: 1px solid green;
		}
		*{
			margin: 0;
			padding: 0;
		}
		body{
			box-sizing: border-box;
		}
		.parent{
			box-sizing: border-box;
			width: 1000px;
			height: 500px;
			border: var(--border);
			transform-origin: 0 0;
			position: relative;
			
		}
		.imgStyle{
			width: 100%;
			height: 100%;
		}
	</style>
</head>
<body style="position: relative;">
	<div class="parent">
		<img src="./1.jpg" class="imgStyle" />
	</div>
	<script type="text/javascript">
		///////////////////////////////////////
		let orgData = null, nowScale = 1, x, y, twoSig, leftNum = 0, topNum = 0,leftNumfalse, topNumfalse, nowScalefalse;
		let SingleFinger = null, singLeft, singTop, oneSig;
		const parentNode = document.querySelector('.parent');
		function getOrigin(e){
			if(e.touches.length == 2){
				// 双指
				const org = {
					x: (e.touches[0].pageX - 2 * leftNum + e.touches[1].pageX) / 2,
					y: (e.touches[0].pageY - 2 * topNum + e.touches[1].pageY) / 2,
					distance: Math.sqrt(Math.pow(e.touches[0].pageX - e.touches[1].pageX, 2) + Math.pow(e.touches[0].pageY - e.touches[1].pageY, 2))
				}
				return org;
			}else if(e.touches.length == 1){
				//单指
				return {
					x: e.touches[0].pageX,
					y: e.touches[0].pageY
				}
			}
		}
		parentNode.addEventListener('touchstart', (e) => {
			if(e.touches.length == 2){
				let flag = true;
				for(let i = 0; i < e.touches.length; i++){
					if(e.touches[i].target.nodeName != 'IMG'){
						flag = false;
					}
				}
				if(flag) {
					//获取双指刚放到屏幕的中心点
					twoSig = true;
					orgData = getOrigin(e);
					x = getOrigin(e).x;
					y = getOrigin(e).y;
				}
			}else if(e.touches.length == 1){
				oneSig = true;
				SingleFinger = getOrigin(e);
			}
		})
		parentNode.addEventListener('touchmove', (e) => {
			e.preventDefault();
			if(e.touches.length == 2){
				if(twoSig){
					let flag = true;
					for(let i = 0; i < e.touches.length; i++){
						if(e.touches[i].target.nodeName != 'IMG'){
							flag = false;
						}
					}
					if(flag) {
						// 获取新的两指中心点
						const nowOrg = getOrigin(e);
						const scale = nowOrg.distance / orgData.distance;
						if(scale * nowScale < 1) return false;
						// 获取放大后的中心点位置1
						const obj = {
							x: orgData.x * scale,
							y: orgData.y * scale
						}
						parentNode.style.transform = `matrix(${scale * nowScale}, 0, 0, ${scale * nowScale}, ${nowOrg.x + leftNum - obj.x}, ${nowOrg.y + topNum - obj.y})`;
						leftNumfalse = nowOrg.x + leftNum - obj.x;
						topNumfalse = nowOrg.y + topNum - obj.y;
						nowScalefalse = scale * nowScale;
					}
				}
			}else if(e.touches.length == 1){
				if(oneSig){
					const nowObj = {
						x: e.touches[0].pageX,
						y: e.touches[0].pageY
					}
					parentNode.style.transform = `matrix(${nowScalefalse}, 0, 0, ${nowScalefalse}, ${nowObj.x - SingleFinger.x + leftNum}, ${nowObj.y - SingleFinger.y + topNum})`;
					leftNumfalse = nowObj.x - SingleFinger.x + leftNum;
					topNumfalse = nowObj.y - SingleFinger.y + topNum;
				}
			}
		})
		parentNode.addEventListener('touchend', (e) => {
			parentNode.style.transform = `matrix(${nowScalefalse}, 0, 0, ${nowScalefalse}, ${leftNumfalse}, ${topNumfalse})`;
			leftNum = leftNumfalse;
			topNum = topNumfalse;
			nowScale = nowScalefalse;
		})
	</script>
</body>
</html>