跳至內容

畫布 2D 網頁應用/過渡

來自華夏公益教科書,為開放世界提供開放書籍

本章擴充套件了有關 頁面 的章節,透過新增頁面之間動畫過渡效果(簡稱為“過渡”)來進行擴充套件。為此,它還依賴於在有關 動畫 的章節中介紹的動畫系統。

實現被封裝在一個具有多個引數的函式中。因此,一方面,只需更改引數而無需檢視函式的實現,就可以實現多種過渡;另一方面,可能難以理解所有引數的含義以及如何使用它們來實現某些效果。因此,下面列出了 24 種流行過渡的實現。此外,還介紹了一些關於如何在網頁應用中設計和選擇過渡的指南。

本章的示例(可在 線上 獲取;也可作為 可下載版本 獲取)為三個頁面之間的過渡添加了四個動畫過渡效果。以下部分將討論如何使用這些函式建立這些過渡以及這些函式是如何實現的。有關其他部分,請參閱有關 頁面動畫響應式按鈕 的章節。

<!DOCTYPE HTML>
<html>
  <head>
    <script src="cui2d.js"></script>

    <script>
      function init() {
        // get images
        imageNormalButton.src = "normal.png";
        imageNormalButton.onload = cuiRepaint;
        imageFocusedButton.src = "selected.png";
        imageFocusedButton.onload = cuiRepaint;
        imagePressedButton.src = "depressed.png";
        imagePressedButton.onload = cuiRepaint;

        // initialize and start cui2d
        cuiInit(firstPage);
      }

      // first page

      var firstPage = new cuiPage(400, 300, firstPageProcess);
      var button0 = new cuiButton();
      var imageNormalButton = new Image();
      var imageFocusedButton = new Image();
      var imagePressedButton = new Image();

      function firstPageProcess(event) {
        if (button0.process(event, 300, 50, 80, 50, "next",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button0.isClicked()) {
            cuiPlayTransition(this, secondPage, true, false, 0, 0, 0.33, // page turn
              -1.2, 0.1, 0.2, 1.1, 5, 1.0,
              0, 0, 1, 1, 0, 0.8);
           }
          return true; 
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("First page using landcape format.", 200, 150);
          cuiContext.fillStyle = "#E0E0E0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;  // event has not been processed
      }

      // second page

      var secondPage = new cuiPage(400, 400, secondPageProcess);
      var button1 = new cuiButton();
      var button2 = new cuiButton();

      function secondPageProcess(event) {
        if (button1.process(event, 20, 50, 120, 50, "previous",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button1.isClicked()) {
            cuiPlayTransition(this, firstPage, false, false, 0, 0, 0.33, // page turn
              0, 0, 1, 1, 0, 0.8,
              -1.2, 0.1, 0.2, 1.1, 5, 1.0);
          }
          return true;
        }
        if (button2.process(event, 300, 50, 80, 50, "next",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button2.isClicked()) {
            var startPoint = {x: 300 + 40, y: 50 + 25}; // start point for maximization
            this.transformPageToTransitionCoordinates(startPoint);
            cuiPlayTransition(this, thirdPage, false, false, 1, 0, 0.25, // maxmize
              0.0, 0.0, 1.0, 1.0, 0, 0.8,
              startPoint.x, startPoint.y, 0.1, 0.1, -5, 1.0);
          }
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("Second page using square format.", 200, 200);
          cuiContext.fillStyle = "#FFF0E0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;
      }

      // third page

      var thirdPage = new cuiPage(400, 533, thirdPageProcess);
      var button3 = new cuiButton();

      function thirdPageProcess(event) {
        if (button3.process(event, 20, 50, 120, 50, "previous",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button3.isClicked()) {
            var targetPoint = {x: 300 + 40, y: 50 + 25}; // target point for minimization
            secondPage.transformPageToTransitionCoordinates(targetPoint);
            cuiPlayTransition(this, secondPage, true, false, 0, 1, 0.3, // minimize
              targetPoint.x, targetPoint.y, 0.1, 0.1, 5, 1.0,
              0.0, 0.0, 1.0, 1.0, 0, 0.8);
         }
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("Third page using portrait format.", 200, 266);
          cuiContext.fillStyle = "#FFE0F0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }    
        return false;
      }
      
    </script>
  </head>
 
  <body bgcolor="#000000" onload="init()" 
    style="-webkit-user-drag:none; -webkit-user-select:none; ">
    <span style="color:white;">A canvas element cannot be displayed.</span>
  </body>
</html>

使用過渡

[編輯 | 編輯原始碼]

為了啟動兩個頁面之間的過渡,必須呼叫函式 cuiPlayTransition()。在示例中,第一個頁面和第二個頁面之間的動畫過渡以這種方式開始

        ...
        if (button0.process(event, 300, 50, 80, 50, "next",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button0.isClicked()) {
            cuiPlayTransition(this, secondPage, true, false, 0, 0, 0.33, // page turn
              -1.2, 0.1, 0.2, 1.1, 5, 1.0,
              0, 0, 1, 1, 0, 0.8);
           }
          return true; 
        }
        ...

過渡的外觀完全由 cuiPlayTransition() 的 19 個引數控制,這些引數將在下一節中討論。

配置過渡

[編輯 | 編輯原始碼]

函式 cuiPlayTransition(previousPage, nextPage, isPreviousOverNext, isFrontMaskAnimated, animationInitialSpeed, animationFinalSpeed, animationLength, previousFinalPositionX, previousFinalPositionY, previousFinalScaleX, previousFinalScaleY, previousFinalRotation, previousFinalOpacity, nextInitialPositionX, nextInitialPositionY, nextInitialScaleX, nextInitialScaleY, nextInitialRotation, nextInitialOpacity) 配置過渡的方方面面,包括過渡的時間和外觀。引數為

  • previousPage:前一個頁面的 cuiPage 物件;過渡是從前一個頁面到下一個頁面
  • nextPage:下一個頁面的 cuiPage 物件
  • isPreviousOverNextpreviousPage 是渲染在 nextPage 上方還是反之;必須為整個過渡選擇一種可能性
  • isFrontMaskAnimated:是否對前面頁面的不透明度蒙版進行動畫處理,而不是對頁面本身進行動畫處理(如果 isPreviousOverNexttrue,則 previousPage 是前面的頁面,否則是 nextPage);不透明度蒙版的動畫處理主要對擦除過渡很有用
  • animationInitialSpeed:過渡開始時動畫值的更改的初始速度;0.0 表示緩慢開始,1.0 表示線性插值,較大的值表示更快的開始;由於三次 Hermite 曲線用於插值,因此大於約 3 的值會導致過沖
  • animationFinalSpeed:過渡結束時動畫值的更改的最終速度(見 animationInitialSpeed
  • animationLength:過渡的持續時間(以秒為單位)(通常,這應該不超過 0.25;在某些情況下 0.33 可能沒問題;更長的持續時間可能會讓移動裝置上的專家使用者感到厭煩)
  • 指定過渡結束時 previousPage 最終外觀的引數(初始外觀始終相同)
    • previousFinalPositionXpreviousFinalPositionYpreviousPage 中心點的最終位置,其中螢幕的左/上邊緣由 -1 指定,右/下邊緣由 +1 指定,因此螢幕的中心位於 0。(初始位置始終為 (0,0);即 previousPage 最初居中。)
    • previousFinalScaleXpreviousFinalScaleYpreviousPage 的寬度和高度的最終縮放比例。(初始縮放比例始終為 1;即最初沒有縮放。)
    • previousFinalRotationpreviousPage 的最終順時針旋轉角度(以度為單位)。(初始旋轉角度始終為 0;即沒有旋轉。)
    • previousFinalOpacitypreviousPage 的最終不透明度。(初始不透明度始終為 1;即頁面完全不透明。)
  • 指定過渡開始時 nextPage 初始外觀的引數(最終外觀始終相同)
    • nextInitialPositionXnextInitialPositionYnextPage 中心點的初始位置,其中螢幕的左/上邊緣由 -1 指定,右/下邊緣由 +1 指定,因此螢幕的中心位於 0。(最終位置始終為 (0,0);即 nextPage 最終居中。)
    • nextInitialScaleXnextInitialScaleYnextPage 的寬度和高度的初始縮放比例。(最終縮放比例始終為 1;即最初沒有縮放。)
    • nextInitialRotationnextPage 的初始順時針旋轉角度(以度為單位)。(最終旋轉角度始終為 0;即沒有旋轉。)
    • nextInitialOpacitynextPage 的初始不透明度。(最終不透明度始終為 1;即頁面完全不透明。)

由於很難找到合適的數值,因此以下列表包含對 cuiPlayTransition() 的呼叫示例,用於實現流行的過渡效果。每個效果都有一個反向效果,應該用於以相反順序在相同頁面之間進行過渡

// push to left (reverse of push to right)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                -2.0, 0.0, 1.0, 1.0, 0, 1.0,
                +2.0, 0.0, 1.0, 1.0, 0, 1.0);

// push to right (reverse of push to left)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                +2.0, 0.0, 1.0, 1.0, 0, 1.0,
                -2.0, 0.0, 1.0, 1.0, 0, 1.0);

// push down (reverse of push to up)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                0.0, -2.0, 1.0, 1.0, 0, 1.0,
                0.0, +2.0, 1.0, 1.0, 0, 1.0);

// push up (reverse of push down)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                0.0, +2.0, 1.0, 1.0, 0, 1.0,
                0.0, -2.0, 1.0, 1.0, 0, 1.0);

