移動開發之手勢與雙指縮放

ajwa4140 7年前發布 | 11K 次閱讀 移動開發

上周遇見一個關于雙指縮放的問題,同時這個雙指縮放也比較常見,于是決定對移動開發手勢做一個學習和總結,并給出一個雙指縮放的實例,希望對讀者提供一些幫助。

先給一個例子,點此查看雙指縮放實例

前言

當年喬布斯的iphone第一次支持多點觸控時,確實驚艷了世人,而現在大部分手機都支持多點觸控,這就有了手勢這個概念,通過多點觸控,形成不同的手勢,開發者根據不同手勢,提供不同功能,比如雙指縮放,是很常見的。

ios的多點觸摸

ios的Safari瀏覽器是第一個支持多點觸摸的瀏覽器,并提供觸摸API供開發者使用,到后來Android 3也開始支持多點觸摸,各大瀏覽器也借鑒Safari提出了觸摸API,除了個別硬件支持屬性外,大致相同,這是值得慶幸的。

IE10的多點觸摸

雖然現在市場上WP手機越來越少,但是為了更完整的學習理解手勢知識,我們也需要進行學習。IE10支持多點觸摸,但是其多點觸摸與ios和android不同,ios和android瀏覽器為多點觸摸提供一個包含touches數組的事件,包含所有多點觸摸對象,而IE10為多點觸摸的每一個觸摸點創建一個單獨的觸摸事件。

漸進增強與手勢

我們知道手勢很方便,用戶也很喜歡手勢,但是我們也要明白手勢并不總是能使用,有些設備或瀏覽器還是不支持的,所以我們最合適的是基于普通點擊和觸摸事件交互的頁面,將手勢作為增強的交互,這就是通常所說的漸進增強思想。

觸摸事件

既然是要漸進增強,那就必須從基礎點擊和觸摸事件說起,點擊事件就不再多說,關于觸摸事件,之前也有一篇文章介紹過 移動開發之輕觸與單擊事件 ,關于觸摸四種基礎事件和事件對象,可以查看該文章,這里就不重復了,這里要對觸摸事件的處理進行說明。

瀏覽器兼容

通常我們在觸摸和手勢事件處理函數內會添加CSS3動畫處理,這個時候需要進行瀏覽器兼容處理。

    var TRANSITION = 'transition';
    var TRANSITION_END = 'transitionend';
    var TRANSFORM = 'transform';
    var TRANSFORM_PROPERTY = 'transform';
    var TRANSITION_PROPERTY = 'transition';

    if (typeof document.body.style.webkitTransform !== undefined) {
        TRANSFORM = 'webkitTransform';
        TRANSITION = 'webkitTransition';
        TRANSITION_END = 'webkitTransitionEnd';
        TRANSFORM_PROPERTY = '-webkit-transform';
        TRANSITION_PROPERTY = '-webkit-transition';
    }

雙指縮放

在移動端手勢事件中,雙指縮放的需求還是很常見的,本節詳細闡述,現在大多數移動設備都支持原生的雙指縮放,但是這樣可能影響頁面布局,而且為了更多可控性,很多時候還是會選擇自行實現縮放手勢功能。

縮放中心

在原生縮放動畫,縮放中心通常是圖像中心,可以拿起移動設備嘗試一番,而在現實需求中,我們通常希望縮放以手勢屏幕的兩個接觸點中心為中心,即縮放中心為接觸點中心。

變換原點

在CSS3動畫中,如, transform動畫旋轉,縮放等動畫 ,有一個 transform-origin 屬性:

The transform-origin property lets you modify the origin for transformations of an element.

transform-origin 屬性使得我們可以修改一個元素變換動畫的原點。

當我們使用CSS3變換動畫進行縮放時,由于變換動畫的變換原點和縮放中心(即接觸點中心)相互獨立,為了不影響縮放中心相對于元素的位置,元素變換縮放時,也應該對縮放中心坐標進行縮放移動,即除了變換縮放元素,還應該對該元素進行額外坐標偏移。

位移偏量

我們希望縮放以接觸點中心為中心,元素向四周縮放,假設變換原點為元素左上角,即 transform-origin: 0 0; ,此時元素變換縮放位移均為正值,僅向右下方向縮放,所以縮放中心(其實也就是該元素)應對應向相反方向(左上方向)偏移,值為 縮放坐標值 * (1 - 縮放比例) 。

