내보내기(0) 인쇄
모두 확장

터치 사용

캔버스 이미지를 로컬에 저장에서 제공한 정보를 기반으로 여기서는 Internet Explorer 10을 사용하여 터치 지원(및 한 가지 제스처)을 사용하도록 설정하는 방법에 대해 설명합니다.

참고  이 항목에 제공된 터치/제스처 코드는 Windows 8 이상의 Internet Explorer 10에만 적용됩니다. Internet Explorer 10에 대해 이 문서가 작성된 이후 Internet Explorer 포인터 이벤트 구현이 약간 변경되었습니다. 코드를 업데이트하고 미래에도 경쟁력을 갖추는 방법에 대한 자세한 내용은 포인터 이벤트 업데이트를 참조하세요.

터치 지원 추가의 첫 번째 단계는 다음과 같은 CSS(마지막 두 줄) 삽입입니다.


canvas {
  border: 2px solid black;
  -ms-touch-action: none;
  touch-action: none;
}    

-ms-touch-action(및 사용 가능한 경우 touch-action) 은 브라우저에서 캔버스 요소에 대해 터치 입력을 사용하지 않도록 지시하여 JavaScript에서 입력을 사용할 수 있도록 합니다. 예를 들어 마우스 이벤트에서처럼 다양한 터치/제스처 입력에 응답하도록 터치/제스처 이벤트 처리기를 등록할 수 있으며, 다음 예제에서 보여 줍니다.

Mandelbrot 7


<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" />
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Mandelbrot 7</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      text-align: center;
    }
    
    canvas {
      border: 2px solid black;
      -ms-touch-action: none;
      touch-action: none; /* Use the standard, if available. */
    }    
    
    table {
      margin: 0 auto; /* Center the table. */
    }
    
    #messageBox {
      text-align: left;
    }
        
    #elapsedTime {
      width: 23em;
      text-align: right;
    }    

    button {
      width: 5em;
    }
    
    #filenameForm {
      visibility: hidden; /* As opposed to "display: none", keep room for this hidden element in the layout. */
    }
}
  </style>      
</head>

