手勢魅力-設定一個觸控選單

itclanCoder發表於2018-02-09

序言

本篇為一移動端博文,個人覺得這篇外文還可以,就翻譯了一下,最終實現的一個效果是:用手勢建立一個本地選單(點選一選單按鈕,實現設定一個觸控側滑選單,滑動滑出效果),主要涉及的知識點有移動端三大觸控事件(touchstart,touchmove,touchend),觸控屬性,以及實現側邊欄動畫,在處理移動端點選,拖動,滑動時,是不得要考慮使用者的觸控手勢,判斷手指在頁面上到底是點選還是滑動的,利用原生js的方法封裝點選,移動,抬起功能函式,儘管移動(手機)端與pc端有很多相似之處,但還是有很多要注意的地方的,如果你想獲得該Demo的原始碼,複製該標題在微信itclanCoder公眾號後臺回覆[手勢魅力-設定一個觸控選單]就可以下載了的,初次翻譯,如果有誤導的地方,歡迎路過的老師,多提意見和指正

最終程式碼實現效果圖所示:

手勢魅力-設定一個觸控選單

前戲

觸控和手勢驅動裝置的興​​起,極大地改變了我們思考互動的方式。手勢不僅僅是娛樂性的,它們非常有用,也很熟悉

移動觸控手勢已成為每個應用程式的重要組成部分,大多數使用者甚至沒有意識到的一部分。誰不喜歡(流暢)的互動應用程式?

然而至今,憎恨可能伴隨的混亂和數學是非常容易的。我知道,令人震驚的是,尤其是當你不是第一塊碼程式碼的人,或者你只是在那裡維護它的時候

有時候,這可能是一個吃力不討好的工作。那種讓你用一隻手盯著螢幕,另一隻手放在你的額頭上,另一隻手放在滑鼠上滾動的時間

有 - 我敢說呢? - 如絲般流暢的手勢觸控手勢和動畫可能是一個挑戰,並隨著時間的推移變得越來越突出。但這是另一天的戰鬥。或另一篇文章。或兩者

今天,我們要告訴你如何用手勢建立一個本地選單

手勢魅力-設定一個觸控選單
所以,在我轉向實際的程式碼之前,在那裡有一些我想要經歷的事情,所以請耐心等待

HTML結構

<!-- layout開始 -->
    <div class="layout">
        <!-- 頭部開始 -->
        <div class="header">
            <div class="header-top">
                <!-- 選單按鈕 -->
                <div data-link="" class="app-menu-burger OSFillParent" href="#" id="b2-Menu">
                    <!-- 三條橫崗線,這裡其實完全用一小圖片或者icon字型圖示代替 -->
                    <div data-container="" class="app-menu-line"></div>
                    <div data-container="" class="app-menu-line OSAutoMarginTop"></div>
                    <div data-container="" class="app-menu-line OSAutoMarginTop"></div>
                </div>
            </div>
        </div>
        <!-- 頭部結束 -->
        <!-- 中間部分開始 -->
        <div data-container="" class="center-content">
            <!-- 中間影象小圖片 -->
            <div class="center-content-header ph" id="b3-Top"></div>
            <div class="center-content-container ph" id="b3-Center">
                <!--中間圖片 -->
                <div data-container="">
                    <div data-container="" style="text-align: center;"><img data-image="" src="https://s11.postimg.org/409mhl043/icon.png">
                    </div>
                    <div data-container="" class="OSAutoMarginTop" style="text-align: center;">
                        <h1><!-- react-text: 75 -->Welcome to itclanCoder!<!-- /react-text --><br></h1>
                        <h3><!-- react-text: 78 -->手勢魅力:設定一個觸控側滑選單<!-- /react-text --><br><!-- react-text: 80 -->閱讀原文即可 <!-- /react-text --></h3>
                    </div>
                </div>
            </div>
            <div class="center-content-bottom ph" id="b3-Bottom"></div>
        </div>
        <!-- 中間部分結束 -->
        <!-- 左側帶單欄開始 -->
        <div class="menu">
            <!-- 黑色遮罩 -->
            <div class="menu-background" style="cursor: pointer;"></div>
            <div class="app-menu-container">
                <div class="app-menu">
                    <!-- 側欄頂部開始 -->
                    <div class="top">
                        <!-- 側欄頂部圖片 -->
                        <div class="top-overlay">
                            <img src="https://s11.postimg.org/409mhl043/icon.png" width=80>
                        </div>
                    </div>
                    <!-- 側欄頂部結束 -->
                    <!-- 側欄列表內容開始 -->
                    <div class="bottom">
                        <!-- 歡迎關注微信itclanCoder公眾號,個人微訊號:suibichuanji -->
                        <div class="list-item">Welcome itclanCoder</div>
                        <div class="list-item">Fancy Time</div>
                        <div class="list-item">Tea Time</div>
                        <div class="list-item">Adventure Time</div>
                        <div class="list-item">Puzzle Time</div>
                        <div class="list-item">Sports Time</div>
                        <div class="list-item">Star Wars Time</div>
                        <div class="list-item">Internet Time</div>
                        <div class="list-item">Sushi Time</div>
                        <div class="list-item">weChatPublicId:itclanCoder</div>
                        <div class="list-item">personId:suibichuanji</div>
                    </div>
                    <!-- 側欄列表內容 -->
                </div>
            </div>
        </div>
        <!-- 左側選單欄結束 -->
    </div>
    <!-- layout 結束 -->
