关于图片缩放,大家可能会想到的是使用transform:scale(x,y)进行对元素的缩放。
但是对于手势缩放的过程中,我们需要在进行缩放的同时,并且要保证双指之间的中心点一直保持在原来的位置,这是我们需要配合使用translate(x,y)来进行位置的调整。但如何计算得到translate(x,y)中的值呢?

其实除去translate,scale的 transform 属性外,我们还可以通过 矩阵matrix(a,b,c,d,e,f) 进行2D转换。具体可以参考张鑫旭老师的博客理解 CSS3 transform中的Matrix(矩阵)。

根据老师的博客,可以知道matrix(a,b,c,d,e,f) 中的参数a、d分别代表水平方向(x)和垂直方向(y)的放大倍数 相当于scale(a,d) ,e、f分别表示水平方向(x)和垂直方向(y)的偏移值 相当于translate(e,f) ,b、c本篇没有用到,不作解释。

元素中的某个点 (x,y) 通过matrix(a,b,c,d,e,f)转换后,新的坐标为 (ax+cy+e, bx+dy+f)

值得注意的是,元素的变换会根据某个原点来进行变换,而这个原点可以根据transform-origin来设置,默认是元素的正中心,即元素的X轴和Y轴的50%。

为了方便计算,我们把元素铺满整个屏幕。

元素的变换过程:
第一次变换:
1、初始化矩阵matrix(1,0,0,1,0,0),分别对应matrix(a,b,c,d,e,f)
2、获取元素中心的 center(x,y)
3、获取双指的中心point(x,y)
4、双指滑动,然后获取缩放倍数scale(这个scale是双指当前在屏幕坐标之间的距离d1与第一次触碰屏幕时的距离d2的比,即d1/d2,这个hammer.js已经帮你算好了)
5、缩放后,原来双指的中心点会偏移,新坐标与原点在x轴上的距离是(point.x-center.x) * scale,同理新坐标与原点在y轴上距离为(point.y-center.y)*scale,由于原点在屏幕上的坐标并不是(0,0),所以新的坐标必须加上原点的偏移值,新的坐标newpoint(x,y):

newpoint.x = scale * (point.x-center.x) + 0 * (point.y-center.y) + 0 + center.x  
           = scale * point.x - scale * center.x + center.x
           = scale * point.x - (scale - 1) * center.x
newpoint.y = 0 * (point.x-center.x) + scale * (point.y-center.y) + 0 +  center.y
           = scale * point.y - scale * center.y + center.y
           = scale * point.y - (scale - 1) * center.y

6、缩放后,如果想要新的坐标回到最初的位置,需要计算偏移值,point(x,y) - newpoint(x,y),通过以下计算可以知道缩放后,如果想要新的坐标回到起初的位置,需要水平偏移(1 - 缩放倍数)* 起初位置与原点的水平距离 同理,垂直方向偏移(1 - 缩放倍数)* 起初位置与原点的垂直距离

point.x - newpoint.x = point.x - [scale * point.x - (scale - 1) * center.x]
                     = point.x - scale * point.x + (scale - 1) * center.x
                     = (1 - scale) * point.x + (scale - 1) * center.x
                     = (1 - scale) * (point.x - center.x)
同理
point.y - newpoint.y = (1 - scale) * (point.y - center.y)

7、通过矩阵变换

matrix( scale, 0, 0, scale,  (1 - scale) * (point.x - center.x),  (1 - scale) * (point.y - center.y) )
其中  
     a = d = scale,
     e = (1 - scale) * (point.x - center.x) ,
     f = (1 - scale) * (point.y - center.y)
     

配上图,自己意会。