<body>
  <h1>Mandelbrot 7</h1>
  <p>This example demonstrates how to enable touch support.</p>        
  <table>
    <tr>
      <td id="messageBox"></td>
      <td id="elapsedTime"></td>
    </tr>
  </table>
  <canvas width="600" height="400" oncontextmenu="return false;"> <!-- Because the hold gesture event can fire more than once, the 'oncontextmenu="return false;"' attribute is used to stop the right-click context menu from appearing inappropriately. -->
    Canvas not supported - upgrade your browser (after checking that your browser is in the correct mode).
  </canvas><br>
  <button type="button" id="resetButton">Reset</button>  
  <button type="button" id="lightenButton">Lighten</button>    
  <button type="button" id="saveButton">Save</button>
  <form id="filenameForm"> 
    Extensionless filename: <input id="filename" type="text"> <input type="submit" value="Submit">
  </form>
  <script>
    if (!window.Worker) { // Check for the availability of the Worker() constructor.
      document.getElementsByTagName('body')[0].innerHTML = "<h2>Web Workers not supported - upgrade your browser<br>(after checking that your browser is in the correct mode)</h2>";      
    }
    else {
      var RE_MAX = 1.1; // This value will be adjusted as necessary to ensure that the rendered Mandelbrot set is never skewed (that is, true to it's actual shape).
      var RE_MIN = -2.5;
      var IM_MAX = 1.2;
      var IM_MIN = -1.2;
      var MAX_ITERATIONS = 1200; // Increase this value to improve detection of complex c values that belong to the Mandelbrot set.
      var STATIC_ZOOM_BOX_FACTOR = 0.25; // Increase to make the double-click and hold gesture zoom box bigger.
      var DEFAULT_MESSAGE = "Click or click-and-drag to zoom."
      
      var globals = {}; // See the handleLoad function.
      
      window.addEventListener('load', handleLoad, false);
    } // if-else
                
    /************************************************************************************************************************************************************/
    
    Number.prototype.format = function() {
    /* 
      Formats this integer so that it has commas in the expected places.
    */
    	var numberString = Math.round(this).toString(); // An integer value is assumed, so we ensure that it is indeed an integer.
    	var precompiledRegularExpression = /(\d+)(\d{3})/;
    	
    	while ( precompiledRegularExpression.test(numberString) ) {
    		numberString = numberString.replace(precompiledRegularExpression, '$1' + ',' + '$2'); // For this integer, inject ","'s at the appropriate locations.
    	} // while
    	
    	return numberString;
    } // Number.prototype.format

    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    

    function handleLoad() {          
      var canvas = document.getElementsByTagName('canvas')[0];
      var canvasWidth = canvas.width;
      var canvasHeight = canvas.height;      
      var ctx = canvas.getContext('2d');
      
      document.getElementsByTagName('table')[0].width = canvasWidth; // Make the table's width the same as the canvas's width. 
      document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE;            

      globals.canvas = canvas;
      globals.canvas.context = ctx;
      globals.canvas.context.imageDataObject = ctx.createImageData(canvasWidth, canvasHeight); // Create an appropriately sized but empty canvas image data object.
      
      globals.staticZoomBoxWidth = STATIC_ZOOM_BOX_FACTOR * canvasWidth; // Maintains the original canvas width/height ratio.
      globals.staticZoomBoxHeight = STATIC_ZOOM_BOX_FACTOR * canvasHeight; // Maintains the original canvas width/height ratio.      
      
      globals.pointer = {};
      globals.pointer.down = false;  
                 
      window.addEventListener('hashchange', handleHashChange, false); // This event handler executes whenever the URL hash string changes.
      
      if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) { // Future proofing.
        // It's either-or with MS pointer events - they cannot be registered concurrently.
        window.gesture = window.gesture || window.MSGesture; // Future proofing.
        globals.gesture = new gesture();
        globals.gesture.target = canvas; 
        canvas.addEventListener('MSPointerDown', function(evt) { globals.gesture.addPointer(evt.pointerId); }, false); 
          
        canvas.addEventListener('MSGestureStart', handlePointer, false); 
        canvas.addEventListener('mousedown', handlePointer, false); // Required for the case when the mouse is clicked but not moved.
        
        canvas.addEventListener('MSGestureChange', handlePointer, false);
        
        canvas.addEventListener('MSGestureEnd', handlePointer, false);
        canvas.addEventListener('mouseup', handlePointer, false); // Required for the case when the mouse is clicked but not moved.

        canvas.addEventListener('MSGestureHold', handlePointer, false);
      }    
      else {
        canvas.addEventListener('mousedown', handlePointer, false);
        canvas.addEventListener('mousemove', handlePointer, false);
        canvas.addEventListener('mouseup', handlePointer, false);    
      } // if-else
            
      document.getElementById('resetButton').addEventListener('click', handleResetButton, false);
      document.getElementById('lightenButton').addEventListener('click', handleLightenButton, false);    
      document.getElementById('saveButton').addEventListener('click', handleSaveButton, false);        
      document.getElementById('filenameForm').addEventListener('submit', handleFormSubmit, false);    
      
      ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; // The color and opacity of the zoom box. This is what gets saved when calling ctx.save().          
 
      handleHashChange(); // On page load, simulate a page URL change to draw the initial Mandelbrot set.
    } // handleLoad
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    
    
    function handleHashChange() {
      var hashValues = getHashValues(); // This function examines window.location.hash but doesn't change it.
      
      if (hashValues) {
        globals.ReMax = hashValues.ReMax;
        globals.ReMin = hashValues.ReMin;
        globals.ImMax = hashValues.ImMax;
        globals.ImMin = hashValues.ImMin;
        globals.grayscaleFactor = hashValues.grayscaleFactor;
      }
      else {
        globals.ReMax = adjusted_RE_MAX();
        globals.ReMin = RE_MIN;
        globals.ImMax = IM_MAX;
        globals.ImMin = IM_MIN;     
        globals.grayscaleFactor = 1; // Multiplying any value by 1 has no effect.
      } // if-else
      
      drawMandelbrot(globals.ReMax, globals.ReMin, globals.ImMax, globals.ImMin, globals.grayscaleFactor);
    } // handelHashChange    

    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/        

    function getHashValues() {
      var dirtyComplexPlaneExtremaString = (window.location.hash).replace('#', ''); // Remove the leading "#" character from the string.
      var complexPlaneExtremaString = dirtyComplexPlaneExtremaString.split(','); // Returns an array. Assumes the following string form: "ReMax,ReMin,ImMax,ImMin,grayscaleFactor" (note that if grayscaleFactor is 1, the image's grayscale is not affected).
      
      var ReMax = parseFloat( complexPlaneExtremaString[0] ); 
      var ReMin = parseFloat( complexPlaneExtremaString[1] ); 
      var ImMax = parseFloat( complexPlaneExtremaString[2] ); 
      var ImMin = parseFloat( complexPlaneExtremaString[3] );
      var grayscaleFactor = parseFloat( complexPlaneExtremaString[4] );
      
      if ( isNaN(ReMax) || isNaN(ReMin) || isNaN(ImMax) || isNaN(ImMin) || isNaN(grayscaleFactor) ) { 
        return null;
      } // if 
      
      return {ReMax: ReMax, ReMin: ReMin, ImMax: ImMax, ImMin: ImMin, grayscaleFactor: grayscaleFactor};
    } // getHashValues
        
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/        

    function adjusted_RE_MAX() {    
      var ReMax = globals.canvas.width * ( (IM_MAX - IM_MIN) / globals.canvas.height ) + RE_MIN;
      
      if (RE_MAX != ReMax) {
        alert("RE_MAX has been adjusted to: " + ReMax); // The user should never see this if RE_MAX is set correctly above.
      } // if

      return ReMax;
    } // adjusted_RE_MAX    
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    
    
    function drawMandelbrot(ReMax, ReMin, ImMax, ImMin, grayscaleFactor) {            
      document.getElementById('messageBox').innerHTML = "Calculating..."; // This isn't displayed until the drawMandelbrot function block exits. 
      document.getElementById('elapsedTime').innerHTML = ""; // Erase the prior run's statistics.           
      
      if (window.setImmediate) {
        window.setImmediate(calculateMandelbrot); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.       
      }
      else {
        window.setTimeout(calculateMandelbrot, 0); // Allow the drawMandelbrot function to immediately terminate, thus printing "Calculating..." to the screen.      
      }           
      
      function calculateMandelbrot() {
        var startTime = new Date(); // Report how long it takes to render this particular region of the Mandelbrot set.             
        
        var canvas = globals.canvas;
        var canvasWidth = canvas.width;
        var canvasHeight = canvas.height;
        var ctx = canvas.context;
        var imageDataObjectData = ctx.imageDataObject.data; // imageDataObjectData is a reference to, not a copy of, ctx.imageDataObject.data
        
        var maxPixelGrayscaleValue = 0; // This will contain the lightest shade of gray in the drawn Mandelbrot image.
        
        var x_coefficient = (ReMax - ReMin) / canvasWidth; // Keep the below loops as computation-free as possible.
        var y_coefficient = (ImMin - ImMax) / canvasHeight; // Keep the below loops as computation-free as possible.
  
        var iterationSum = 0;
        var currentPixel = 0;          
        for (var y = 0; y < canvasHeight; y++) {
          var c_Im = (y * y_coefficient) + ImMax; // Note that c = c_Re + c_Im*i
          
          for (var x = 0; x < canvasWidth; x++) {
            var c_Re = (x * x_coefficient) + ReMin // Convert the canvas x-coordinate to a complex plane Re-coordinate. c_Re represents the real part of a c value.
            
            var z_Re = 0; // The first z value (Zo) must be 0.
            var z_Im = 0; // The first z value (Zo) must be 0. Note that z = z_Re + z_Im*i
            
            var c_belongsToMandelbrotSet = true; // Assume that the c associated with Zn belongs to the Mandelbrot set (i.e., Zn remains bounded under iteration of Zn+1 = (Zn)^2 + c).
            var exponentialSmoothingSum = 0;
            for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
              iterationSum++; // Keep track of how many iterations were performed in total so we can report this to the user.
            
              var z_Re_squared = z_Re * z_Re; // A small speed optimization.
              var z_Im_squared = z_Im * z_Im; // A small speed optimization.
              
              exponentialSmoothingSum += Math.exp( -(z_Re_squared + z_Im_squared) ); // Technically, this should be e^(-|z|). However, avoiding the expensive square root operation doesn't really affect the resulting image.              
              if (exponentialSmoothingSum >= 255) { // Don't cycle through the gray colors.
                exponentialSmoothingSum = 255;
              }
      
              if (z_Re_squared + z_Im_squared > 4) { // Checks if |z^2| is greater than 2. This approach avoids the expensive square root operation.
                c_belongsToMandelbrotSet = false; // This complex c value is not part of the Mandelbrot set (because it will always tend towards infinity under iteration).
                break; // Immediately check the next c value to see if it belongs to the Mandelbrot set or not.
              } // if
              
              // The next two lines perform Zn+1 = (Zn)^2 + c (recall that (x + yi)^2 = x^2 - y^2 + 2xyi, thus the real part is x^2 - y^2 and the imaginary part is 2xyi).
              z_Im = (2 * z_Re * z_Im) + c_Im; // We must calculate the next value of z_Im first because it depends on the current value of z_Re (not the next value of z_Re).
              z_Re = z_Re_squared - z_Im_squared + c_Re; // Calculate the next value of z_Re.
            } // for   
            
            if (c_belongsToMandelbrotSet) { // This complex c value is probably part of the Mandelbrot set because Zn did not tend toward infinity within MAX_ITERATIONS iterations.
              imageDataObjectData[currentPixel++] = 0; // Red. Note that there are 255 possible shades of red, green, blue, and alpha (i.e., opacity).
              imageDataObjectData[currentPixel++] = 0; // Green.
              imageDataObjectData[currentPixel++] = 0; // Blue.
              imageDataObjectData[currentPixel++] = 255; // Alpha (i.e., 0% transparency).
            } 
            else { // This complex c value is definitely not part of the Mandelbrot set because Zn would tend toward infinity under iteration (i.e., |Zn| > 2).
              var pixelGrayscaleValue = 255 - exponentialSmoothingSum % 256; // Force the value of exponentialSmoothingSum to be between 0 and 255 inclusively. Note that all values for red, green, and blue are identical when using a grayscale.
              var adjustedPixelGrayscaleValue = pixelGrayscaleValue * grayscaleFactor;
              
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // Because we mod by 256, the value of exponentialSmoothingSum will always be between 0 and 255.
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // If exponentialSmoothingSum is 255 (its maximum possible value), then 255 % 256 = 255.
              imageDataObjectData[currentPixel++] = adjustedPixelGrayscaleValue; // When exponentialSmoothingSum is 255, we have 255 - 255 = 0, so the shade values for RGB are all set to 0 (that is, the c-value pixel is rendered black - indicating that this particular c-value very slowly tends towards infinity).
              imageDataObjectData[currentPixel++] = 255; // Always draw the c-value pixels with no transparency.
              
              if (pixelGrayscaleValue > maxPixelGrayscaleValue) {
                maxPixelGrayscaleValue = pixelGrayscaleValue; // Determine the lightest shade of gray in case the user clicks the Lighten button.
              } // if
            } // if-else
          } // for
        } // for        
        
        globals.maxPixelGrayscaleValue = maxPixelGrayscaleValue; // Store the lightest shade of gray in case the user clicks the Lighten button.      
        ctx.putImageData(ctx.imageDataObject, 0, 0); // Render our carefully constructed canvas image data array to the canvas.
            
        var elapsedMilliseconds = (new Date()) - startTime;
        document.getElementById('elapsedTime').innerHTML = iterationSum.format() + " iterations in " + (elapsedMilliseconds / 1000).toFixed(2) + " seconds"; // Note that the UI element is not updated until after this block terminates (which is the desired behavior).            
        document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE; // Erase the "Calculating..." message and replace it with the default message.        
      } // calculateMandelbrot
    } // drawMandelbrot
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function xToRe(x) {
      var x_coefficient = (globals.ReMax - globals.ReMin) / globals.canvas.width; 
      
      return (x * x_coefficient) + globals.ReMin; // Converts a canvas x-coordinate value to the associated complex plane Re-coordinate.
    } // xToRe
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/    

    function yToIm(y) {
      var y_coefficient = (globals.ImMin - globals.ImMax) / globals.canvas.height; 
      
      return (y * y_coefficient) + globals.ImMax; // Converts a canvas y-coordinate value to the associated complex plane Im-coordinate.
    } // yToIm
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handlePointer(evt) {
      var canvasWidthHeightRatio = globals.canvas.width / globals.canvas.height;
      var ctx = globals.canvas.context;
      
      var canvasX;
      var canvasY;      
      
      if (evt.offsetX && evt.offsetY) {
        canvasX = evt.offsetX; // Not supported in Firefox.
        canvasY = evt.offsetY; // Does not assume that the canvas element is a direct descendent of the body element.
      } else {
        canvasX = evt.clientX - evt.target.offsetLeft; // Supported in Firefox.
        canvasY = evt.clientY - evt.target.offsetTop; // Assumes that the canvas element is a direct descendent of the body element.
      } // if-else
      
      var zoomBoxWidth;
      var zoomBoxHeight;
      
      var ReMax;
      var ReMin;
      var ImMax;
      var ImMin;
      
      var staticZoomBoxWidth = globals.staticZoomBoxWidth;
      var staticZoomBoxHeight = globals.staticZoomBoxHeight;
      var halfStaticZoomBoxWidth = staticZoomBoxWidth / 2;
      var halfStaticZoomBoxHeight = staticZoomBoxHeight / 2;
      
      switch (evt.type) {
        case 'MSGestureStart':              
        case 'mousedown':
          globals.pointer.down = true;      
          globals.pointer.x1 = canvasX;
          globals.pointer.y1 = canvasY;
          break;
        case 'MSGestureChange':                  
        case 'mousemove':
          if (globals.pointer.down) {                               
            zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1);  
            zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // We must keep the zoom box dimensions proportional to the canvas dimensions in order to ensure that the resulting zoomed Mandelbrot image does not become skewed.
            ctx.putImageData(ctx.imageDataObject, 0, 0); // Assumes that an initial image of the Mandelbrot set is drawn before we get to this point in the code. The purpose of this line is to erase the prior zoom box rectangle before drawing the next zoom box rectangle.
            ctx.fillRect(globals.pointer.x1, globals.pointer.y1, zoomBoxWidth, zoomBoxHeight); // With a freshly painted image of the current Mandelbrot set in place (see prior line), draw a new zoom box rectangle.
          }
          break;
        case 'MSGestureEnd':
        case 'mouseup':                              
          globals.pointer.down = false;          
          
          zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1); // Only allow the zoom box to be drawn from an upper-left corner position down to a lower-right corner position.
          zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // Again, ensure that the width/height ratio of the zoom box is proportional to the canvas's (this simplifies the algorithm).          
          
          if (zoomBoxHeight == 0) { // No zoom box has been drawn, so honor the fixed sized zoom box.  
            ctx.putImageData(ctx.imageDataObject, 0, 0); // For the MSGestureHold case, erase the previously drawn zoom box so we don't draw two or more on top of each other.
            ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight); // Just leave this on the screen.
                         
            ReMin = xToRe(canvasX - halfStaticZoomBoxWidth); // Center the static zoom box about the point (evt.offsetX, evt.offsetY).
            ImMax = yToIm(canvasY - halfStaticZoomBoxHeight); 
            
            ReMax = xToRe(canvasX + halfStaticZoomBoxWidth);
            ImMin = yToIm(canvasY + halfStaticZoomBoxHeight);
          } 
          else { // A (possibly tiny) zoom box has been drawn, so honor it.
            ReMin = xToRe(globals.pointer.x1); // Convert the mouse's x-coordinate value (on the canvas) to the associated Re-coordinate value in the complex plane.
            ImMax = yToIm(globals.pointer.y1); // Convert the mouse's y-coordinate value (on the canvas) to the associated Im-coordinate value in the complex plane.
                                      
            ReMax = xToRe(zoomBoxWidth + globals.pointer.x1); // Convert the zoom box's final x-coordinate value to the associated Re-coordinate value in the complex plane.  
            ImMin = yToIm(zoomBoxHeight + globals.pointer.y1);  // Convert the zoom box's final y-coordinate value to the associated Re-coordinate value in the complex plane.            
          } // if-else        
        
          window.location.hash = ReMax + "," + ReMin + "," + ImMax + "," + ImMin + "," + globals.grayscaleFactor; // This triggers the handleHashChange event handler which, among other things, is responsible for drawing the Mandelbrot set.
          break; 
        case 'MSGestureHold':
          if (evt.detail & evt.MSGESTURE_FLAG_BEGIN) {
            ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight); // At the first sign of a hold gesture, get the zoom box up on the screen immediately.                 
          }  
          
          // The evt.MSGESTURE_FLAG_END component of the hold gesture is handled by the "if (zoomBoxHeight == 0)" clause of the MSGestureEnd clause above.
          
          break;
        default:
          alert("Error in switch statement."); // Although unnecessary, defensive programming techniques such as this are highly recommended.
      } // switch              
    } // handlePointer    
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function handleResetButton() {
      window.location.hash = adjusted_RE_MAX() + "," + RE_MIN + "," + IM_MAX + "," + IM_MIN + "," + 1; // // This triggers the handleHashChange event handler which, among other things, is responsible for drawing the Mandelbrot set.
    } // handleResetButton
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/
    
    function handleLightenButton() {
    /* 
      This creates a value (factor) such that black (0) stays black and the lightest gray value in the image becomes white (255). Thus, clicking the 
      Lighten button can remove mathematical meaning of the (proper) grayscale but can make dark images more visible.
    */
      var grayscaleFactor = 255 / globals.maxPixelGrayscaleValue; // For the canvas element, 255 is white, 0 is black.

      window.location.hash = globals.ReMax + "," + globals.ReMin + "," + globals.ImMax + "," + globals.ImMin + "," + grayscaleFactor; // This invokes handleHashChange which, among other things, is responsibile for drawing the Mandelbrot set.
    } // handleResetButton
    
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handleSaveButton() {
      document.getElementById('filenameForm').style.visibility = "visible";
      document.getElementById('filename').focus(); // Place the cursor in the filename text input box.
    } // handleResetButton 
 
    /*----------------------------------------------------------------------------------------------------------------------------------------------------------*/

    function handleFormSubmit(evt) {
    /* 
      As of 8/2012, the following code only works starting with Internet Explorer 10.
    */
      evt.preventDefault(); // Do not refresh the page when the Submit button is clicked.
      
      window.BlobBuilder = window.BlobBuilder || window.MSBlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
      globals.canvas.toBlob = globals.canvas.toBlob || globals.canvas.msToBlob;      
      window.navigator.saveBlob = window.navigator.saveBlob || window.navigator.msSaveBlob;
      
      if (window.BlobBuilder && globals.canvas.toBlob && window.navigator.saveBlob) {
        var extensionlessFilename = evt.target[0].value;
        var filename = extensionlessFilename + ".png";
        var blobBuilderObject = new BlobBuilder(); // Create a blob builder object so that we can append content to it.
                
        blobBuilderObject.append( globals.canvas.toBlob() ); // Append the user's drawing in PNG format to the builder object.
        window.navigator.saveBlob(blobBuilderObject.getBlob(), filename); // Move the builder object content to a blob and save it to a file.      
      }
      else {
        document.getElementById('messageBox').innerHTML = "<strong style='color: red;'>CANNOT SAVE FILE</strong>";
      } // if-else
      
      document.getElementById('filenameForm').style.visibility = "hidden";
    } // handleFormSubmit
  </script>