複製程式碼

所有你需要了解的JavaScript觸控事件

我將使用JavaScript事件來檢測我的移動觸控手勢。在這種情況下在那裡是:

  • touchstart:當你觸控DOM元素時觸發。
  • touchmove:當你沿著DOM元素拖動手指時觸發
  • touchend:當你從DOM元素中移除手指時觸發

在這些事件中,我將使用觸控屬性(在那裡還有兩個屬性,但這是我現在關心的)。觸控屬性列出當前在螢幕上的所有手指:

  • PageX:返回手指放置在DOM中的x座標。從左邊開始計算,如果適用,則考慮水平滾動
  • PageY:返回手指放置在DOM中的y座標。它是從頂部邊緣測量的,並考慮垂直滾動(如果適用)

而你也需要知道關於requestAnimationFrame

requestAnimationFrame函式告訴瀏覽器你要執行一個動畫。它要求瀏覽器呼叫指定的函式,在下一次重繪之前更新動畫。這有什麼好處呢

  • 瀏覽器將嘗試匹配顯示重新整理,以允許流暢的動畫
  • 非活動選項卡中的動畫將停止(在CPU上花費的更少)
  • 它不會耗盡你的電池壽命

拖動,點選和滑動:額外的東西要考慮移​​動觸控手勢

這些事件需要能夠檢測和區分拖拽,點選和移動,並相應地做不同的事情。所以,當你玩手機觸控手勢,想想:

  • 限制:你想要什麼元素停止?您希望它在每次拖動時移動多遠?
  • 這個手勢的方向:你想只能水平移動,或者還是垂直移動?也許是兩個?
  • 拖動完成後你想要發生什麼?它會回到開始還是結束,取決於它在哪裡結束?它是否考慮到速度?
  • 詳情:我們是否正在用這個手勢記住速度?你想在選單後面加一個遮罩,當你開啟它時會變得越來越暗嗎?

在我的情況下,我只希望手勢的方向是水平的,因為我希望滾動功能正常。我有限制,並且我希望它回到開始或結束。這取決於使用者拖動了多少以及手指在螢幕上的速度

你不知道你想知道的關於 - 是超級重要的部分

我知道你想要了解移動觸控手勢的有趣部分,但是我必須先介紹這一點,因為它會影響到你的程式碼。是的,現在是討論變數的時候了。這好訊息是,我也要解釋為什麼要設定它們的價值。這些功能將使程式碼看起來更清潔

全域性變數和設定預設值

啊,是如此的好玩!看看所需要的變數數量;正是大多數人傾向於跳過的東西。(不要,你會後悔的)

    var trackableElement;
    var menu = document.querySelector(".menu");
    var appMenu = document.querySelector(".app-menu-container");
    var overlay = document.querySelector(".menu-background");
    var burger = document.querySelector(".app-menu-burger");
複製程式碼
    var touchingElement = false;
    var startTime;
    var startX = 0,
        startY = 0;
    var currentX = 0,
        currentY = 0;
    var isOpen = false;
    var isMoving = false;
    var menuWidth = 0;
    var lastX = 0;
    var lastY = 0;
    var moveX = 0; // where in the screen is the menu currently

    var dragDirection = "";
    var maxOpacity = 0.5; 
    // if you want to change this, don’t forget to change the opacity value 
    // of the ‘.menu--visible .menu-background’ CSS class

    var init = function(element, start, move, end) {
              trackableElement = element;

             startTime = new Date().getTime(); // start time of the touch

             addEventListeners();
   }

  var addEventListeners = function() {
      trackableElement.addEventListener("touchstart", onTouchStart, false);
      trackableElement.addEventListener("touchmove", onTouchMove, false);
      trackableElement.addEventListener("touchend", onTouchEnd, false);

      overlay.addEventListener("click", closeMenuOverlay, false); 
      // I want to be able to click the overlay and immediately close the menu
      // (in the space between the actual menu and the page behind it)
  }
