跳转至内容

画布 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 网页应用

除非另有说明,否则此页面上的所有示例源代码均归属公有领域。
华夏公益教科书