</body>

</html>

가리키는 첫 번째 항목은 oncontextmenu 특성입니다.


<canvas width="600" height="400" oncontextmenu="return false;">

이 특성은 마우스 오른쪽 단추 클릭 상황에 맞는 메뉴가 캔버스에 나타나지 않도록 할 수 있으며, 누르기 제스처로 발생될 수 있습니다.

제스처 지원의 첫 번째 단계는 해당 이벤트 처리기 등록입니다.


function handleLoad() {          
  var canvas = document.getElementsByTagName('canvas')[0];
  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;      
  var ctx = canvas.getContext('2d');
  
  document.getElementsByTagName('table')[0].width = canvasWidth; // Make the table's width the same as the canvas's width. 
  document.getElementById('messageBox').innerHTML = DEFAULT_MESSAGE;            

  globals.canvas = canvas;
  globals.canvas.context = ctx;
  globals.canvas.context.imageDataObject = ctx.createImageData(canvasWidth, canvasHeight); // Create an appropriately sized but empty canvas image data object.
  
  globals.staticZoomBoxWidth = STATIC_ZOOM_BOX_FACTOR * canvasWidth; // Maintains the original canvas width/height ratio.
  globals.staticZoomBoxHeight = STATIC_ZOOM_BOX_FACTOR * canvasHeight; // Maintains the original canvas width/height ratio.      
  
  globals.pointer = {};
  globals.pointer.down = false;  
             
  window.addEventListener('hashchange', handleHashChange, false); // This event handler executes whenever the URL hash string changes.
  
  if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) { // Future proofing.
    // It's either-or with MS pointer events - they cannot be registered concurrently.
    window.gesture = window.gesture || window.MSGesture; // Future proofing.
    globals.gesture = new gesture();
    globals.gesture.target = canvas; 
    canvas.addEventListener('MSPointerDown', function(evt) { globals.gesture.addPointer(evt.pointerId); }, false); 
      
    canvas.addEventListener('MSGestureStart', handlePointer, false); 
    canvas.addEventListener('mousedown', handlePointer, false); // Required for case when the mouse is clicked but not moved.
    
    canvas.addEventListener('MSGestureChange', handlePointer, false);
    
    canvas.addEventListener('MSGestureEnd', handlePointer, false);
    canvas.addEventListener('mouseup', handlePointer, false); // Required for case when the mouse is clicked but not moved.

    canvas.addEventListener('MSGestureHold', handlePointer, false);
  }    
  else {
    canvas.addEventListener('mousedown', handlePointer, false);
    canvas.addEventListener('mousemove', handlePointer, false);
    canvas.addEventListener('mouseup', handlePointer, false);    
  } // if-else
        
  document.getElementById('resetButton').addEventListener('click', handleResetButton, false);
  document.getElementById('lightenButton').addEventListener('click', handleLightenButton, false);    
  document.getElementById('saveButton').addEventListener('click', handleSaveButton, false);        
  document.getElementById('filenameForm').addEventListener('submit', handleFormSubmit, false);    
  
  ctx.fillStyle = "rgba(255, 0, 0, 0.3)"; // The color and opacity of the zoom box. This is what gets saved when calling ctx.save().          

  handleHashChange(); // On page load, simulate a page URL change to draw the initial Mandelbrot set.
} // handleLoad