// cover from top to bottom (reverse of uncover from bottom to top)
              cuiPlayTransition(firstPage, secondPage, false, false, 2, 0, 0.25,
                0.0, +0.0, 1.0, 1.0, 0, 1.0,
                0.0, -2.0, 1.0, 1.0, 0, 1.0);

// uncover from bottom to top (reverse of cover from top to bottom)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                 0.0, -2.0, 1.0, 1.0, 0, 1.0,
                 0.0, +0.0, 1.0, 1.0, 0, 1.0);

// cover from bottom to top (reverse of uncover from top to bottom)
              cuiPlayTransition(firstPage, secondPage, false, false, 2, 0, 0.25,
                0.0, -0.0, 1.0, 1.0, 0, 1.0,
                0.0, +2.0, 1.0, 1.0, 0, 1.0);

// uncover from top to bottom (reverse of cover from bottom to top)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                 0.0, +2.0, 1.0, 1.0, 0, 1.0,
                 0.0, -0.0, 1.0, 1.0, 0, 1.0);

// cover from left to right (reverse of uncover from right to left)
              cuiPlayTransition(firstPage, secondPage, false, false, 2, 0, 0.25,
                +0.0, 0.0, 1.0, 1.0, 0, 1.0,
                -2.0, 0.0, 1.0, 1.0, 0, 1.0);

