原文: js實作移動端圖檔預覽:手勢縮放, 手勢拖動,輕按兩下放大...
前言 本文将介紹如何通過js實作移動端圖檔預覽,包括圖檔的 預覽模式,手勢縮放,手勢拖動,輕按兩下放大 等基本功能;
掃碼檢視示例效果:
js實作移動端圖檔預覽:手勢縮放, 手勢拖動,輕按兩下放大...

代碼位址http://pangyongsheng.github.io/imgPreview/
一、功能介紹
圖檔預覽主要有以下幾個功能點組成:
- 監聽圖檔點選事件,進入圖檔預覽模式
- 自定義手勢事件, (雙指縮放,滑動,輕按兩下。。。)
- 監聽圖檔手勢事件,通過 transform-matrix 實作圖檔的各種變換;
二、實作方法
1、圖檔預覽模式
圖檔預覽即點選圖檔在頁面中插入一個黑色全屏背景框并将圖檔居中顯示。封裝時,為了隻對指定圖檔添加功能,可通過監聽指定類名或添加某種屬性的img标簽監聽;另外需在對背景框綁定點選事件,退出預覽模式。一下是一個簡單示例代碼:
//點選圖檔進入預覽
var $Dom = document.querySelector(".preview");
$Dom.onclick = function() {
var temp = this.src;
var objE = document.createElement("div");
objE.innerHTML = '<div class="bgM" >' +
'<img src="'+temp+'" id="img_scan" class="img-custom-img2"/>' +
'</div>';
document.body.appendChild(objE.children[0]);
//退出圖檔預覽事件
var $bg = document.querySelector(".bgM");
$bg.onclick = function() {
var dm = document.querySelector(".bgM");
document.body.removeChild(dm);
}
//阻止事件冒泡
var $img = document.querySelector(".img-custom-img2");
$img.onclick = function(event) {
event.stopPropagation();
}
}
css樣式參考
.bgM{
width: 100%;
height: 100%;
position: absolute;
top: 0;left: 0;right: 0;bottom: 0;
z-index: 1000;
background-color: rgba(0,0,0,0.85);
overflow: hidden;
}
.bgM img{
width: 100%;
max-height:100%;
position: absolute;
top: 0;left: 0;right: 0;bottom: 0;
z-index: 1001;
margin: auto;
}
2、自定義手勢事件
這裡通過監聽移動端touch事件實作自定義雙指縮放,單指滑動,輕按兩下事件,并通過事件屬性傳遞相關參數,如縮放比例,滑動距離等,詳細實作方式參考這篇部落格:
請參考此博文:https://www.cnblogs.com/pangys/p/9119845.html這裡隻大概說明;
當觸發touch事件的時候,會生成一個TouchEvent對象,我們可通過其屬性e.touches.length來判斷是否多點觸控,通過e.touches[index].pageX,e.touches[index].pageY擷取去觸點坐标,通過e.target擷取dom節點;
這裡為了友善,直接監聽document事件然後對目标元素觸發事件,實際也可以直接對img監聽事件,然後分别處理;
(1)手勢事件
- 監聽touchstart事件,若e.touches.length>=2,為雙指事件,擷取觸點坐标(觸點坐标-目标元素.offsetLeft/Top)計算兩個點中點 添加到事件屬性中,改變相關狀态,觸發gesturestart事件;
- 監聽touchmove事件,若e.touches.length>=2,獲目前取觸點坐标和gesturestart坐标,計算出縮放比例及角度,觸發gesturechange事件;
- 監聽touchend事件,根據前面事件記錄的狀态觸發結束gestureend事件;
(2)滑動事件
- 監聽touchstart事件,若e.touches.length<2,為單指事件,擷取觸點坐标(觸點坐标-目标元素.offsetLeft/Top)添加到事件屬性中,記錄事件狀态;
- 監聽touchmove事件,若e.touches.length<2,獲目前取觸點坐标和上一步坐标,計算出移動距離添加到事件屬性中,觸發swipeMove事件;
(3)輕按兩下事件
監聽touchstart事件,若e.touches.length<2,為單指事件,擷取觸點坐标(觸點坐标-目标元素.offsetLeft/Top)添加到事件屬性中,擷取目前時間挫記錄到相關變量中,計算本次時間戳與上次事件時間戳之差,若時間差範圍在指定範圍(0~250)則觸發doubleTouch事件;
參考代碼:
var isTouch = false;
var isDoubleTouch = false; //是否為多觸點
var start = []; //存放觸點坐标
var now, delta; //目前時間,兩次觸發事件時間差
var startPosition, movePosition, endPosition; //滑動起點,移動,結束點坐标
//事件聲明
var gesturestart = new CustomEvent('gesturestart');
var gesturechange = new CustomEvent('gesturechange');
var gestureend = new CustomEvent('gestureend');
var swipeMove = new CustomEvent('swipeMove');
var doubleTouch = new CustomEvent("doubleTouch");
//監聽touchstart事件
document.addEventListener('touchstart', function(e) {
if (e.touches.length >= 2) { //判斷是否有兩個點在螢幕上
isDoubleTouch = true;
start = e.touches; //得到第一組兩個點
var screenMinPoint = getMidpoint(start[0], start[1]); //擷取兩個觸點中心坐标
gesturestart.midPoint = [screenMinPoint[0] - e.target.offsetLeft, screenMinPoint[1] - e.target.offsetTop]; //擷取中心點坐标相對目标元素坐标
e.target.dispatchEvent(gesturestart);
} else {
delta = Date.now() - now; //計算兩次點選時間差
now = Date.now();
startPosition = [e.touches[0].pageX, e.touches[0].pageY];
if (delta > 0 && delta <= 250) { //輕按兩下事件
doubleTouch.position = [e.touches[0].pageX - e.target.offsetLeft, e.touches[0].pageY - e.target.offsetTop];
e.target.dispatchEvent(doubleTouch);
} else { //滑動事件
isTouch = true;
}
}
}, false);
//監聽touchmove事件
document.addEventListener('touchmove', function(e) {
if (e.touches.length >= 2 && isDoubleTouch) { //手勢事件
var now = e.touches; //得到第二組兩個點
var scale = getDistance(now[0], now[1]) / getDistance(start[0], start[1]); //得到縮放比例
var rotation = getAngle(now[0], now[1]) - getAngle(start[0], start[1]); //得到旋轉角度差
gesturechange.scale = scale.toFixed(2);
gesturechange.rotation = rotation.toFixed(2);
e.target.dispatchEvent(gesturechange);
} else if (isTouch) {
movePosition = [e.touches[0].pageX, e.touches[0].pageY];
endPosition = movePosition;
movePosition = [movePosition[0] - startPosition[0], movePosition[1] - startPosition[1]];
startPosition = [e.touches[0].pageX, e.touches[0].pageY];
swipeMove.distance =[movePosition[0].toFixed(2) , movePosition[1].toFixed(2)];
e.target.dispatchEvent(swipeMove);
}
}, false);
//監聽touchend事件
document.addEventListener('touchend', function(e) {
if (isDoubleTouch) {
isDoubleTouch = false;
gestureend.position = endPosition;
e.target.dispatchEvent(gestureend);
};
}, false);
/*
* 兩點的距離
*/
function getDistance(p1, p2) {
var x = p2.pageX - p1.pageX,
y = p2.pageY - p1.pageY;
return Math.sqrt((x * x) + (y * y));
};
/*
* 兩點的夾角
*/
function getAngle(p1, p2) {
var x = p1.pageX - p2.pageX,
y = p1.pageY - p2.pageY;
return Math.atan2(y, x) * 180 / Math.PI;
};
/*
* 擷取中點
*/
function getMidpoint(p1, p2) {
var x = (p1.pageX + p2.pageX) / 2,
y = (p1.pageY + p2.pageY) / 2;
return [x, y];
}
三、圖檔的變換
對于圖檔的每次操作都需在上一次操作的基礎上進行疊加,如果直接使用width,top,left或scale,translate等css樣式需要每次都記錄目前圖檔狀态的全部參數,而且計算較多,這裡考慮使用transform-matrix實作圖檔的基本變換,這樣隻需建立一個數組作為變換矩陣,每次操作直接在目前變換矩陣上修改相關參數即可實作圖像的變換:
transform-matrix :可配置[a,b,c,d,e,f]6個參數,如下圖所示,x和y是初始的坐标,x’ 和y’則是通過矩陣變換後得到新的坐标。變換矩陣,對原先的坐标施加變換,就能得到新的坐标了。依據矩陣變換規則即可得到: x’=ax+cy+e y’=bx+dy+f。
變換 | x方向 | y方向 |
---|---|---|
縮放 | a | d |
移動 | e | f |
(1) 擷取目标元素及相關參數,綁定事件
var $imgs = document.querySelector("#img_scan");
var clientWidth = document.body.clientWidth; //視窗寬
var clientHeight = document.body.clientHeight; //視窗高
var imgWidth = parseInt(window.getComputedStyle($imgs).width); //圖檔寬
var imgHeight = parseInt(window.getComputedStyle($imgs).height); //圖檔高
$imgs.addEventListener('gesturestart', gesturef, false);
$imgs.addEventListener('gesturechange', gesturef, false);
$imgs.addEventListener('gestureend', gesturef, false);
$imgs.addEventListener('swipeMove', gesturef, false);
$imgs.addEventListener('doubleTouch', gesturef, false);
var tMatrix = [1, 0, 0, 1, 0, 0]; //x縮放,無,無,y縮放,x平移,y平移
var originLast, maxSwipeLeft, maxSwipeRight, maxSwipeTop, maxSwipeBottom; //上下左右可拖動距離
(2)監聽 gesturestart 設定 變換中心
case "gesturestart":
var x = event.midPoint[0];
var y = event.midPoint[1];
originLast = event.midPoint;
$imgs.style.transformOrigin = x + "px " + y + "px";
break;
(2)監聽 gesturechange 進行縮放變換,這裡設定了縮放範圍為0.5 ~ 3;
case "gesturechange":
var sc = parseFloat(event.scale);
tMatrix[0] = tMatrix[0] + sc - 1 > 0.5 && tMatrix[0] + sc - 1 < 3 ? tMatrix[0] + sc - 1 : tMatrix[0];
tMatrix[3] = tMatrix[3] + sc - 1 > 0.5 && tMatrix[3] + sc - 1 < 3 ? tMatrix[3] + sc - 1 : tMatrix[3];
var temp = tMatrix.join(",");
$imgs.style.transform = "matrix(" + temp + ")";
break;
(3)監聽 gestureend 擷取移動邊界範圍邊界
case "gestureend":
maxMove();
break;
可移動邊界範圍的計算:
對于圖檔中的任意點可拖動範圍都是相同的,那麼以縮放中心點來計算,如下圖所示,對于圖檔中的縮放中心點p,有縮放後距離邊距的距離,可移動的範圍均為 縮放後增加或減少的距離 - (縮放中心點距離圖檔邊緣的距離),即 | 縮放比例 - 1 | * p點距離邊緣的距離;
代碼如下:
function maxMove(){
//最大可拖動範圍
var sca = tMatrix[0];
maxSwipeLeft = Math.abs(sca - 1) * originLast[0];
maxSwipeRight = Math.abs(sca - 1) * (imgWidth - originLast[0]);
maxSwipeTop = Math.abs(sca - 1) * originLast[1];
maxSwipeBottom = Math.abs(sca - 1) * (imgHeight - originLast[1]);
}
(4)監聽 swipeMove 拖動圖檔,需考慮是否在可拖動範圍
if (!maxSwipeLeft || !maxSwipeRight || !maxSwipeTop || !maxSwipeBottom) return;
if (event.distance[0] > 0 && maxSwipeLeft < tMatrix[4]) return;
if (event.distance[0] < 0 && maxSwipeRight < -tMatrix[4]) return;
if (event.distance[1] > 0 && maxSwipeTop < tMatrix[5]) return;
if (event.distance[1] < 0 && maxSwipeBottom < -tMatrix[5]) return;
tMatrix[4] = tMatrix[4] + parseInt(event.distance[0]);
tMatrix[5] = tMatrix[5] + parseInt(event.distance[1]);
var temp = tMatrix.join(",");
$imgs.style.transform = "matrix(" + temp + ")";
break;
(5)監聽 doubleTouch 實作輕按兩下點縮放
case "doubleTouch":
originLast = event.position;
$imgs.style.transformOrigin = event.position[0] + "px " + event.position[1] + "px";
tMatrix[0] = 2;
tMatrix[3] = 2;
var temp = tMatrix.join(",");
$imgs.style.transform = "matrix(" + temp + ")";
maxMove();
break;
至此一個圖檔預覽的基本功能即可實作 , 也可以通過手勢做旋轉及上下一張的功能;