예를 보면 알 수 있듯이 window.navigator.msPointerEnabled 속성을 쿼리하여 사용할 수 있는 경우에만 제스처 처리기를 등록합니다.

window.navigator.pointerEnabled || window.navigator.msPointerEnabledtrue라고 가정하고 먼저 다음 세 줄을 실행합니다.


window.gesture = window.gesture || window.MSGesture; // Future proofing.
globals.gesture = new gesture(); 
globals.gesture.target = canvas; 
canvas.addEventListener('MSPointerDown', function(evt) { globals.gesture.addPointer(evt.pointerId); }, false);

즉, 다음과 같이 수행합니다.

  1. 제스처 개체를 만듭니다.
  2. 대상을 캔버스로 설정합니다. 이는 캔버스 요소의 터치 이벤트가 전역 제스처 개체 globals.gesture에 연결됨을 의미합니다.
  3. canvas down 이벤트가 발생할 때 이벤트의 ID 값이 전역 제스처 개체에 할당되는 방식으로 익명 이벤트 처리기를 등록합니다. 이 ID 값은 여러 손가락 제스처(이 예제에서는 사용되지 않음)에서 손가락별 이벤트 개체를 추적하는 데 필요합니다.

다음으로 제스처 이벤트 처리기(콜백)를 등록합니다.