// uncover from right to left (reverse of cover from left to right)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                -2.0, 0.0, 1.0, 1.0, 0, 1.0,
                 0.0, 0.0, 1.0, 1.0, 0, 1.0);

// cover from right to left (reverse of uncover from left to right)
              cuiPlayTransition(firstPage, secondPage, false, false, 2, 0, 0.25,
                +0.0, 0.0, 1.0, 1.0, 0, 1.0,
                +2.0, 0.0, 1.0, 1.0, 0, 1.0);

// uncover from left to right (reverse of cover from right to left)
              cuiPlayTransition(firstPage, secondPage, true, false, 2, 0, 0.25,
                +2.0, 0.0, 1.0, 1.0, 0, 1.0,
                 0.0, 0.0, 1.0, 1.0, 0, 1.0);

// page turn uncovering from right to left (reverse of page turn covering from left to right)
              cuiPlayTransition(firstPage, secondPage, true, false, 0, 0, 0.33,
                -1.2, 0.0, 0.2, 1.1, 5, 1.0,
                0, 0, 1, 1, 0, 0.8);

// page turn covering from left to right (reverse of page turn uncovering from left to right)
              cuiPlayTransition(firstPage, secondPage, false, false, 0, 0, 0.33,
                0, 0, 1, 1, 0, 0.8,
                -1.2, 0.0, 0.2, 1.1, -5, 1.0);

