js實現移動端圖片預覽:手勢縮放, 手勢拖動,雙擊放大...

pangyongsheng發表於2018-10-22

前言
本文將介紹如何通過js實現移動端圖片預覽,包括圖片的 預覽模式,手勢縮放,手勢拖動,雙擊放大等基本功能;程式碼地址http://pangyongsheng.github.io/imgPreview/

 檢視示例效果:

js實現移動端圖片預覽:手勢縮放, 手勢拖動,雙擊放大...

一、功能介紹

  圖片預覽主要有以下幾個功能點組成:

  • 監聽圖片點選事件,進入圖片預覽模式
  • 自定義手勢事件, (雙指縮放,滑動,雙擊。。。)
  • 監聽圖片手勢事件,通過 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事件;

(4)單擊事件

監聽touchstart事件,使用延時器450ms觸發單擊事件,若在450ms無其他事件則觸發單擊事件

參考程式碼:

    var isTouch = false;
    var isDoubleTouch = false; //是否為多觸點   
    var start = []; //存放觸點座標
    var now, delta; //當前時間,兩次觸發事件時間差
    var timer = null; //計時器,觸發單擊事件
    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");
    var oneTouch = new CustomEvent("oneTouch");

    //監聽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) { //雙擊事件
                 clearTimeout(timer);

                doubleTouch.position = [e.touches[0].pageX - e.target.offsetLeft, e.touches[0].pageY - e.target.offsetTop];
                e.target.dispatchEvent(doubleTouch);
            } else { //滑動事件
                 timer = setTimeout(function(){
                    e.target.dispatchEvent(oneTouch);//單擊事件
                },450)
            }
            isTouch = true;

        }
    }, false);
    //監聽touchmove事件
    document.addEventListener('touchmove', function(e) {
         clearTimeout(timer);
        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方向
縮放ad
移動ef

(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);
    $imgs.addEventListener('oneTouch', 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;複製程式碼

(6)監聽 oneTouch 實現退出預覽

 case "oneTouch":
      var $bg = document.querySelector(".bgM");
      document.body.removeChild($bg);
      break;
複製程式碼


相關文章