canvas.addEventListener('MSPointerDown', function(evt) { globals.gesture.addPointer(evt.pointerId); }, false); 
  
canvas.addEventListener('MSGestureStart', handlePointer, false); 
canvas.addEventListener('mousedown', handlePointer, false); // Required for the case when the mouse is clicked but not moved.

canvas.addEventListener('MSGestureChange', handlePointer, false);

canvas.addEventListener('MSGestureEnd', handlePointer, false);
canvas.addEventListener('mouseup', handlePointer, false); // Required for the case when the mouse is clicked but not moved.

canvas.addEventListener('MSGestureHold', handlePointer, false);

한 가지를 제외하면 이벤트 처리기 자체에 표시된 대로 표준 마우스 이벤트와 제스처 이벤트가 일대일 대응됩니다.


function handlePointer(evt) {
  var canvasWidthHeightRatio = globals.canvas.width / globals.canvas.height;
  var ctx = globals.canvas.context;
  
  var canvasX;
  var canvasY;      
  
  if (evt.offsetX && evt.offsetY) {
    canvasX = evt.offsetX; // Not supported in Firefox.
    canvasY = evt.offsetY; // Does not assume that the canvas element is a direct descendent of the body element.
  } else {
    canvasX = evt.clientX - evt.target.offsetLeft; // Supported in Firefox.
    canvasY = evt.clientY - evt.target.offsetTop; // Assumes that the canvas element is a direct descendent of the body element.
  } // if-else
  
  var zoomBoxWidth;
  var zoomBoxHeight;
  
  var ReMax;
  var ReMin;
  var ImMax;
  var ImMin;
  
  var staticZoomBoxWidth = globals.staticZoomBoxWidth;
  var staticZoomBoxHeight = globals.staticZoomBoxHeight;
  var halfStaticZoomBoxWidth = staticZoomBoxWidth / 2;
  var halfStaticZoomBoxHeight = staticZoomBoxHeight / 2;
  
  switch (evt.type) {
    case 'MSGestureStart':              
    case 'mousedown':                 
      globals.pointer.down = true;      
      globals.pointer.x1 = canvasX;
      globals.pointer.y1 = canvasY;
      break;
    case 'MSGestureChange':                  
    case 'mousemove':
      if (globals.pointer.down) {                               
        zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1);  
        zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // We must keep the zoom box dimensions proportional to the canvas dimensions in order to ensure that the resulting zoomed Mandelbrot image does not become skewed.
        ctx.putImageData(ctx.imageDataObject, 0, 0); // Assumes that an initial image of the Mandelbrot set is drawn before we get to this point in the code. The purpose of this line is to erase the prior zoom box rectangle before drawing the next zoom box rectangle.
        ctx.fillRect(globals.pointer.x1, globals.pointer.y1, zoomBoxWidth, zoomBoxHeight); // With a freshly painted image of the current Mandelbrot set in place (see prior line), draw a new zoom box rectangle.
      }
      break;
    case 'MSGestureEnd':
    case 'mouseup':                              
      globals.pointer.down = false;          
      
      zoomBoxHeight = Math.abs(canvasY - globals.pointer.y1); // Only allow the zoom box to be drawn from an upper-left corner position down to a lower-right corner position.
      zoomBoxWidth = zoomBoxHeight * canvasWidthHeightRatio; // Again, ensure that the width/height ratio of the zoom box is proportional to the canvas's (this simplifies the algorithm).          
      
      if (zoomBoxHeight == 0) { // No zoom box has been drawn, so honor the fixed sized zoom box.  
        ctx.putImageData(ctx.imageDataObject, 0, 0); // For the MSGestureHold case, erase the previously drawn zoom box so we don't draw two or more on top of each other.
        ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight); // Just leave this on the screen.
                     
        ReMin = xToRe(canvasX - halfStaticZoomBoxWidth); // Center the static zoom box about the point (evt.offsetX, evt.offsetY).
        ImMax = yToIm(canvasY - halfStaticZoomBoxHeight); 
        
        ReMax = xToRe(canvasX + halfStaticZoomBoxWidth);
        ImMin = yToIm(canvasY + halfStaticZoomBoxHeight);
      } 
      else { // A (possibly tiny) zoom box has been drawn, so honor it.
        ReMin = xToRe(globals.pointer.x1); // Convert the mouse's x-coordinate value (on the canvas) to the associated Re-coordinate value in the complex plane.
        ImMax = yToIm(globals.pointer.y1); // Convert the mouse's y-coordinate value (on the canvas) to the associated Im-coordinate value in the complex plane.
                                  
        ReMax = xToRe(zoomBoxWidth + globals.pointer.x1); // Convert the zoom box's final x-coordinate value to the associated Re-coordinate value in the complex plane.  
        ImMin = yToIm(zoomBoxHeight + globals.pointer.y1);  // Convert the zoom box's final y-coordinate value to the associated Re-coordinate value in the complex plane.            
      } // if-else        
    
      window.location.hash = ReMax + "," + ReMin + "," + ImMax + "," + ImMin + "," + globals.grayscaleFactor; // This triggers the handleHashChange event handler which, among other things, is responsible for drawing the Mandelbrot set.
      break; 
    case 'MSGestureHold':
      if (evt.detail & evt.MSGESTURE_FLAG_BEGIN) {
        ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight); // At the first sign of a hold gesture, get the zoom box up on the screen immediately.                 
      }  
      
      // The evt.MSGESTURE_FLAG_END component of the hold gesture is handled by the "if (zoomBoxHeight == 0)" clause of the MSGestureEnd clause above.
      
      break;
    default:
      alert("Error in switch statement."); // Although unnecessary, defensive programming techniques such as this are highly recommended.
  } // switch              
} // handlePointer