複製程式碼

非常簡單,真的。按照這個順序,程式碼不那麼混亂,不那麼可怕,而且更容易消化

函式中的函式

這些函式被EventListener呼叫,即使它們不是做實際的動畫或者使選單工作所必需的計算

function onTouchStart(evt) {
  startTime = new Date().getTime();
  startX = evt.touches[0].pageX;
  startY = evt.touches[0].pageY;

  touchingElement = true;

  touchStart(startX, startY);
}

function onTouchMove(evt) {
  if (!touchingElement)
    return;

    currentX = evt.touches[0].pageX;
    currentY = evt.touches[0].pageY;
    const translateX = currentX - startX; // distance moved in the x axis
    const translateY = currentY - startY; // distance moved in the y axis

    touchMove(evt, currentX, currentY, translateX, translateY);
}

function onTouchEnd(evt) {

  if (!touchingElement)
    return;

    touchingElement = false;
    const translateX = currentX - startX; // distance moved in the x axis
    const translateY = currentY - startY; // distance moved in the y axis

    const timeTaken = (new Date().getTime() - startTime);
}

複製程式碼

所有這些變數都用於動畫所涉及的數學運算。為了可讀性,在函式中沒有太多的程式碼行,我把它們全部分成了小的一行

這個手機觸控手勢最後有趣的一部分

現在我對觸控事件,變數和函式的解釋已經不存在了,現在是我關注如何建立動畫的時候了。這正是選單移動以及所有數學和演算法背後的原因

動畫開始

function touchStart(startX, startY) {
     var menuOpen = document.querySelector(".menu.menu--visible");

     if (menuOpen !== null) {
        isOpen = true;
     } else {
        isOpen = false;
     }
    menu.classList.add("no-transition");
    appMenu.classList.add("no-transition");

    isMoving = true;
    menuWidth = document.querySelector(".app-menu").offsetWidth;
    lastX = startX;
    lastY = startY;

    if (isOpen) {
      moveX = 0;
    } else {
      moveX = -menuWidth;
    }
    dragDirection = "";
    menu.classList.add("menu--background-visible"); 
    // why is this being added? ‘.menu--background-visible .menu-background’ makes the overlay 
    // ‘active’, displaying it on the DOM for those sweet opacity changes.
}
複製程式碼

每次觸控螢幕時,這些程式碼都會執行。此功能將用作重置為預設值,具體取決於你上次提起手指後選單發生了什麼

動畫中間

function touchMove(evt, currentX, currentY, translateX, translateY) {
  if (!dragDirection) {
    if (Math.abs(translateX) >= Math.abs(translateY)) {
      dragDirection = "horizontal";
    } else {
      dragDirection = "vertical";
    }

    requestAnimationFrame(updateUi); 
    // this is what actually does the animation
  }
  // ...
}
複製程式碼

你想知道的第一件事是手勢的方向

在選單中,垂直滾動真的不是什麼可以關心的東西。意思是,在與手勢相關的程式碼方面,行為本身應該是預設滾動。因此,確定當什麼時候這是需要的

if (dragDirection === "vertical") {
    lastX = currentX;
    lastY = currentY;
} else {
    evt.preventDefault(); 
    // ...
}
複製程式碼

沒有preventDefault,這是會發生什麼事情:

這絕對不是你想要用你的手機觸控手勢發生的事情,所以考慮一下:當你開啟/關閉選單時,你是否有興趣閱讀滾動隱藏的內容?如果你的拖拽方向是水平的,你就不能滾動

我們需要一些邊界在這裡! (設定限制)

if (moveX + (currentX - lastX) < 0 && moveX + (currentX - lastX) > -menuWidth) {
  moveX = moveX + (currentX - lastX);
  // ...
}
複製程式碼

所以,記得我說我有限制嗎?在這個例子中,選單隱藏在螢幕的左邊。所以,如果選單是關閉的,變數moveX開始為-menuWidth- 我希望它被拖動到右邊,直到完全顯示

moveX + (currentX - lastX)
複製程式碼