// maximize (reverse of minimize)
              var startPoint = {x: 300 + 40, y: 50 + 25}; // start point for maximization
              firstPage.transformPageToTransitionCoordinates(startPoint);
              cuiPlayTransition(firstPage, secondPage, false, false, 1, 0, 0.25,
                0.0, 0.0, 1.0, 1.0, 0, 0.8,
              startPoint.x, startPoint.y, 0.1, 0.1, -5, 1.0);

// minimize (reverse of maximize)
              var targetPoint = {x: 300 + 40, y: 50 + 25}; // target point for minimization
              secondPage.transformPageToTransitionCoordinates(targetPoint);
              cuiPlayTransition(firstPage, secondPage, true, false, 0, 1, 0.3, 
                targetPoint.x, targetPoint.y, 0.1, 0.1, 5, 1.0,
                0.0, 0.0, 1.0, 1.0, 0, 0.8);

// dissolve (reverse of itself)
              cuiPlayTransition(firstPage, secondPage, true, false, 0, 0, 0.33,
                0.0, 0.0, 1.0, 1.0, 0, 0.0,
                0.0, 0.0, 1.0, 1.0, 0, 1.0);

// fade through black (reverse of itself)
              cuiPlayTransition(firstPage, secondPage, true, false, 1, 1, 0.33,
                0.0, 0.0, 1.0, 1.0, 0, -1.0,
                0.0, 0.0, 1.0, 1.0, 0, -1.0);

// materialize from air (reverse of dissolve into air)
              cuiPlayTransition(firstPage, secondPage, true, false, 0, 0, 0.33,
                0.0, 0.0, 1.0, 1.0, 0, 0.0,
                0.0, 0.0, 2.0, 2.0, 0, 1.0);

// dissolve into air (reverse of materialize from air)
              cuiPlayTransition(firstPage, secondPage, true, false, 0, 0, 0.33,
                 0.0, 0.0, 2.0, 2.0, 0, 0.0,
                 0.0, 0.0, 1.0, 1.0, 0, 1.0);

// wipe from left to right (reverse of wipe from right to left)
              cuiPlayTransition(firstPage, secondPage, true, true, 1, 1, 0.25,
                2.0, 0.0, 1.0, 1.0, 0, 1.0,
                0.0, 0.0, 1.0, 1.0, 0, 1.0);

// wipe from right to left (reverse of wipe from left to right)
              cuiPlayTransition(firstPage, secondPage, true, true, 1, 1, 0.25,
                -2.0, 0.0, 1.0, 1.0, 0, 1.0,
                0.0, 0.0, 1.0, 1.0, 0, 1.0);

// wipe from top to bottom (reverse of wipe from bottom to top)
              cuiPlayTransition(firstPage, secondPage, true, true, 1, 1, 0.25,
                0.0, 2.0, 1.0, 1.0, 0, 1.0,
                0.0, 0.0, 1.0, 1.0, 0, 1.0);

// wipe from bottom to top (reverse of wipe from top to bottom)
              cuiPlayTransition(firstPage, secondPage, true, true, 1, 1, 0.25,
                0.0, -2.0, 1.0, 1.0, 0, 1.0,
                0.0, 0.0, 1.0, 1.0, 0, 1.0);

最大化和最小化過渡應該使用 cuiPagetransformPageToTransitionCoordinates() 方法,從用於定位按鈕等的(頁面)座標計算出 cuiPlayTransition() 的合適座標。

你可能對許多過渡及其名稱從幻燈片演示軟體中瞭解到。實際上,包含的過渡傾向於這種軟體提供的更微妙的過渡。另一方面,移動裝置上的大多數應用程式幾乎完全依賴於這種過渡。下一節關於選擇和設計過渡指南中將討論其中的一些原因。

選擇和設計過渡

[編輯 | 編輯原始碼]