如上圖,原始元素A盒,以接觸點M,N兩點的中心點L(x, y)為縮放中心點,向四周縮放,其位置應該如圖中C盒,由于L(x, y)點的位置是不可控的(用戶行為),我們設置元素變換原點 transform-origin: 0 0; ,此時其縮放后位置如B盒,然而我們依然希望達到縮放后形成C盒的位移效果,則我們需要將元素向左上方向位移,假設縮放比例為k, 則元素位移值計算過程如下:

  • 1.計算元素A的位移,其實等效于縮放點的位移,也就是計算坐標系縮放后進行的位移;
  • 2. L(x, y) 點,在縮放前坐標系中坐標為 (x, y) ,則縮放k倍后,其在縮放后坐標系中坐標為 (kx, ky) ;
  • 3.計算兩點位移距離:
    • 水平位移: x – kx;
    • 垂直位移:y – ky;
  • 4.得到元素在水平和垂直方向需要偏移的距離。

縮放的實現

縮放與滾動

我們自行實現的縮放事件需要使用 touchmove 事件,而 touchmove 事件會觸發設備的滾動事件,所以要么阻止滾動,要么不讓屏幕出現滾動條。

  • 阻止滾動

        document.addEventListener('touchmove', function(event){
            event.preventDefault();
        })
  • 阻止滾動條

        html, body {
            height: 100%;
        }

計算縮放中心

縮放動畫以接觸點為中心,當我們使用雙指手勢實現縮放時,這個接觸點中心就是兩個接觸點的中心點:

    function getOrigin(first, second) {
        return {
            x: (first.x + second.x) / 2,
            y: (first.y + second.y) / 2
        };
    }
    getOrigin({
        x: event.touches[0].pageX, 
        y: event.touches[0].pageY
    }, {
        x: event.touches[1].pageX, 
        y: event.touches[1].pageY
    });

計算縮放比例

縮放比例如何確定呢,起始觸摸兩指間距離除以縮放時兩指間距離,即縮放比例:

    function getDistance(start, stop) {
        return Math.sqrt(Math.pow((stop.x - start.x), 2) + Math.pow((stop.y - start.y), 2));
    }

    function getScale(start, stop) {
        return getDistance(start[0], start[1]) / getDistance(stop[0], stop[1]);
    }

處理觸摸事件

要實現縮放功能除了計算相關縮放中心和縮放比例,另外還需要處理觸摸事件:

    var distance = {};
    var origin;
    var scale = 1;
    function handleTouch(e) {
        switch(e.type) {
            case 'touchstart':
                if (e.touches.length > 1) {
                    distance.start = getDistance({
                        x: e.touches[0].screenX, 
                        y: e.touches[0].screenY  
                    }, {
                        x: e.touches[1].screenX, 
                        y: e.touches[1].screenY
                    });
                }
                break;
            case 'touchmove':
                if (e.touches.length === 2) {
                    origin = getOrigin({
                        x: event.touches[0].pageX, 
                        y: event.touches[0].pageY
                    }, {
                        x: event.touches[1].pageX, 
                        y: event.touches[1].pageY
                    });
                    distance.stop = getDistance({
                        x: e.touches[0].screenX, 
                        y: e.touches[0].screenY  
                    }, {
                        x: e.touches[1].screenX, 
                        y: e.touches[1].screenY
                    });
                    scale = distance.stop / distance.start;
                    setScaleAnimation(scale, true);
                }
                break;
            case 'touchend':
                scale = 1;
                setScaleAnimation(scale);
                break;
            case 'touchcancel':
                scale = 1;
                setScaleAnimation(scale);
                break;
            default:;
        }
    }

使用變換

接下來就是使用CSS3動畫縮放圖片了,代碼如下:


    function setScaleAnimation(scale, animation) {
        var transition_animation = '';
        var x, y;
        if (animation) {
            transition_animation = 'none';
        } else {
            transition_animation = TRANSFORM_PROPERTY + ' 0.3s ease-out';
        }
        element.style[TRANSITION] = transition_animation;
        // 計算位移偏量
        x = origin.x + (-origin.x) * scale;// 縮放中心偏移量
        y = origin.y + (-origin.y) * scale;

        // 縮放和位移
        element.style[TRANSFORM] = 'matrix(' + scale + ', 0, 0, ' + scale + ', ' + x + ', ' + y +  ')';
    }

本文關于移動開發手勢及實現雙指縮放介紹到此完結,實現的實例比較粗糙,但原理基本闡述清楚,感興趣可以自己動手實現,優化;本文很久以前就開篇了,但直到今天才終于完筆,若有不足,也歡迎指正。

 

來自:http://blog.codingplayboy.com/2017/04/16/mobile_gesture/

 

 本文由用戶 ajwa4140 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!