你可以稱之為移動間隔。這就是告訴指令碼選單在視窗中的確切位置。我使用moveX是因為我做了實際的動畫。轉到updateUI函式 -`` requestAnimationFrame`呼叫的函式 - 這就是你所擁有的

function updateUi() {
  if (isMoving) {
    var element = document.querySelector(".app-menu-container");

    element.style.transform = "translateX(" + moveX + "px)";
    element.style.webkitTransform = "translateX(" + moveX + "px)";

    requestAnimationFrame(updateUi);
  }
}
複製程式碼

我希望動畫無縫平滑。為此,指令碼可以檢測到並用於translateX的時間間隔越小越好。目標不是看到使用translateX引起的跳轉

現在已經完成了,下一步就是計算疊加層的淡入效果

重疊計算

目標是:

  • 當moveX = -menuWidth時,不透明度= 0
  • 當movX = 0,不透明度= 0.5

然而,這些計算並不那麼線性。問題始終是打破這些情況下通常使用的三路規則的零

overlay.classList.add("no-transition");

var percentageBeforeDif = (Math.abs(moveX) * 100) / menuWidth;
var percentage = 100 - percentageBeforeDif;
複製程式碼

在這裡,我確保menuWidth對應於100%,當前位置(moveX)對應於百分比。在這個計算中我追求的百分比是

var newOpacity = (((maxOpacity) * percentage) / 100);
複製程式碼

這個計算是需要的,因為不透明度只有在0到0.5之間(如在變數中定義的)之後才有效。如果0.5不透明度與100%相關,則百分比將是期望的不透明度

動畫結束

function touchEnd(currentX, currentY, translateX, translateY, timeTaken) {
  isMoving = false;
  var velocity = 0.3;
  //... 
}
複製程式碼

首先要記住的是,有人可以簡單地點選,事件認為這是一個摸索和touchend。如果這是一個點選,選單上沒有任何事情發生

if (translateX === 0 && translateY === 0) {
  if (isOpen) {
    appMenu.classList.remove("no-transition");
    menu.classList.remove("no-transition");
  } else {
    menu.classList.remove("menu--background-visible");
    menu.classList.remove("no-transition");
  }
}
複製程式碼

拖動結束後會發生什麼?

  • 當選單開啟時,它可以關閉或保持開啟狀態 - 與動畫一起 - 返回之前的位置
  • 如果它關閉了,那麼它可以開啟或者保持關閉狀態,也可以在動畫返回之前
if ((translateX < (-menuWidth) / 2) || (Math.abs(translateX) / timeTaken > velocity)) { 
  // if menu is open, this represents the close condition
               

if (translateX > menuWidth / 2 || (Math.abs(translateX) / timeTaken > velocity)) { 
  // if menu is closed, this represents the open condition
複製程式碼

那麼什麼被認為足以開啟選單?五個畫素移動?那麼這個選單可以根據距離開啟或關閉。也就是說,如果將其拖過寬度的中間,並且拖動的速度大於定義的速度(也就是若手指拖動側邊欄超過該選單欄的一半位置的話,或者拖動的速度大於剛開始定義的速度,則該側邊欄就關閉或者開啟的,若不是,則恢復初始前一個位置的)

就這樣,你有一個工作的觸控式選單!

總結

對本文進行總結一下,首先這個效果在我們平日的手機應用裡,非常的常見,實現這一效果,主要利用的是移動端三大事件touchstart,touchmove,touchend,以及它們的觸控屬性,也就是手指在螢幕DOM中的實際位置,這時,需要考慮手指是水平滑動還是垂直,甚至有時候還得考慮手傾斜的滑動,還要區分是一根手指滑動,還是多根手指滑動,側邊選單欄動畫的實現,以及要注意阻止預設事件,重疊計算等等一些細節

看似簡單的效果,整個過程實現起來,還是不容易的,當然很多時候,在平時中,想當然的會用一些框架,移動端庫來代替原生當中一些繁瑣的寫法的,原生js固然耐人耗腦,但也是很有味道的,其實甭管咋實現,只要能實現就好,最後在重複一遍,若想獲得本篇Demo原始碼,itclanCoder後臺複製該標題回覆[手勢魅力-設定一個觸控選單] 就可以下載了

以下是本篇提點概要

  • HTML結構
  • 所有你需要了解的JavaScript觸控事件(touchstart,touchmove,touchend),以及兩個觸控屬性pageX,pageY
  • 需要知道關於requestAnimationFrame
  • 拖動,點選和滑動:額外的東西要考慮移​​動觸控手勢(手勢方向,水平,垂直,還有手指根數)
  • 你不知道你想知道的關於 - 是超級重要的部分
  • 全域性變數和設定預設值(一些初始化值變數的設定)
  • 函式中的函式(手指按下,移動,抬起功能函式的封裝呼叫)
  • 這個手機觸控手勢最後有趣的一部分(建立動畫)
  • 動畫中間(手指移動,拖動選單過程)
  • 我們需要一些邊界在這裡!(設定限制),也就是側邊欄選單滑動的位置
  • 重疊計算(透明度變化,也就是用小數來計算,百分比值)
  • 動畫結束(選單欄開啟和關閉狀態,選單欄的位置)

原文地止:手勢魅力-設定一個觸控選單點選該連結可讀英文原文

手勢魅力-設定一個觸控選單

相關文章