이러한 대응은 switch 문 내에 다음과 같이 포함되어 있습니다.

  • mousedownMSGestureStart
  • mousemoveMSGestureChange
  • mouseupMSGestureEnd

한 가지 예외는 MSGestureHold입니다.


case 'MSGestureHold':
  if (evt.detail & evt.MSGESTURE_FLAG_BEGIN) {
    ctx.fillRect(canvasX - halfStaticZoomBoxWidth, canvasY - halfStaticZoomBoxHeight, staticZoomBoxWidth, staticZoomBoxHeight);           
  }  
  break;

이 절의 유일한 목적은 누르기 제스처가 감지되는 즉시 (가운데) 확대/축소 상자를 화면의 위로 가져오는 것입니다. 누르기 제스처의 최종 구성 요소는 MSGestureEnd이므로 switch 문의 MSGestureEnd 절에서 실제 확대/축소 요청을 처리하도록 합니다.

Internet Explorer 10에서는 속도가 상대적으로 빠르지만 웹 작업자를 사용하면 Mandelbrot 집합의 이미지 두 개를 동시에 그려 사용자 환경을 개선할 수 있습니다.

  • 거친(낮은 상세) 이미지
  • 정교한(높은 상세) 이미지

일반적으로 낮은 상세 이미지가 먼저 완료되어 캔버스에 그려집니다. 그 직후에 높은 상세 이미지가 캔버스에 그려집니다. 따라서 이미지 계산이 완료될 때까지 기다리는 동안 검정색 화면에서 시작되는 최적화되지 않은 사용자 환경을 줄일 수 있습니다. 다음 웹 작업자 활용 섹션에서 이 기술에 대해 자세히 설명합니다.

관련 항목

제스처 이벤트를 사용하여 이동, 확대/축소 및 탭 이상의 기능 수행
웹 작업자 활용
터치

 

 

표시:
© 2014 Microsoft