第一次变换比较简单,第二次之后就要考虑前边的缩放,还有偏移值了。
第二次之后的变换,要注意三点:
1、实际的缩放倍数是:当前操作的倍数乘以上一次操作后的倍数。
2、作为变换依据的原点也偏移了,偏移值为上一次操作后的偏移值。
3、新的偏移值:当前操作后的偏移值要加上上一次操作后的偏移值。
4、在触摸屏幕(touchstart)一瞬间记录一些初始值:lastScale(上一轮操作缩放倍数),nowCenter(新的变换依据点坐标),poscenter(当前触摸中心点与新的依据点的水平和垂直距离)。
5、缩放过程中,当前的实际倍数是当前倍数操作倍数(双指当前在屏幕坐标之间的距离d1与第一次触碰屏幕时的距离d2的比 scale) 乘以 上一轮操作缩放倍数(lastScale)。
6、需要偏移的值:(1 - 当前操作倍数) * 新的触摸中心点与新的中心原点的距离 + 上一轮的偏移值。

第二次之后变换:

我们把matrix看成一个数组
tMatrix = [a, b, c, d, e, f]

lastScale = tMatrix[0]  //上一次的缩放倍数
lastTranslate = { x : tMatrix[4], y : tMartix[5] } //上一次的偏移值
nowCenter = { x : center.x +lastTranslate.x ,  y : center.y + lastTranslate.y } //重新计算后当前操作的依据点
nowScale = lastScale * scale //当前实际倍数为上次的倍数乘以当前操作的倍数(如果表达不清楚请见谅)

poscenter = { x : ev.center.x - nowCenter .x,  y :  ev.center.y - nowCenter .y} //触摸中心点到新的依据点的水平和垂直距离

newX = (1 - scale) * poscenter.x + lastTranslate.x //新的水平偏移值
newY = (1 - scale) * poscenter.y + lastTranslate.y //新的垂直偏移值

最后tMatrix的值
tMatrix = [nowScale, 0, 0, nowScale, newX, newY ]

总结

最后的公式为:

tMatrix = [a, b, c, d, e, f]

nowScale表示当前缩放倍数
lastScale表示上一次缩放操作的次数(初次操作时1)
lastTranslate表示上一次元素的偏移值,初次是(0,0)
p表示当前单指触摸的坐标或者双指触摸点之间的中心坐标
c表示初始时图像或内容框的中心坐标

a = b = nowScale * lastScale
e = ( 1 - nowScale ) * (p.x - ( c.x + lastTranslate.x ) + lastTranslate.x
f = ( 1 - nowScale ) * (p.y - ( c.x + lastTranslate.y ) + lastTranslate.y
Matrix = [a, b, 0, 0, e, f],通过矩阵变换


缩放操作完成后
lastTranslate.x = e
lastTranslate.y = f
lastScale = a

代码如下,源码放在:

<!DOCTYPE html>
<html>
<head>
	<title>图片缩放</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
	<style type="text/css">
		html,body,#scale,#scale img{
			height:100%;
			width:100%;
			margin:0;
			padding:0;
		}
		#scale{
			position:absolute;
			background:#54a;
			overflow: hidden;
			top:0;
			bottom:0;
			left:0;
			right:0;
		}
		#scale img{
			transition: 0s ease all;
			position:absolute;
			top:0;
		}
	</style>
	 <script src="./Script/hammer.js"></script>
</head>
<body>
<div id="scale">
	<img id="img1" src="timg1.jpg" alt="">
</div>
<script type="text/javascript">

function point2D(x,y){
	return {x : x,y : y};
}

var reqAnimationFrame = (function () {
    return window[Hammer.prefixed(window, 'requestAnimationFrame')] || function (callback) {
        window.setTimeout(callback, 1000 / 60);
    };
})();


var tMatrix = [1,0,0,1,0,0]//x缩放,无,无,y缩放,x平移,y平移

var initScale = 1;//初始化scale
var el =  document.getElementById("img1");//获取元素
var mc = new Hammer.Manager(el)
var ticking = false
var poscenter = point2D(0,0);//缓存双指的中心坐标
var duration = '';//设置过渡效果,用于双击缩放效果
var lastTranslate = point2D(0,0);//记录上次的偏移值
var lastcenter= point2D(el.offsetWidth/2,el.offsetHeight/2)//图像的中心点,用于对比双指中心点