透過阻止、使人迷失方向和/或分散使用者的注意力,很容易用過渡惹惱使用者。確保你不這樣做。至少,使用者應該更喜歡你的過渡,而不是從一個頁面立即切換到另一個頁面。(這並不像聽起來那麼容易。)以下是一個檢查列表,以避免令人討厭的過渡

  1. 使它們快速不要阻止使用者在下一個頁面上執行任何操作(如果過渡持續時間超過約 1/4 秒,就必須有一個充分的理由;如果過渡持續時間超過約 1/3 秒,就必須有一個非常充分的理由(例如,你確信使用者想要這樣做);確保在最小的適用顯示器上測試你的過渡,因為尺寸越小,物理速度就越低;因此,過渡看起來會更慢)
  2. 使它們一致不要使使用者迷失方向
    • 相同型別的過渡之間存在不一致(例如,列表中下一個(或上一個)頁面的過渡、層次結構中較低(或較高)級別的頁面的過渡、到(或從)對話方塊的過渡等)
    • 從頁面 A 到頁面 B 的過渡與從頁面 B 返回頁面 A 的過渡之間存在不一致(它們應該在視覺上反轉,除非過渡本身就是反轉過渡,例如溶解)
    • 過渡與啟用它的使用者操作的位置之間存在不一致(例如,如果螢幕右側的圖形元素上的點選或觸控激活了過渡,則過渡的主要方向應該是從右到左,無論使用哪種型別的過渡)
    • 過渡與啟用它的手勢之間存在不一致(例如,一個方向上的輕掃應該導致同一個方向上的過渡,沒有緩入,但有緩出)
  3. 使它們微妙不要分散使用者對頁面內容的注意力(頁面內容應該比任何過渡都更重要);請記住,使用者會一遍又一遍地看到相同的過渡(至少如果你一致地使用它們);因此,即使過渡在最初的 40 次觀看時很有趣,它們也可能很快變得過時。

這僅僅是為了避免阻止、使人迷失方向和/或分散使用者注意力的令人討厭的過渡。此外,良好的過渡應該是有意義的,並且可以傳達資訊

4. 使用過渡儘可能多地傳達資訊
  • 傳達頁面對之間的關係(例如,在適當的方向使用推入過渡,以傳達兩個頁面是列表中的相鄰頁面)
  • 傳達一個或兩個頁面的型別(例如,為通知或對話方塊保留從上到下的封面)
  • 傳達頁面組織的結構(例如,使用向上/向下推入在層次結構的同一級別上移動,使用向左/向右推入在層次結構中向上或向下移動)
  • 傳達頁面的功能(例如,使用電影中的過渡(特別是溶解和擦除)來講述故事;或使用最大化來提供有關特定位置的詳細資訊)
  • 傳達啟用過渡的使用者操作的型別和位置(例如,使用與啟用它的輕掃手勢類似的方向的推入過渡)

與往常一樣,對於設計指南:如果你有充分的理由,可以打破它們。

實現過渡

[編輯 | 編輯原始碼]

本節討論在 cui2d.js 中實現的兩個函式 cuiPlayTransition()cuiDrawTransition()cuiDrawTransition() 由全域性變數 cuiPageForTransitions 中特殊頁面的處理函式呼叫。cuiPlayTransition() 函式執行四個主要任務。

  1. 如有必要,它會為上一頁和下一頁建立畫布。
  2. 然後,它將上一頁的快照儲存在一個畫布中,將下一頁的快照儲存在另一個畫布中。
  3. 此外,它還會為動畫過渡設定全域性變數 cuiAnimationForTransitions 的屬性。
  4. 最後,它啟動動畫,指定在動畫結束之前忽略事件,並請求重新繪製畫布。

要理解程式碼,您應該知道 2 個關鍵幀中的 12 個動畫值是:previousPositionXpreviousPositionYpreviousScaleXpreviousScaleYpreviousRotationpreviousOpacitynextPositionXnextPositionYnextScaleXnextScaleYnextRotationnextOpacity。第一個關鍵幀中上一頁的值和第 0 個關鍵幀中下一頁的值由使用者指定,而其餘的值是預設值,它們指定上一頁在第 0 個關鍵幀中開始時沒有任何變化,下一頁在第 1 個關鍵幀中結束時沒有任何變化。程式碼如下:

