跳轉至內容

畫布 2D 網頁應用/可變換物件

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

本章介紹“可變換物件”。這裡,“可變換物件”表示可以透過兩指手勢移動(“平移”)、旋轉和縮放(在兩個方向上均勻)的影像。可變換物件的使用方式與 可拖動物件 非常相似;但是,它們的處理函式需要另一幅影像來處理兩指觸碰的情況,並且需要另一個布林值來確定物件是否可以用一根手指或滑鼠(而不是僅用兩根手指)拖動。

與可拖動物件一樣,可以同時變換任意數量的物件(只要觸控裝置支援所需的觸控事件數量)。此外,應用程式程式設計師不必擔心處理多個同時發生的觸控事件:process 函式只接收具有單個座標對的事件,並一次只處理一個物件。

本章的示例(可 線上獲取;也可 下載版本)展示了兩個物件:這兩個物件都可以用一根手指(或滑鼠)拖動,並用兩根手指變換,但其中一個不能縮放,另一個不能旋轉。以下部分將討論如何使用可變換物件以及如何使用它們。有關其他部分,請參見 可拖動物件 一章。

<!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
        imageNormalAlien.src = "alien_sleepy.png";
        imageNormalAlien.onload = cuiRepaint;
        imageFocusedAlien.src = "alien_wow.png";
        imageFocusedAlien.onload = cuiRepaint;
        imageGrabbedOnceAlien.src = "alien_lipbite.png";
        imageGrabbedOnceAlien.onload = cuiRepaint;
        imageGrabbedTwiceAlien.src = "alien_smiley.png";
        imageGrabbedTwiceAlien.onload = cuiRepaint;

        // set defaults for all pages
        cuiBackgroundFillStyle = "#00A000";

        // initialize cui2d and start with myPage
        cuiInit(myPage);
      }

      // create images for the smiley
      var imageNormalAlien = new Image();
      var imageFocusedAlien = new Image();
      var imageGrabbedOnceAlien = new Image();
      var imageGrabbedTwiceAlien = new Image();

      // create draggable objects
      var transformable0 = new cuiTransformable();
      var transformable1 = new cuiTransformable();

      // create a page
      var myPage = new cuiPage(400, 300, myPageProcess);

      // a function to repaint the canvas and return false (if null == event)
      // or to process user events (if null != event) and return true
      // if the event has been processed
      function myPageProcess(event) {

        // draw and react to transformables
        if (transformable0.process(event, 50, 100, 200, 200, null,
          imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
          cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
          transformable0.scale = 1.0; // always reset scale (i.e. don't scale)
          return true;
        }

        if (transformable1.process(event, 250, 100, 200, 200, null,
          imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien,
          cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
          transformable1.rotation = 0.0; // always reset rotation (i.e. dont' rotate)
          return true;
        }

       // repaint this page?
        if (null == event) {
          // background
          cuiContext.fillStyle = "#F0F0F0";
          cuiContext.fillRect(0, 0, this.width, this.height);
        }

        return false; // event has not been processed
      }
    </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>

使用可變換物件

[編輯 | 編輯原始碼]

與可拖動物件類似,應為每個可變換物件定義一個全域性變數。在示例中,看起來像這樣

      // create draggable objects
      var transformable0 = new cuiTransformable();
      var transformable1 = new cuiTransformable();

然後在頁面的處理函式中處理可變換物件

        // draw and react to transformables
        if (transformable0.process(event, 50, 100, 200, 200, null, 
          imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien, 
          cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
          transformable0.scale = 1.0; // always reset scale (i.e. don't scale)
          return true;
        }
 
        if (transformable1.process(event, 250, 100, 200, 200, null, 
          imageNormalAlien, imageFocusedAlien, imageGrabbedOnceAlien, imageGrabbedTwiceAlien, 
          cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)) {
          transformable1.rotation = 0.0; // always reset rotation (i.e. dont' rotate)
          return true;
        }

處理函式的最後一個引數(這裡為 cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers)指定了可用的互動形式。要支援多種互動形式,需要按示例中所示使用按位或運算將常量組合起來。其他有用的常量是 cuiConstants.isDraggableWithTwoFingerscuiConstants.isRotatableWithTwoFingerscuiConstants.isUniformlyScalableWithTwoFingers。使用這些標誌停用旋轉和縮放不如示例中的方法靈活;但是,它可以避免一些與旋轉和縮放手勢相關的平移運動。

process 函式在物件處理事件時返回 true。在這種情況下,應採取其他操作。這裡,透過始終將其設定為 1.0 來停用第一個物件的縮放。類似地,透過始終將旋轉角度設定為 0 度來停用第二個物件的旋轉。

當然,對物件的變換有許多其他的反應方式;例如,限制 translationXtranslationY

可變換物件的實現

[編輯 | 編輯原始碼]

由於不同的變換和手勢使用兩個觸點,因此可變換物件比可拖動物件需要更多的屬性

/**
 * @class cuiTransformable
 * @classdesc Transformable objects can be translated, rotated, and scaled with one- and 
 * two-finger gestures.
 *
 * @desc Create a new cuiTransformable.
 */
function cuiTransformable() {
  /** 
   * Clockwise rotation angle in degrees by which the object has been rotated. 
   * @member {number} cuiTransformable.rotation 
   */
  this.rotation = 0; 
  /** 
   * Scaling factor by which the object has been magnified. 
   * @member {number} cuiTransformable.scale 
   */
  this.scale = 1; 
  /** 
   * Difference in x coordinate by which the centre of the transformable has been moved relative to its
   * initial position (specified by x + 0.5 * width with the arguments of {@link cuiTransformable#process}). 
   * @member {number} cuiTransformable.translationX 
   */
  this.translationX = 0;
  /** 
   * Difference in y coordinate by which the centre of the transformable has been moved relative to its
   * initial position (specified by y + 0.5 * height with the arguments of {@link cuiTransformable#process}). 
   * @member {number} cuiTransformable.translationY 
   */
  this.translationY = 0;
  /** 
   * Flag specifying whether a mouse button or first finger is inside the object's rectangle.
   * @member {boolean} cuiTransformable.isPointerInside0
   */
  this.isPointerInside0 = false;
  /** 
   * Flag specifying whether a mouse button or first finger is pushing the object or has been
   * pushing the object and is still held down (but may have moved outside the object's    
   * rectangle). 
   * @member {boolean} cuiTransformable.isPointerDown0 
   */
  this.isPointerDown0 = false; 
  /** 
   * Flag specifying whether a second finger is pushing the object or has been
   * pushing the object and is still held down (but may have moved outside the object's    
   * rectangle). 
   * @member {boolean} cuiTransformable.isPointerDown1 
   */
  this.isPointerDown1 = false; 
  this.hasTriggeredClick = false; // click event has been triggered?
  this.hasTriggeredDoubleClick = false; // double click event has been triggered?
  this.hasTriggeredHold0 = false; // hold event has been triggered for first pointer?
  this.hasTriggeredHold1 = false; // hold event has been triggered for second pointer?
  /** 
   * Flag to specify whether to process events even if they are outside of the rectangle.
   * If true, it will consume many more events and therefore should only be used for background objects.
   * @member {boolean} cuiTransformable.isProcessingOuterEvents
   */
  this.isProcessingOuterEvents = false;
  
  this.timeDown0 = 0; // time in milliseconds after January 1, 1970 when the first pointer went down
  this.timeDown1 = 0; // time in milliseconds after January 1, 1970 when the second pointer went down
  this.identifier0 = -1; // identifier of the first touch point (-1 for mouse)
  this.identifier1 = -1; // identifier of the second touch point (-1 for mouse)
  this.translationXDown = 0; // value of translationX when the pointer went down
  this.translationYDown = 0; // value of translationX when the pointer went down
  this.rotationDown = 0; // value of rotation when the pointer went down
  this.scaleDown = 0; // value of scale when the pointer went down
  this.eventXDown0 = 0; // x coordinate of the event when the first pointer went down 
  this.eventYDown0 = 0; // y coordinate of the event when the first pointer went down 
  this.eventXDown1 = 0; // x coordinate of the event when the second pointer went down 
  this.eventYDown1 = 0; // y coordinate of the event when the second pointer went down 
  this.eventX0 = 0; // current x coordinate of the first pointer 
  this.eventY0 = 0; // current Y coordinate of the first pointer 
  this.eventX1 = 0; // current x coordinate of the second pointer 
  this.eventY1 = 0; // current y coordinate of the second pointer 
};

/**
 * Returns whether the transformable has just been clicked. 
 * @returns {boolean} True if the draggable has been clicked, false otherwise.
 */
cuiTransformable.prototype.isClicked = function() {
  return this.hasTriggeredClick;
}

/** 
 * Determine whether the button has just been double clicked. 
 * @returns {boolean} True if the button has been double clicked, false otherwise.
 */
cuiTransformable.prototype.isDoubleClicked = function() {
  return this.hasTriggeredDoubleClick;
}

/** 
 * Determine whether first pointer has just been held down longer than {@link cuiTimeUntilHold}. 
 * @returns {boolean} True if the first pointer has just been held down long enough, false otherwise.
 */
cuiTransformable.prototype.isHeldDown0 = function() {
  return this.hasTriggeredHold0;
}

/** 
 * Determine whether second pointer has just been held down longer than {@link cuiTimeUntilHold}. 
 * @returns {boolean} True if the second pointer has just been held down long enough, false otherwise.
 */
cuiTransformable.prototype.isHeldDown1 = function() {
  return this.hasTriggeredHold1;
}

/** 
 * Either process the event (if event != null) and return true if the event has been processed, 
 * or draw the appropriate image for the object state in the rectangle 
 * with a text string on top of it (if event == null) and return false.
 * This function is usually called by {@link cuiPage.process} of a {@link cuiPage}.
 * @param {Object} event - An object describing a user event by its "type", coordinates in 
 * page coordinates ("eventX" and "eventY"), an "identifier" for touch events, and optionally
 * "buttons" to specify which mouse buttons are depressed. If null, the function should
 * redraw the object.
 * @param {number} x - The x coordinate of the top, left corner of the object's rectangle.
 * @param {number} y - The y coordinate of the top, left corner of the object's rectangle.
 * @param {number} width - The width of the object's rectangle.
 * @param {number} height - The height of the object's rectangle.
 * @param {string} text - A text that is written at the center of the rectangle. (May be null).
 * @param {Object} imageNormal - An image to be drawn inside the object's rectangle if there
 * are no user interactions. (May be null.)
 * @param {Object} imageFocused - An image to be drawn inside the object's rectangle if the
 * mouse hovers over the object's rectangle or a touch point moves into it. (May be null.)
 * @param {Object} imagePressed0 - An image to be drawn inside the object's rectangle if a
 * mouse button is pushed or the object is touched once. (May be null.)
 * @param {Object} imagePressed1 - An image to be drawn inside the object's rectangle if a
 * mouse button is pushed or the object is touched twice. (May be null.)
 * @param {number} interactionBits - The forms of interaction, either {@link cuiConstants.none} 
 * or a bitwise-or of other constants in {@link cuiConstants}, e.g. 
 * cuiConstants.isDraggableWithOneFinger | cuiConstants.isTransformableWithTwoFingers.
 * @returns {boolean} True if event != null and the event has been processed (implying that 
 * no other GUI elements should process it). False otherwise.
 */ 
cuiTransformable.prototype.process = function (event, x, y, width, height, 
  text, imageNormal, imageFocused, imagePressed0, imagePressed1, interactionBits) {
 
  if (null == event) {
    // choose appropriate image
    var image = imageNormal;
    if (this.isPointerDown1) {
      image = imagePressed1;
    } 
    else if (this.isPointerDown0) {
      image = imagePressed0;
    }
    else if (this.isPointerInside0) {
      image = imageFocused;
    }

    // transform and draw object
    cuiContext.save();
    cuiContext.translate(this.translationX, this.translationY);
    cuiContext.translate(x + 0.5 * width, y + 0.5 * height);
    cuiContext.rotate(this.rotation * Math.PI / 180.0);
    cuiContext.scale(this.scale, this.scale);
    cuiContext.translate(-x - 0.5 * width, -y - 0.5 * height);
 
    if (null != text) {
      cuiContext.fillText(text, x, y, width, height);
    }
    if (null != image) {
      cuiContext.drawImage(image, x, y, width, height);
    } 
    cuiContext.restore();   

    return false;       
  }
 
  // check point of event
  var isIn = false;       
  var mappedX = event.eventX - this.translationX;
  var mappedY = event.eventY - this.translationY;
  mappedX = mappedX - x - 0.5 * width;
  mappedY = mappedY - y - 0.5 * height;
  var angle = -this.rotation * Math.PI / 180.0;
  var tempX = Math.cos(angle) * mappedX - Math.sin(angle) * mappedY;
  mappedY = Math.sin(angle) * mappedX  + Math.cos(angle) * mappedY;
  mappedX = tempX / this.scale;
  mappedY = mappedY / this.scale;
  mappedX = mappedX + x + 0.5 * width;
  mappedY = mappedY + y + 0.5 * height;
  if ((x <= mappedX && mappedX < x + width && y <= mappedY && mappedY < y + height) || 
    this.isProcessingOuterEvents) {
    isIn = true;
  }
 
  // clear event notifications (they are only set once and need to be cleared afterwards)
  this.hasTriggeredClick = false;
  this.hasTriggeredDoubleClick = false;
  this.hasTriggeredHold0 = false;
  this.hasTriggeredHold1 = false;
 
  // process double click events
  if ("dblclick" == event.type) {
    this.hasTriggeredDoubleClick = isIn;
    return isIn;
  }
  
  // process our hold events
  if ("mousehold" == event.type) {
    if (event.timeDown == this.timeDown0 && event.identifier == this.identifier0 && 
      this.isPointerDown0) {
      this.hasTriggeredHold0 = true;
      return true;
    }
    return false;
  }
  if ("mousehold" == event.type) {
    if (event.timeDown == this.timeDown1 && event.identifier == this.identifier1 && 
      this.isPointerDown1) {
      this.hasTriggeredHold1 = true;
      return true;
    }
    return false;
  }
  
  // process wheel events
  if ("wheel" == event.type || "mousewheel" == event.type) {
    if (!(cuiConstants.isUniformlyScalableWithTwoFingers & interactionBits)) {
      return false;
    }
    if (isIn) {
      // compute new x and y based on the motion of the point under the mouse
      
      // first compute the point that is mapped to the point under the mouse
      var fixpointX = event.eventX;
      var fixpointY = event.eventY;
      var mappedX = fixpointX - this.translationX - x - 0.5 * width;
      var mappedY = fixpointY - this.translationY - y - 0.5 * height;
      var angle = -this.rotation * Math.PI / 180.0;
      fixpointX = (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY) / this.scale
        + x + 0.5 * width;
      fixpointY = (Math.sin(angle) * mappedX  + Math.cos(angle) * mappedY) / this.scale
        + y + 0.5 * height;
       
      // change scale 
      var delta;
      if ("mousewheel" == event.type) {
        delta = -event.wheelDelta / 30.0;
      }
      else {
        delta = event.deltaY;
      }
      this.scale = this.scale * Math.pow(2.0, -0.025 * delta); 

      // now see where this fixpoint is mapped to with the current transformation
      mappedX = fixpointX - x - 0.5 * width;
      mappedY = fixpointY - y - 0.5 * height;
      angle = this.rotation * Math.PI / 180.0;
      var tempX = this.scale * (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY);
      mappedY = this.scale * (Math.sin(angle) * mappedX  + Math.cos(angle) * mappedY); 
      mappedX = tempX + x + 0.5 * width + this.translationX;
      mappedY = mappedY + y + 0.5 * height + this.translationY;
      
      // (x,y) should be at the position of the mouse; 
      // we change the transformation such that it ends up there
      
      this.translationX = this.translationX + event.eventX - mappedX;
      this.translationY = this.translationY + event.eventY - mappedY;
      
      cuiRepaint();
    }
    return isIn; 
  }

  // ignore mouse or touch points that are not the tracked point (apart from mousedown and touchstart)
  if ((this.isPointerInside0 || this.isPointerDown0)  && !this.isPointerDown1) {  
    if ("touchend" == event.type || "touchmove" == event.type || "touchcancel" == event.type) {
      if (event.identifier != this.identifier0) {
        return false; // ignore all other touch points except "touchstart" events
      }
    } 
    else if (("mousemove" == event.type || "mouseup" == event.type) && this.identifier0 >= 0) {
      return false; // ignore mouse (except mousedown) if we are tracking a touch point
    }
  }
  if (this.isPointerDown0 && this.isPointerDown1) {
    if ("touchend" == event.type || "touchmove" == event.type || "touchcancel" == event.type) {
      if (event.identifier != this.identifier0 && 
        event.identifier != this.identifier1) {
        return false; // ignore all other touch points except "touchstart" events
      }
    }
    else if (("mousemove" == event.type || "mouseup" == event.type) && 
      (this.identifier0 >= 0 && this.identifier1 >= 0)) {
      return false; // ignore mouse (except mousedown) if we are tracking a touch point    
    }
  }

  // state changes
  if (!this.isPointerInside0 && !this.isPointerDown0 && !this.isPointerDown1) { // passive object state
    if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { // add 0th point
      this.isPointerDown0 = true;
      this.isPointerInside0 = true;
      if ("touchstart" == event.type) {
        this.identifier0 = event.identifier;
      } 
      else {
        this.identifier0 = -1; // mouse 
      }    
      this.timeDown0 = (new Date()).getTime();
      setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0); 
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale;
      this.eventXDown0 = event.eventX;
      this.eventYDown0 = event.eventY;
      this.eventX0 = event.eventX;
      this.eventY0 = event.eventY;
      cuiRepaint();
      return true;
    }
    else if (isIn && ("mousemove" == event.type || "mouseup" == event.type || 
      "touchmove" == event.type)) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = true;
      if ("touchmove" == event.type) {
        this.identifier0 = event.identifier;
      } 
      else {
        this.identifier0 = -1; // mouse 
      }    
      cuiRepaint();
      return true;
    }
    else {
      return false; // none of our business
    }
  }
  else if (this.isPointerInside0 && !this.isPointerDown0 && !this.isPointerDown1) { // focused object state (not pushed yet) 
    if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { // add 0th point
      this.isPointerDown0 = true;
      this.isPointerInside0 = true;
      if ("touchstart" == event.type) {
        this.identifier0 = event.identifier;
      } 
      else {
        this.identifier0 = -1; // mouse 
      }    
      this.timeDown0 = (new Date()).getTime();
      setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0); 
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale;
      this.eventXDown0 = event.eventX;
      this.eventYDown0 = event.eventY;
      this.eventX0 = event.eventX;
      this.eventY0 = event.eventY;
      cuiRepaint();
      return true;
    }
    else if (isIn && ("touchend" == event.type || "touchcancel" == event.type)) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = false;
      cuiRepaint();
      return true; 
    }
    else if (!isIn && ("touchmove" == event.type || "touchend" == event.type || 
      "touchcancel" == event.type || "mousemove" == event.type || "mouseup" == event.type)) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = false;
      cuiRepaint();
      return false; // none of our business
    }
    else {
      return false; // none of our business
    }
  }
  else if (this.isPointerDown0 && !this.isPointerDown1) { // object grabbed once 
    if (isIn && this.identifier0 < 0 && "mousedown" == event.type) { 
      // replace 0th mouse point
      this.identifier0 = -1; // mouse down
      this.isPointerDown0 = true;
      this.timeDown0 = (new Date()).getTime();
      setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier0, this.timeDown0); 
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale;
      this.eventXDown0 = event.eventX;
      this.eventYDown0 = event.eventY;
      this.eventX0 = event.eventX;
      this.eventY0 = event.eventY;
      cuiRepaint();
      return true;
    }
    else if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { 
      // add 1st touch point
      this.isPointerDown1 = true;
      if ("touchstart" == event.type) {
        this.identifier1 = event.identifier;
      } 
      else {
        this.identifier1 = -1; // mouse 
      }    
      this.timeDown1 = (new Date()).getTime();
      setTimeout(cuiSendHoldEvent, cuiTimeUntilHold, event.clientX, event.clientY, this.identifier1, this.timeDown1); 
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale;
      this.eventXDown0 = this.eventX0;
      this.eventYDown0 = this.eventY0;
      this.eventXDown1 = event.eventX;
      this.eventYDown1 = event.eventY;
      this.eventX1 = event.eventX;
      this.eventY1 = event.eventY;
      cuiRepaint();
      return true;
    } 
    else if ("mouseup" == event.type || ("mousemove" == event.type && 0 == event.buttons)) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = isIn;
      this.identifier0 = -1; // mouse 
      if (isIn) {
        this.hasTriggeredClick = true;
      }
      cuiRepaint();
      return true; 
    }
    else if ("touchend" == event.type) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = false;
      if (isIn) {
        this.hasTriggeredClick = true;
      }
      cuiRepaint();
      return true; 
    }
    else if ("touchcancel" == event.type) { 
      this.isPointerDown0 = false;
      this.isPointerInside0 = false;
      cuiRepaint();
      return true; 
    }
    else if ("touchmove" == event.type || ("mousemove" == event.type)) {
      this.isPointerInside0 = isIn;
      this.eventX0 = event.eventX;
      this.eventY0 = event.eventY;
      if (cuiConstants.isDraggableWithOneFinger & interactionBits) {
        this.translationX = this.translationXDown + (this.eventX0 - this.eventXDown0);
        this.translationY = this.translationYDown + (this.eventY0 - this.eventYDown0);
      }
      cuiRepaint();
      return true; 
    }
    else if (!isIn && (("mousedown" == event.type && this.identifier0 < 0) ||
      ("touchstart" == event.type && this.identifier0 == event.identifier))) {
      this.isPointerDown0 = false;
      this.isPointerInside0 = false;
      cuiRepaint();
      return false; // none of our business
    }
    else {
      return false; // none of our business
    }
  }
  else if (this.isPointerDown1) { // two pointers down
    if (("mouseup" == event.type && this.identifier0 < 0) || 
      (("touchend" == event.type || "touchcancel" == event.type) && 
      event.identifier == this.identifier0)) { // 0th point goes up
       // remove 0th point, replace by 1st 
      this.isPointerDown1 = false;
      this.isPointerDown0 = true;
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale; 
      this.eventXDown0 = this.eventX1;
      this.eventYDown0 = this.eventY1;
      this.eventX0 = this.eventX1;
      this.eventY0 = this.eventY1;
      this.identifier0 = this.identifier1;
      if (isIn) {
        this.hasTriggeredClick = true;
      }
      cuiRepaint();
      return true;
    }
    else if (("mouseup" == event.type && this.identifier1 < 0) || 
      (("touchend" == event.type || "touchcancel" == event.type) && 
      event.identifier == this.identifier1)) { // 1st point goes up
      // just remove 1st point
      this.isPointerDown1 = false;
      this.isPointerDown0 = true;
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale; 
      this.eventXDown0 = this.eventX0;
      this.eventYDown0 = this.eventY0;
      if (isIn) {
        this.hasTriggeredClick = true;
      }
      cuiRepaint();
      return true;
    } 
    else if (isIn && ("mousedown" == event.type || "touchstart" == event.type)) { 
      // remove 0th point, replace by 1st, add new as 1stby removing the 0th point, 
      // the user has a way of getting rid of ghost points
      // which are no longer tracked (but which we still assume to be active)
      this.translationXDown = this.translationX;
      this.translationYDown = this.translationY;
      this.rotationDown = this.rotation;
      this.scaleDown = this.scale;
      this.eventXDown0 = this.eventX1;
      this.eventYDown0 = this.eventY1;
      this.eventX0 = this.eventX1;
      this.eventY0 = this.eventY1;
      this.identifier0 = this.identifier1;
      this.eventXDown1 = event.eventX;
      this.eventYDown1 = event.eventY;
      this.eventX1 = event.eventX;
      this.eventY1 = event.eventY;
      if ("touchstart" == event.type) {
        this.identifier1 = event.identifier;
      } 
      else {
        this.identifier1 = -1; // mouse 
      }    
      cuiRepaint();
      return true;
    } 
    else if ("touchmove" == event.type || ("mousemove" == event.type)) {
      // update dragging
      if (("mousemove" == event.type && this.identifier0 < 0) || 
        ("touchmove" == event.type && event.identifier == this.identifier0)) { 
        this.eventX0 = event.eventX;
        this.eventY0 = event.eventY;
      }
      else if (("mousemove" == event.type && this.identifier1 < 0) || 
        ("touchmove" == event.type && event.identifier == this.identifier1)) { 
        this.eventX1 = event.eventX;
        this.eventY1 = event.eventY;
      }
      else {
        return false; // we should not have gotten this event (see above for the filtering)
      }
 
      if (cuiConstants.isRotatableWithTwoFingers & interactionBits) {
        // compute new rotation
        this.rotation = this.rotationDown + 
          (Math.atan2(this.eventY1 - this.eventY0, 
          this.eventX1 - this.eventX0) - 
          Math.atan2(this.eventYDown1 - this.eventYDown0, 
          this.eventXDown1 - this.eventXDown0)
          ) * 180.0 / Math.PI;
        while (this.rotation >= 360.0) {
          this.rotation -= 360.0;
        }
        while (this.rotation < 0.0) {
          this.rotation += 360.0;
        }
      }

      if (cuiConstants.isScalableWithTwoFingers & interactionBits) {
        // compute new scale
        var diffPointsLength = 
          Math.sqrt((this.eventX0 - this.eventX1) * 
          (this.eventX0 - this.eventX1) +  
          (this.eventY0 - this.eventY1) * 
          (this.eventY0 - this.eventY1));
        var diffPointsLengthDown = 
          Math.sqrt((this.eventXDown0 - this.eventXDown1) * 
          (this.eventXDown0 - this.eventXDown1) +  
          (this.eventYDown0 - this.eventYDown1) * 
          (this.eventYDown0 - this.eventYDown1));
        this.scale = this.scaleDown * diffPointsLength / 
           diffPointsLengthDown;  
      }

      if (cuiConstants.isDraggableWithTwoFingers & interactionBits) {
        // compute new x and y based on the motion of the center between the two points
      
        // first compute the point that was mapped to the center between the two fingers when grabbed  
        var fixpointX = 0.5 * (this.eventXDown0 + this.eventXDown1);
        var fixpointY = 0.5 * (this.eventYDown0 + this.eventYDown1);
        var mappedX = fixpointX - this.translationXDown - x - 0.5 * width;
        var mappedY = fixpointY - this.translationYDown - y - 0.5 * height;
        var angle = -this.rotationDown * Math.PI / 180.0;
        fixpointX = (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY) / this.scaleDown
          + x + 0.5 * width;
        fixpointY = (Math.sin(angle) * mappedX  + Math.cos(angle) * mappedY) / this.scaleDown
          + y + 0.5 * height;
       
        // now see where this fixpoint is mapped to with the current transformation
        mappedX = fixpointX - x - 0.5 * width;
        mappedY = fixpointY - y - 0.5 * height;
        angle = this.rotation * Math.PI / 180.0;
        var tempX = this.scale * (Math.cos(angle) * mappedX - Math.sin(angle) * mappedY);
        mappedY = this.scale * (Math.sin(angle) * mappedX  + Math.cos(angle) * mappedY); 
        mappedX = tempX + x + 0.5 * width + this.translationX;
        mappedY = mappedY + y + 0.5 * height + this.translationY;
      
        // (x,y) should be at the center between the two fingers; 
        // we change the transformation such that it ends up there
      
        this.translationX = this.translationX + 
          0.5 * (this.eventX0 + this.eventX1) - mappedX;
        this.translationY = this.translationY + 
          0.5 * (this.eventY0 + this.eventY1) - mappedY;
      }
      cuiRepaint();
      return true; 
    } 
    else {
      return false;
    }
  }
  // unreachable code
  return false;
}


< 畫布 2D 網頁應用

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