var center = lastcenter
mc.add(new Hammer.Pan({ threshold: 0, pointers: 1 }))
mc.add(new Hammer.Pinch({ threshold: 0 }))
mc.add( new Hammer.Tap({ event: 'doubletap', taps: 2 }) );
mc.on("panmove", onPan);
mc.on("panstart",onPanStart)
mc.on("pinchmove", onPinch);
mc.on("pinchstart",onPinchStart);
mc.on("doubletap",onDoubleTap);

function onPanStart(ev){
	lastTranslate = point2D(tMatrix[4],tMatrix[5])//缓存上一次的偏移值
}
function onPan(ev){	
		duration = ''
		el.className = ''			
		tMatrix[4] = lastTranslate.x + ev.deltaX
		tMatrix[5] = lastTranslate.y + ev.deltaY
		requestElementUpdate('onpan');
	
}
function onPinchStart(ev){
	duration = '';
	lastTranslate = point2D(tMatrix[4],tMatrix[5])//记录上一次的偏移值
	initScale = tMatrix[0] || 1;
	poscenter = point2D(ev.center.x, ev.center.y)
	
	lastcenter = point2D(center.x + lastTranslate.x,center.y + lastTranslate.y)//重新计算放大后的中心坐标
	poscenter = point2D(ev.center.x - lastcenter.x, ev.center.y-lastcenter.y)
	console.log("center",lastcenter.x,lastcenter.y)
	
	requestElementUpdate('onpinchStart');
}
function onPinch(ev){
	var nowScale = tMatrix[0] = tMatrix[3] = initScale * ev.scale;
	var composscal = (1 - ev.scale) 
	//tMatrix[4] = poscenter.x - ((poscenter.x - lastcenter.x) *  ev.scale + lastcenter.x)  + lastTranslate.x//最后加上上一次的偏移值
	//tMatrix[5] = poscenter.y - ((poscenter.y - lastcenter.y) *  ev.scale + lastcenter.y)  + lastTranslate.y
	tMatrix[4] = (1 - ev.scale) * poscenter.x + lastTranslate.x
	tMatrix[5] = (1 - ev.scale) * poscenter.y + lastTranslate.y
	requestElementUpdate('onpinch');	
}

function onDoubleTap(ev){
	duration = ".3s ease all";
	var nowScale = tMatrix[0];
	if(nowScale != 1 || tMatrix[4]!= 0){
		//scale不等于1,要重回1
		tMatrix[0] = tMatrix[3] = 1;
		tMatrix[4] = tMatrix[5] = 0;
	}else{
		var pointer = ev.center
		var scale = 2
		tMatrix[0] = tMatrix[3] = scale
		//var last = point2D
		//tMatrix[4] = pointer.x - ((pointer.x-lastcenter.x) * scale + lastcenter.x);
		//tMatrix[5] = pointer.y - ((pointer.y-lastcenter.y) * scale + lastcenter.y);
		tMatrix[4] = (1 - scale) * ( pointer.x - center.x) 
		tMatrix[5] = (1 - scale) * ( pointer.y - center.y)
	}
	requestElementUpdate('doubleTap');
}

function updateElementTransform() {
	el.style.transition = duration
    var tmp = tMatrix.join(',')
    console.log(tmp)
    el.style.transform = 'matrix(' + tmp + ')';
    ticking = false;
}

function requestElementUpdate() {
	arguments && console.log(arguments[0])

    if(!ticking) {
        reqAnimationFrame(updateElementTransform);
        ticking = true;
    }
}

/**
初始化设置
*/

requestElementUpdate();

</script>
</body>
</html>

更多推荐

使用hammer.js实现图片手势缩放及算法解释