/**
 * Play a transition between two pages.
 * @param {cuiPage} previousPage - The initial page for the transition.
 * @param {cuiPage} nextPage - The final page for the transition.
 * @param {boolean} isPreviousOverNext - Whether to draw previousPage over nextPage.
 * @param {boolean} isFrontMaskAnimated - Whether to animate only an opacity mask of the page in front.
 * @param {number} animationInitialSpeed - 0 for zero initial speed, 1 for linear interpolation, other values scale the speed.
 * @param {number} animationFinalSpeed - 0 for zero final speed, 1 for linear interpolation, other values scale the speed.
 * @param {number} animationLength - Length of the transition in seconds. 
 * @param {number} previousFinalPositionX - Final x position of the previous page (-1/+1: centered on left/right edge). 
 * @param {number} previousFinalPositionY - Final y position of the previous page (-1/+1: centered on top/bottom edge). 
 * @param {number} previousFinalScaleX - Final x scale of the previous page (1: no scaling). 
 * @param {number} previousFinalScaleY - Final y scale of the previous page (1: no scaling). 
 * @param {number} previousFinalRotation - Final rotation in degrees of the previous page (0: no rotation). 
 * @param {number} previousFinalOpacity - Final opacity of the previous page (0: transparent, 1: opaque). 
 * @param {number} nextInitialPositionX - Initial x position of the next page (-1/+1: centered on left/right edge). 
 * @param {number} nextInitialPositionY - Initial y position of the next page (-1/+1: centered on top/bottom edge). 
 * @param {number} nextInitialScaleX - Initial x scale of the next page (1: no scaling). 
 * @param {number} nextInitialScaleY - Initial y scale of the next page (1: no scaling). 
 * @param {number} nextInitialRotation - Initial rotation in degrees of the next page (0: no rotation). 
 * @param {number} nextInitialOpacity - Initial opacity of the next page (0: transparent, 1: opaque). 
 */
function cuiPlayTransition(
  previousPage, nextPage, isPreviousOverNext, isFrontMaskAnimated,
  animationInitialSpeed, animationFinalSpeed, animationLength,
  previousFinalPositionX, previousFinalPositionY,
  previousFinalScaleX, previousFinalScaleY,
  previousFinalRotation, previousFinalOpacity,
  nextInitialPositionX, nextInitialPositionY,
  nextInitialScaleX, nextInitialScaleY,
  nextInitialRotation, nextInitialOpacity)
{ 
  // if necessary, create previousCanvas and nextCanvas
  if (null == cuiAnimationForTransitions.previousCanvas) {
    cuiAnimationForTransitions.previousCanvas = document.createElement("canvas");
  }
  if (null == cuiAnimationForTransitions.nextCanvas) {
    cuiAnimationForTransitions.nextCanvas = document.createElement("canvas");
  }

  // draw previousCanvas and nextCanvas

  // save current animations state and make sure the render loop doesn't render now
  var tempCanvas = cuiCanvas;
  var tempAnimationsArePlaying = cuiAnimationsArePlaying;
  var tempAnimationsEnd = cuiAnimationsEnd;
  cuiAnimationsArePlaying = false;
  cuiAnimationsEnd = 0; 

  // draw previous page into previousCanvas
  var previousCanvas = cuiAnimationForTransitions.previousCanvas;
  cuiCurrentPage = previousPage;
  cuiCanvas = previousCanvas;
  cuiContext = previousCanvas.getContext("2d");
  cuiProcess(null);

  // draw next page into nextCanvas
  var nextCanvas = cuiAnimationForTransitions.nextCanvas;
  cuiCurrentPage = nextPage;
  cuiCanvas = nextCanvas;
  cuiContext = nextCanvas.getContext("2d");
  cuiProcess(null);

  // restore cui state
  cuiCanvas = tempCanvas;
  cuiContext = cuiCanvas.getContext("2d");
  cuiCurrentPage = cuiPageForTransitions;
  cuiAnimationsArePlaying = tempAnimationsArePlaying; // restore animations state
  cuiAnimationsEnd = tempAnimationsEnd; // restore animations state

  // set cuiAnimationForTransitions
  var transitionKeyframes = [
    {time : 0.00, out : -animationInitialSpeed,
     values : [
      0.0, 0.0, 1.0, 1.0, 0.0, 1.0, // previous page initial values
      nextInitialPositionX, nextInitialPositionY,
      nextInitialScaleX, nextInitialScaleY,
      nextInitialRotation, nextInitialOpacity
    ]}, 
    {time : 1.00, in : -animationFinalSpeed,
     values : [
      previousFinalPositionX, previousFinalPositionY,
      previousFinalScaleX, previousFinalScaleY,
      previousFinalRotation, previousFinalOpacity,
      0.0, 0.0, 1.0, 1.0, 0.0, 1.0 // next page final values
    ]}
  ];
  cuiAnimationForTransitions.nextPage = nextPage;
  cuiAnimationForTransitions.isPreviousOverNext = isPreviousOverNext;
  cuiAnimationForTransitions.isFrontMaskAnimated = isFrontMaskAnimated;
  cuiAnimationForTransitions.keyframes = transitionKeyframes;
  cuiAnimationForTransitions.stretch = animationLength;
  cuiAnimationForTransitions.play();
  cuiIgnoringEventsEnd = cuiAnimationForTransitions.end;
  cuiRepaint();
}

