跳轉到內容

Canvas 2D Web 應用程式/覆蓋層

來自華夏公益教科書

本章擴充套件了關於 頁面 的章節,為這三個頁面添加了一個半透明的覆蓋層,並將按鈕從頁面移動到該覆蓋層。

cui2d 中的覆蓋層只是一個頁面(即一個 cuiPage),它用作另一個頁面的覆蓋層(即它的 processOverlay 方法在另一個頁面的處理函式中被呼叫)。除此之外,它們只是普通的頁面,具有自己的處理函式(由 processOverlay 呼叫);因此,覆蓋層頁面實際上可以像任何其他頁面一樣使用。

本章的示例(可在 線上 獲取;也可以 下載版本)為關於 頁面 的章節的示例添加了另一個(覆蓋層)頁面及其處理函式。然後從覆蓋層頁面的處理函式中呼叫兩個按鈕。本章重點介紹如何建立一個覆蓋層頁面以及其他頁面如何包含覆蓋層頁面。有關其他部分,請參閱關於 頁面響應式按鈕 的章節。

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
    <meta name="viewport"
      content="width=device-width, initial-scale=1.0, user-scalable=no">

    <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);
      }

      // overlay page
      var imageNormalButton = new Image();
      var imageFocusedButton = new Image();
      var imagePressedButton = new Image();
      var button0 = new cuiButton();
      var button1 = new cuiButton();

      var overlayPage = new cuiPage(600, 90, overlayPageProcess);
      overlayPage.isAdjustingHeight = false; // only adjust width
      overlayPage.verticalAlignment = -1; // top align
      overlayPage.interactionBits = (cuiConstants.isDraggableWithOneFinger | cuiConstants.isLimitedToVerticalDragging);

      function overlayPageProcess(event) {
        cuiContext.fillStyle = "#FFFFFF"; // draw in white

        if (button0.process(event, 20, 20, 120, 50, "previous",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button0.isClicked()) {
            if (cuiCurrentPage == secondPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
                  // ignore events for 50 milliseconds
              cuiCurrentPage = firstPage;          
            }
            else if (cuiCurrentPage == thirdPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
              cuiCurrentPage = secondPage;   
            }
            cuiRepaint(); 
          }
          return true;
        }
        if (button1.process(event, 150, 20, 80, 50, "next",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button1.isClicked()) {
            if (cuiCurrentPage == firstPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
                  // ignore events for 50 milliseconds
              cuiCurrentPage = secondPage;          
            }
            else if (cuiCurrentPage == secondPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
              cuiCurrentPage = thirdPage;   
            }
            cuiRepaint(); 
          }
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.globalAlpha = 0.3;
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;  // event has not been processed
      }

      // first page

      var firstPage = new cuiPage(400, 300, firstPageProcess);

      function firstPageProcess(event) {
        if (overlayPage.processOverlay(event)) {
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("First page using landscape format.", 200, 150);
          cuiContext.fillStyle = "#C0C0C0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;  // event has not been processed
      }

      // second page

      var secondPage = new cuiPage(400, 400, secondPageProcess);

      function secondPageProcess(event) {
        if (overlayPage.processOverlay(event)) {
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("Second page using square format.", 200, 200);
          cuiContext.fillStyle = "#DDD0C0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;
      }

      // third page

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

      function thirdPageProcess(event) {
        if (overlayPage.processOverlay(event)) {
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("Third page using portrait format.", 200, 266);
          cuiContext.fillStyle = "#DDC0D0";
          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>


建立覆蓋層頁面

[編輯 | 編輯原始碼]

應該用作覆蓋層頁面的頁面與其他頁面一樣建立。但是,覆蓋層通常不可變換。在本例中,覆蓋層只能用一根手指垂直拖動。如果頁面及其覆蓋層都不可變換,則它們應該具有相同的尺寸並使用相同的佈局。但是,如果頁面可變換但覆蓋層不可變換,事情就會變得更加複雜。通常,覆蓋層將調整為螢幕的寬度或高度,並將與相應的邊緣之一對齊。在本例中,寬度進行了調整,並且覆蓋層的頂部邊緣對齊

      ...
      var overlayPage = new cuiPage(600, 90, overlayPageProcess);
      overlayPage.isAdjustingHeight = false; // only adjust width
      overlayPage.verticalAlignment = -1; // top align
      overlayPage.interactionBits = (cuiConstants.isDraggableWithOneFinger | cuiConstants.isLimitedToVerticalDragging);
      ...

覆蓋層頁面的處理函式包含兩個按鈕(“上一個”和“下一個”)。由於相同的覆蓋層用於所有三個頁面,因此這些按鈕也用於所有三個頁面,因此必須檢查 cuiCurrentPage 的當前值才能將其設定為正確的新值

      ...
      function overlayPageProcess(event) {
        cuiContext.fillStyle = "#FFFFFF"; // draw in white

        if (button0.process(event, 20, 20, 120, 50, "previous",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button0.isClicked()) {
            if (cuiCurrentPage == secondPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
                  // ignore events for 50 milliseconds
              cuiCurrentPage = firstPage;          
            }
            else if (cuiCurrentPage == thirdPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
              cuiCurrentPage = secondPage;   
            }
            cuiRepaint(); 
          }
          return true;
        }
        if (button1.process(event, 150, 20, 80, 50, "next",
          imageNormalButton, imageFocusedButton, imagePressedButton)) {
          if (button1.isClicked()) {
            if (cuiCurrentPage == firstPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
                  // ignore events for 50 milliseconds
              cuiCurrentPage = secondPage;          
            }
            else if (cuiCurrentPage == secondPage) {
              cuiIgnoreEventsEnd = (new Date()).getTime() + 50; 
              cuiCurrentPage = thirdPage;   
            }
            cuiRepaint(); 
          }
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.globalAlpha = 0.3;
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;  // event has not been processed
      }
      ...


使用覆蓋層頁面

[編輯 | 編輯原始碼]

為了將頁面用作另一個頁面的覆蓋層,它的 processOverlay(event) 方法應該從該另一個頁面的處理函式中呼叫。在本例中

      ...
      // first page

      var firstPage = new cuiPage(400, 300, firstPageProcess);

      function firstPageProcess(event) {
        if (overlayPage.processOverlay(event)) {
          return true;
        }
        if (null == event) {
          // draw background
          cuiContext.fillText("First page using landscape format.", 200, 150);
          cuiContext.fillStyle = "#C0C0C0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }
        return false;  // event has not been processed
      }
      ...

正如您可能已經預料到的那樣,如果 processOverlay(event) 處理了事件,則它將返回 true,否則返回 false

另外兩個頁面以完全相同的方式工作。

覆蓋層的用途

[編輯 | 編輯原始碼]

覆蓋層的主要用途是始終停留在螢幕上相同位置的選單、圖示和按鈕。但是,它們也可以用於可拖動(或可變換)的調色盤和工具箱。此外,它們對對話方塊很有用。它們甚至可以用來實現一個簡單的視窗系統(但是沒有可滾動的內容,也沒有改變“視窗”縱橫比的可能性)。

由於它們用途廣泛,因此應該問一下什麼時候不使用它們?覆蓋層與可拖動或可變換元素的主要區別在於覆蓋層不受其頁面的變換的影響。一個具體的例子可能會有所幫助:想象一個應用程式,它呈現一張可縮放的地圖,使用者可以點選地圖上的某些點以獲取包含有關這些點的更多資訊的 infobox。這些 infobox 應該是覆蓋層還是可變換元素?如果它們是覆蓋層,使用者可能會很快用 infobox 塞滿整個螢幕。如果它們是可變換元素,那麼當用戶平移到地圖的其他部分時,開啟的 infobox 會移出視線。但是,如果使用者縮小,可變換元素的內容很快就會變得不可讀,而覆蓋層會保持其可讀的大小。另一方面,覆蓋層的位置並沒有告訴使用者點選點在地圖上的位置,而可變換元素會一直靠近點選點,除非使用者將其拖走。但是,如果使用者想比較兩個相距很遠的點的 infobox 內容呢?用覆蓋層更容易做到這一點。

因此,與可變換元素和可拖動元素相比,覆蓋層有多個優點和缺點,具體取決於具體的應用程式。因此,關於是否使用覆蓋層的決定必須針對每個特定案例做出。

覆蓋層的實現

[編輯 | 編輯原始碼]

processOverlay 的實現相對簡單。步驟如下

  • 變換事件座標,因為覆蓋層通常使用與頁面不同的變換(事件座標已對其進行變換)。
  • 設定 cuiContext 的幾何變換(同樣因為頁面通常使用不同的變換)。
  • 呼叫覆蓋層頁面的處理函式。
  • 處理覆蓋層頁面可變換 view 成員未處理的任何事件。

在程式碼中

/** 
 * Either process the event (if event != null) and return true if the event has been processed, 
 * or draw the page as an overlay (to another page) in the rectangle, which is specified in 
 * window coordinates (if event == null) and return false. This function is usually called
 * by {@link cuiPage.process} of another page.
 * @param {Object} event - An object describing a user event by its "type", coordinates in 
 * window coordinates ("clientX" and "clientY"), an "identifier" for touch events, and optionally
 * "buttons" to specify which mouse buttons are depressed. If null, the function should
 * redraw the overlay page. 
 * @returns {boolean} True if event != null and the event has been processed (implying that 
 * no other GUI elements should process it). False otherwise.
 */ 
cuiPage.prototype.processOverlay = function(event) {
  var orgEvent = event;

  var transform = {scale : 1.0, x : 0.0, y : 0.0};
  this.computeInitialTransformation(transform);

  if (null != orgEvent) {
    event = {clientX : orgEvent.clientX, clientY : orgEvent.clientY, 
      eventX : orgEvent.eventX, eventY : orgEvent.eventY,
      type : orgEvent.type, buttons : orgEvent.buttons, 
      deltaY : orgEvent.deltaY, wheelDelta : orgEvent.wheelDelta};
    this.computeEventCoordinates(event, transform); // set event coordinates for our transformation
  }
  if (null == orgEvent) {
    cuiContext.save();
    this.setPageTransformation(transform); // set our transformation in cuiContext
  }
  var flag = this.process(event); // call our process function
  
  if (!flag && null != event && (this.interactionBits != cuiConstants.none)) { 
    // event hasn't been processed and we have an event?
    event.eventX = event.clientX; // we don't need any transformation here because the initial ...
    event.eventY = event.clientY; // ... transformation is applied to the arguments of ... 
      // ... view.process() and the transformation in view is applied internally in view.process()
    
    var oldTranslationX = this.view.translationX;
    var oldTranslationY = this.view.translationY;
    var oldFlag = this.view.isProcessingOuterEvents;
    this.view.isProcessingOuterEvents = false; // don't let a page process outer events if it is an overlay

    if (this.view.process(event, transform.x, transform.y, this.width * transform.scale, 
      this.height * transform.scale,
      null, null, null, null, null, this.interactionBits)) {
      flag = true;
      // enforce interaction constraints with page
      if (cuiConstants.isLimitedToVerticalDragging & this.interactionBits) {
        this.view.translationX = oldTranslationX;
      }
      if (cuiConstants.isLimitedToHorizontalDragging & this.interactionBits) {
        this.view.translationY = oldTranslationY;
      }
    }
    this.view.isProcessingOuterEvents = oldFlag; // restore page's setting
  }

  if (null == orgEvent) { 
    cuiContext.restore();
  }
  return flag;
}


< Canvas 2D Web 應用程式

除非另有說明,否則本頁上的所有示例原始碼均授予公有領域。
華夏公益教科書