動畫的實際渲染由 cuiDrawTransition 執行。首先,它使用 animateValues 計算動畫值並提取 12 個動畫引數。根據 isPreviousOverNext,它要麼將上一頁的畫布渲染到下一頁的畫布上,要麼反之亦然。(因為我們使用複合操作 "destination-over",所以前頁必須先渲染。)如果 isFrontMaskAnimated 為假,則直接渲染前頁(使用 drawImage),否則渲染一個動畫遮罩(使用 fillRect),並且僅在遮罩區域內渲染靜態前頁,方法是使用複合操作 "source-in"。(動畫遮罩對於擦除過渡很有用。)畫布和遮罩的動畫主要透過不透明度的動畫值和使用動畫值作為引數的幾何變換來實現。最後,cuiDrawTransition 檢查過渡是否完成,如果是,則將 cuiCurrentPage 設定為 nextPage 並請求重新繪製。

/** Draw a frame of the current transition; called by cuiProcess(). */
function cuiDrawTransition() {
  var previousCanvas = cuiAnimationForTransitions.previousCanvas;
  var nextCanvas = cuiAnimationForTransitions.nextCanvas;
  var width = cuiCanvas.width;
  var height = cuiCanvas.height;
  var values = cuiAnimationForTransitions.animateValues();
  var previousPositionX = values[0];
  var previousPositionY = values[1];
  var previousScaleX = values[2];
  var previousScaleY = values[3];
  var previousRotation = values[4];
  var previousOpacity = values[5];
  var nextPositionX = values[6];
  var nextPositionY = values[7];
  var nextScaleX = values[8];
  var nextScaleY = values[9];
  var nextRotation = values[10];
  var nextOpacity = values[11];
  
  if (cuiAnimationForTransitions.isPreviousOverNext) {  
    // first draw previous page then next page
    if (!cuiAnimationForTransitions.isFrontMaskAnimated) { 
      // draw without mask
      cuiContext.globalCompositeOperation = "destination-over";
      cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, previousOpacity));
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.translate((0.5 * previousPositionX + 0.5) * width, 
        (0.5 * previousPositionY + 0.5) * height);
        // translate center as specified
      cuiContext.rotate(previousRotation * Math.PI / 180.0); 
        // rotate around center
      cuiContext.scale(previousScaleX, previousScaleY); 
        // scale image as specified
      cuiContext.translate(-0.5 * width, -0.5 * height); 
        // translate center to origin
      cuiContext.drawImage(previousCanvas, 0, 0, width, height); 
        // draw full size
    } else { // draw with transform mask for wipe transitions
      cuiContext.globalCompositeOperation = "copy";
      cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, previousOpacity));
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.translate((0.5 * previousPositionX + 0.5) * width, 
        (0.5 * previousPositionY + 0.5) * height);
        // translate center as specified
      cuiContext.rotate(previousRotation * Math.PI / 180.0); 
        // rotate around center
      cuiContext.scale(previousScaleX, previousScaleY); 
        // scale image as specified
      cuiContext.translate(-0.5 * width, -0.5 * height); 
        // translate center to origin
      cuiContext.fillStyle = "#000000";
      cuiContext.fillRect(0, 0, width, height); 
        // draw black full-size mask with specified opacity
      cuiContext.globalCompositeOperation = "source-in";
      cuiContext.globalAlpha = 1.0;
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.drawImage(previousCanvas, 0, 0, width, height); 
        // draw canvas without trafo
    }
    // now draw next page under previous page            
    cuiContext.globalCompositeOperation = "destination-over";
      cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, nextOpacity));
    cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
    cuiContext.translate((0.5 * nextPositionX + 0.5) * width, 
      (0.5 * nextPositionY + 0.5) * height);
      // translate center as specified
    cuiContext.rotate(nextRotation * Math.PI / 180.0); 
      // rotate around center
    cuiContext.scale(nextScaleX, nextScaleY); // scale image as specified
    cuiContext.translate(-0.5 * width, -0.5 * height); 
      // translate center to origin
    cuiContext.drawImage(nextCanvas, 0, 0, width, height); 
      // draw full size
  } else { 
    // first draw next page then previous page
    if (!cuiAnimationForTransitions.isFrontMaskAnimated) { 
      // draw without mask
      cuiContext.globalCompositeOperation = "destination-over";
      cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, nextOpacity));
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.translate((0.5 * nextPositionX + 0.5) * width, 
        (0.5 * nextPositionY + 0.5) * height);
        // translate center as specified
      cuiContext.rotate(nextRotation * Math.PI / 180.0); 
        // rotate around center
      cuiContext.scale(nextScaleX, nextScaleY); 
        // scale image as specified
      cuiContext.translate(-0.5 * width, -0.5 * height); 
        // translate center to origin
      cuiContext.drawImage(nextCanvas, 0, 0, width, height); 
        // draw full size
    } else { 
      // draw with transform mask for wipe transitions
      cuiContext.globalCompositeOperation = "copy";
      cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, nextOpacity));
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.translate((0.5 * nextPositionX + 0.5) * width, 
        (0.5 * nextPositionY + 0.5) * height);
        // translate center as specified
      cuiContext.rotate(nextRotation * Math.PI / 180.0); 
        // rotate around center
      cuiContext.scale(nextScaleX, nextScaleY); 
        // scale image as specified
      cuiContext.translate(-0.5 * width, -0.5 * height); 
        // translate center to origin
      cuiContext.fillStyle = "#000000";
      cuiContext.fillRect(0, 0, width, height); 
        // draw black full-size mask with specified opacity
      cuiContext.globalCompositeOperation = "source-in";
      cuiContext.globalAlpha = 1.0;
      cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
      cuiContext.drawImage(nextCanvas, 0, 0, width, height); 
        // draw canvas without trafo
    }
    // now draw previous page under next page            
    cuiContext.globalCompositeOperation = "destination-over";
    cuiContext.globalAlpha = Math.max(0.0, Math.min(1.0, previousOpacity));
    cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
    cuiContext.translate((0.5 * previousPositionX + 0.5) * width, 
      (0.5 * previousPositionY + 0.5) * height);
      // translate center as specified
    cuiContext.rotate(previousRotation * Math.PI / 180.0); 
      // rotate around center
    cuiContext.scale(previousScaleX, previousScaleY); 
      // scale image as specified
    cuiContext.translate(-0.5 * width, -0.5 * height); 
      // translate center to origin
    cuiContext.drawImage(previousCanvas, 0, 0, width, height); 
      // draw full size
  }       
  // draw opaque background to avoid any semitransparent colors in the canvas
  cuiContext.globalCompositeOperation = "destination-over";
  cuiContext.globalAlpha = 1.0;
  cuiContext.setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
  cuiContext.fillStyle = "#000000";
  cuiContext.fillRect(0, 0, width, height);

  if (!cuiAnimationForTransitions.isPlaying()) { 
    // transition has finished
    cuiCurrentPage = cuiAnimationForTransitions.nextPage;
    cuiRepaint();
  }
}

如果您檢視程式碼,您會注意到程式碼中有許多重複,事實上,可以顯著縮短程式碼;但是,為了便於閱讀,程式碼以這種長形式保留下來。


< Canvas 2D Web Apps

除非另有說明,本頁面上的所有示例原始碼均歸屬公共領域。
華夏公益教科書