From 05b8cb82fdf85ecf83229e5b517f188207a3b9f9 Mon Sep 17 00:00:00 2001 From: thatcher Date: Thu, 9 Feb 2012 22:16:09 -0500 Subject: [PATCH] finished support for touch events, works great on the ipad, a little slow on iphone (I think we can scale drag and zoom better based on viewport size). Awesome --- build.properties | 4 +- openseadragon.js | 210 +++++++++++++++++++++++++++++++++++++------ src/mousetracker.js | 167 +++++++++++++++++++++++++++++----- src/openseadragon.js | 15 +++- src/viewer.js | 26 +++++- 5 files changed, 363 insertions(+), 59 deletions(-) diff --git a/build.properties b/build.properties index 314e4b65..97d8d9bc 100644 --- a/build.properties +++ b/build.properties @@ -5,8 +5,8 @@ PROJECT: openseadragon BUILD_MAJOR: 0 -BUILD_MINOR: 8 -BUILD_ID: 27 +BUILD_MINOR: 9 +BUILD_ID: 01 BUILD: ${PROJECT}.${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID} VERSION: ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_ID} diff --git a/openseadragon.js b/openseadragon.js index 7021df6b..6c05e08c 100644 --- a/openseadragon.js +++ b/openseadragon.js @@ -1,5 +1,5 @@ /** - * @version OpenSeadragon 0.8.27 + * @version OpenSeadragon 0.9.01 * * @fileOverview *

@@ -371,9 +371,18 @@ OpenSeadragon = window.OpenSeadragon || (function(){ * function when console is unavailable. * @static */ - $.console = window.console ? window.console : function(){}; - + var nullfunction = function( msg ){ + //document.location.hash = msg; + }; + $.console = window.console || { + log: nullfunction, + debug: nullfunction, + info: nullfunction, + warn: nullfunction, + error: nullfunction + }; + $.extend( $, { /** @@ -384,7 +393,7 @@ OpenSeadragon = window.OpenSeadragon || (function(){ * @returns {Element} The element with the given id, null, or the element itself. */ getElement: function( element ) { - if ( typeof ( element ) == "string") { + if ( typeof ( element ) == "string" ) { element = document.getElementById( element ); } return element; @@ -1353,7 +1362,7 @@ $.EventHandler.prototype = { //Store private properties in a scope sealed hash map var _this = this; - + /** * @private * @property {Boolean} tracking @@ -1373,24 +1382,28 @@ $.EventHandler.prototype = { * Position of last mouse down */ THIS[ this.hash ] = { - "mouseover": function( event ){ onMouseOver( _this, event ); }, - "mouseout": function( event ){ onMouseOut( _this, event ); }, - "mousedown": function( event ){ onMouseDown( _this, event ); }, - "mouseup": function( event ){ onMouseUp( _this, event ); }, - "click": function( event ){ onMouseClick( _this, event ); }, - "DOMMouseScroll": function( event ){ onMouseWheelSpin( _this, event ); }, - "mousewheel": function( event ){ onMouseWheelSpin( _this, event ); }, - "mouseupie": function( event ){ onMouseUpIE( _this, event ); }, - "mousemoveie": function( event ){ onMouseMoveIE( _this, event ); }, - "mouseupwindow": function( event ){ onMouseUpWindow( _this, event ); }, - "mousemove": function( event ){ onMouseMove( _this, event ); }, - tracking : false, - capturing : false, - buttonDown : false, - insideElement : false, - lastPoint : null, - lastMouseDownTime : null, - lastMouseDownPoint : null + "mouseover": function( event ){ onMouseOver( _this, event ); }, + "mouseout": function( event ){ onMouseOut( _this, event ); }, + "mousedown": function( event ){ onMouseDown( _this, event ); }, + "mouseup": function( event ){ onMouseUp( _this, event ); }, + "click": function( event ){ onMouseClick( _this, event ); }, + "DOMMouseScroll": function( event ){ onMouseWheelSpin( _this, event ); }, + "mousewheel": function( event ){ onMouseWheelSpin( _this, event ); }, + "mouseupie": function( event ){ onMouseUpIE( _this, event ); }, + "mousemoveie": function( event ){ onMouseMoveIE( _this, event ); }, + "mouseupwindow": function( event ){ onMouseUpWindow( _this, event ); }, + "mousemove": function( event ){ onMouseMove( _this, event ); }, + "touchstart": function( event ){ onTouchStart( _this, event ); }, + "touchmove": function( event ){ onTouchMove( _this, event ); }, + "touchend": function( event ){ onTouchEnd( _this, event ); }, + tracking: false, + capturing: false, + buttonDown: false, + insideElement: false, + lastPoint: null, + lastMouseDownTime: null, + lastMouseDownPoint: null, + lastPinchDelta: 0 }; }; @@ -1539,8 +1552,10 @@ $.EventHandler.prototype = { */ function startTracking( tracker ) { var events = [ - "mouseover", "mouseout", "mousedown", "mouseup", "click", - "DOMMouseScroll", "mousewheel" + "mouseover", "mouseout", "mousedown", "mouseup", + "click", + "DOMMouseScroll", "mousewheel", + "touchstart", "touchmove", "touchend" ], delegate = THIS[ tracker.hash ], event, @@ -1568,8 +1583,10 @@ $.EventHandler.prototype = { */ function stopTracking( tracker ) { var events = [ - "mouseover", "mouseout", "mousedown", "mouseup", "click", - "DOMMouseScroll", "mousewheel" + "mouseover", "mouseout", "mousedown", "mouseup", + "click", + "DOMMouseScroll", "mousewheel", + "touchstart", "touchmove", "touchend" ], delegate = THIS[ tracker.hash ], event, @@ -1693,6 +1710,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -1706,11 +1724,13 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner */ function onMouseOver( tracker, event ) { + var event = $.getEvent( event ), delegate = THIS[ tracker.hash ]; @@ -1755,6 +1775,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -1804,6 +1825,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -1853,6 +1875,38 @@ $.EventHandler.prototype = { } }; + /** + * @private + * @inner + */ + function onTouchStart( tracker, event ) { + var touchA, + touchB; + + window.location.hash = event.touches[ 0 ].target.tagName; + if( event.touches.length == 1 && + event.targetTouches.length == 1 && + event.changedTouches.length == 1 ){ + + THIS[ tracker.hash ].lastTouch = event.touches[ 0 ]; + onMouseOver( tracker, event.changedTouches[ 0 ] ); + onMouseDown( tracker, event.touches[ 0 ] ); + } + + if( event.touches.length == 2 ){ + + touchA = getMouseAbsolute( event.touches[ 0 ] ); + touchB = getMouseAbsolute( event.touches[ 1 ] ); + THIS[ tracker.hash ].lastPinchDelta = + Math.abs( touchA.x - touchB.x ) + + Math.abs( touchA.y - touchB.y ); + //$.console.debug("pinch start : "+THIS[ tracker.hash ].lastPinchDelta); + } + + event.preventDefault(); + }; + + /** * @private * @inner @@ -1894,6 +1948,29 @@ $.EventHandler.prototype = { } }; + + /** + * @private + * @inner + */ + function onTouchEnd( tracker, event ) { + + if( event.touches.length == 0 && + event.targetTouches.length == 0 && + event.changedTouches.length == 1 ){ + + THIS[ tracker.hash ].lastTouch = null; + onMouseUp( tracker, event.changedTouches[ 0 ] ); + onMouseOut( tracker, event.changedTouches[ 0 ] ); + } + if( event.touches.length + event.changedTouches.length == 2 ){ + THIS[ tracker.hash ].lastPinchDelta = null; + //$.console.debug("pinch end"); + } + event.preventDefault(); + }; + + /** * Only triggered once by the deepest element that initially received * the mouse down event. We want to make sure THIS event doesn't bubble. @@ -1930,6 +2007,7 @@ $.EventHandler.prototype = { $.stopEvent( event ); }; + /** * Only triggered in W3C browsers by elements within which the mouse was * initially pressed, since they are now listening to the window for @@ -1946,6 +2024,7 @@ $.EventHandler.prototype = { releaseMouse( tracker ); }; + /** * @private * @inner @@ -1956,6 +2035,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -1975,7 +2055,9 @@ $.EventHandler.prototype = { } else if (event.detail) { // Mozilla FireFox nDelta = -event.detail; } - + //The nDelta variable is gated to provide smooth z-index scrolling + //since the mouse wheel allows for substantial deltas meant for rapid + //y-index scrolling. nDelta = nDelta > 0 ? 1 : -1; if ( tracker.scrollHandler ) { @@ -1999,6 +2081,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -2036,6 +2119,7 @@ $.EventHandler.prototype = { } }; + /** * @private * @inner @@ -2069,6 +2153,50 @@ $.EventHandler.prototype = { } }; + + /** + * @private + * @inner + */ + function onTouchMove( tracker, event ) { + var touchA, + touchB, + pinchDelta; + + if( event.touches.length === 1 && + event.targetTouches.length === 1 && + event.changedTouches.length === 1 && + THIS[ tracker.hash ].lastTouch === event.touches[ 0 ]){ + + onMouseMove( tracker, event.touches[ 0 ] ); + + } else if ( event.touches.length === 2 ){ + + touchA = getMouseAbsolute( event.touches[ 0 ] ); + touchB = getMouseAbsolute( event.touches[ 1 ] ); + pinchDelta = + Math.abs( touchA.x - touchB.x ) + + Math.abs( touchA.y - touchB.y ); + + //TODO: make the 75px pinch threshold configurable + if( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ){ + //$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta); + + onMouseWheelSpin( tracker, { + shift: false, + pageX: ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2, + pageY: ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2, + detail:( + THIS[ tracker.hash ].lastPinchDelta > pinchDelta + ) ? 1 : -1 + }); + + THIS[ tracker.hash ].lastPinchDelta = pinchDelta; + } + } + event.preventDefault(); + }; + /** * Only triggered once by the deepest element that initially received * the mouse down event. Since no other element has captured the mouse, @@ -2390,6 +2518,7 @@ $.Viewer = function( options ) { this.bodyHeight = document.body.style.height; this.bodyOverflow = document.body.style.overflow; this.docOverflow = document.documentElement.style.overflow; + this.previousBody = []; this._fsBoundsDelta = new $.Point( 1, 1 ); this._prevContainerSize = null; @@ -2815,8 +2944,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { }, /** + * Toggle full page mode. * @function * @name OpenSeadragon.Viewer.prototype.setFullPage + * @param {Boolean} fullPage + * If true, enter full page mode. If false, exit full page mode. */ setFullPage: function( fullPage ) { @@ -2826,8 +2958,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle = this.container.style, canvasStyle = this.canvas.style, oldBounds, - newBounds; + newBounds, + nodes, + i; + //dont bother modifying the DOM if we are already in full page mode. if ( fullPage == this.isFullPage() ) { return; } @@ -2850,12 +2985,24 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle.position = "fixed"; containerStyle.zIndex = "99999999"; + //when entering full screen on the ipad it wasnt sufficient to leave + //the body intact as only only the top half of the screen would + //respond to touch events on the canvas, while the bottom half treated + //them as touch events on the document body. Thus we remove and store + //the bodies elements and replace them when we leave full screen. + this.previousBody = []; + nodes = document.body.childNodes.length; + for ( i = 0; i < nodes; i ++ ){ + this.previousBody.push( document.body.childNodes[ 0 ] ); + document.body.removeChild( document.body.childNodes[ 0 ] ); + } body.appendChild( this.container ); this._prevContainerSize = $.getWindowSize(); // mouse will be inside container now $.delegate( this, onContainerEnter )(); + } else { bodyStyle.overflow = this.bodyOverflow; @@ -2870,6 +3017,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle.position = "relative"; containerStyle.zIndex = ""; + document.body.removeChild( this.container ); + nodes = this.previousBody.length; + for ( i = 0; i < nodes; i++ ){ + document.body.appendChild( this.previousBody.shift() ); + } this.element.appendChild( this.container ); this._prevContainerSize = $.getElementSize( this.element ); diff --git a/src/mousetracker.js b/src/mousetracker.js index a0109a6f..ad7e8b9e 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -84,7 +84,7 @@ //Store private properties in a scope sealed hash map var _this = this; - + /** * @private * @property {Boolean} tracking @@ -104,24 +104,28 @@ * Position of last mouse down */ THIS[ this.hash ] = { - "mouseover": function( event ){ onMouseOver( _this, event ); }, - "mouseout": function( event ){ onMouseOut( _this, event ); }, - "mousedown": function( event ){ onMouseDown( _this, event ); }, - "mouseup": function( event ){ onMouseUp( _this, event ); }, - "click": function( event ){ onMouseClick( _this, event ); }, - "DOMMouseScroll": function( event ){ onMouseWheelSpin( _this, event ); }, - "mousewheel": function( event ){ onMouseWheelSpin( _this, event ); }, - "mouseupie": function( event ){ onMouseUpIE( _this, event ); }, - "mousemoveie": function( event ){ onMouseMoveIE( _this, event ); }, - "mouseupwindow": function( event ){ onMouseUpWindow( _this, event ); }, - "mousemove": function( event ){ onMouseMove( _this, event ); }, - tracking : false, - capturing : false, - buttonDown : false, - insideElement : false, - lastPoint : null, - lastMouseDownTime : null, - lastMouseDownPoint : null + "mouseover": function( event ){ onMouseOver( _this, event ); }, + "mouseout": function( event ){ onMouseOut( _this, event ); }, + "mousedown": function( event ){ onMouseDown( _this, event ); }, + "mouseup": function( event ){ onMouseUp( _this, event ); }, + "click": function( event ){ onMouseClick( _this, event ); }, + "DOMMouseScroll": function( event ){ onMouseWheelSpin( _this, event ); }, + "mousewheel": function( event ){ onMouseWheelSpin( _this, event ); }, + "mouseupie": function( event ){ onMouseUpIE( _this, event ); }, + "mousemoveie": function( event ){ onMouseMoveIE( _this, event ); }, + "mouseupwindow": function( event ){ onMouseUpWindow( _this, event ); }, + "mousemove": function( event ){ onMouseMove( _this, event ); }, + "touchstart": function( event ){ onTouchStart( _this, event ); }, + "touchmove": function( event ){ onTouchMove( _this, event ); }, + "touchend": function( event ){ onTouchEnd( _this, event ); }, + tracking: false, + capturing: false, + buttonDown: false, + insideElement: false, + lastPoint: null, + lastMouseDownTime: null, + lastMouseDownPoint: null, + lastPinchDelta: 0 }; }; @@ -270,8 +274,10 @@ */ function startTracking( tracker ) { var events = [ - "mouseover", "mouseout", "mousedown", "mouseup", "click", - "DOMMouseScroll", "mousewheel" + "mouseover", "mouseout", "mousedown", "mouseup", + "click", + "DOMMouseScroll", "mousewheel", + "touchstart", "touchmove", "touchend" ], delegate = THIS[ tracker.hash ], event, @@ -299,8 +305,10 @@ */ function stopTracking( tracker ) { var events = [ - "mouseover", "mouseout", "mousedown", "mouseup", "click", - "DOMMouseScroll", "mousewheel" + "mouseover", "mouseout", "mousedown", "mouseup", + "click", + "DOMMouseScroll", "mousewheel", + "touchstart", "touchmove", "touchend" ], delegate = THIS[ tracker.hash ], event, @@ -424,6 +432,7 @@ } }; + /** * @private * @inner @@ -437,11 +446,13 @@ } }; + /** * @private * @inner */ function onMouseOver( tracker, event ) { + var event = $.getEvent( event ), delegate = THIS[ tracker.hash ]; @@ -486,6 +497,7 @@ } }; + /** * @private * @inner @@ -535,6 +547,7 @@ } }; + /** * @private * @inner @@ -584,6 +597,38 @@ } }; + /** + * @private + * @inner + */ + function onTouchStart( tracker, event ) { + var touchA, + touchB; + + window.location.hash = event.touches[ 0 ].target.tagName; + if( event.touches.length == 1 && + event.targetTouches.length == 1 && + event.changedTouches.length == 1 ){ + + THIS[ tracker.hash ].lastTouch = event.touches[ 0 ]; + onMouseOver( tracker, event.changedTouches[ 0 ] ); + onMouseDown( tracker, event.touches[ 0 ] ); + } + + if( event.touches.length == 2 ){ + + touchA = getMouseAbsolute( event.touches[ 0 ] ); + touchB = getMouseAbsolute( event.touches[ 1 ] ); + THIS[ tracker.hash ].lastPinchDelta = + Math.abs( touchA.x - touchB.x ) + + Math.abs( touchA.y - touchB.y ); + //$.console.debug("pinch start : "+THIS[ tracker.hash ].lastPinchDelta); + } + + event.preventDefault(); + }; + + /** * @private * @inner @@ -625,6 +670,29 @@ } }; + + /** + * @private + * @inner + */ + function onTouchEnd( tracker, event ) { + + if( event.touches.length == 0 && + event.targetTouches.length == 0 && + event.changedTouches.length == 1 ){ + + THIS[ tracker.hash ].lastTouch = null; + onMouseUp( tracker, event.changedTouches[ 0 ] ); + onMouseOut( tracker, event.changedTouches[ 0 ] ); + } + if( event.touches.length + event.changedTouches.length == 2 ){ + THIS[ tracker.hash ].lastPinchDelta = null; + //$.console.debug("pinch end"); + } + event.preventDefault(); + }; + + /** * Only triggered once by the deepest element that initially received * the mouse down event. We want to make sure THIS event doesn't bubble. @@ -661,6 +729,7 @@ $.stopEvent( event ); }; + /** * Only triggered in W3C browsers by elements within which the mouse was * initially pressed, since they are now listening to the window for @@ -677,6 +746,7 @@ releaseMouse( tracker ); }; + /** * @private * @inner @@ -687,6 +757,7 @@ } }; + /** * @private * @inner @@ -706,7 +777,9 @@ } else if (event.detail) { // Mozilla FireFox nDelta = -event.detail; } - + //The nDelta variable is gated to provide smooth z-index scrolling + //since the mouse wheel allows for substantial deltas meant for rapid + //y-index scrolling. nDelta = nDelta > 0 ? 1 : -1; if ( tracker.scrollHandler ) { @@ -730,6 +803,7 @@ } }; + /** * @private * @inner @@ -767,6 +841,7 @@ } }; + /** * @private * @inner @@ -800,6 +875,50 @@ } }; + + /** + * @private + * @inner + */ + function onTouchMove( tracker, event ) { + var touchA, + touchB, + pinchDelta; + + if( event.touches.length === 1 && + event.targetTouches.length === 1 && + event.changedTouches.length === 1 && + THIS[ tracker.hash ].lastTouch === event.touches[ 0 ]){ + + onMouseMove( tracker, event.touches[ 0 ] ); + + } else if ( event.touches.length === 2 ){ + + touchA = getMouseAbsolute( event.touches[ 0 ] ); + touchB = getMouseAbsolute( event.touches[ 1 ] ); + pinchDelta = + Math.abs( touchA.x - touchB.x ) + + Math.abs( touchA.y - touchB.y ); + + //TODO: make the 75px pinch threshold configurable + if( Math.abs( THIS[ tracker.hash ].lastPinchDelta - pinchDelta ) > 75 ){ + //$.console.debug( "pinch delta : " + pinchDelta + " | previous : " + THIS[ tracker.hash ].lastPinchDelta); + + onMouseWheelSpin( tracker, { + shift: false, + pageX: ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2, + pageY: ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2, + detail:( + THIS[ tracker.hash ].lastPinchDelta > pinchDelta + ) ? 1 : -1 + }); + + THIS[ tracker.hash ].lastPinchDelta = pinchDelta; + } + } + event.preventDefault(); + }; + /** * Only triggered once by the deepest element that initially received * the mouse down event. Since no other element has captured the mouse, diff --git a/src/openseadragon.js b/src/openseadragon.js index 33faae46..b0ceb8ed 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -371,9 +371,18 @@ OpenSeadragon = window.OpenSeadragon || (function(){ * function when console is unavailable. * @static */ - $.console = window.console ? window.console : function(){}; - + var nullfunction = function( msg ){ + //document.location.hash = msg; + }; + $.console = window.console || { + log: nullfunction, + debug: nullfunction, + info: nullfunction, + warn: nullfunction, + error: nullfunction + }; + $.extend( $, { /** @@ -384,7 +393,7 @@ OpenSeadragon = window.OpenSeadragon || (function(){ * @returns {Element} The element with the given id, null, or the element itself. */ getElement: function( element ) { - if ( typeof ( element ) == "string") { + if ( typeof ( element ) == "string" ) { element = document.getElementById( element ); } return element; diff --git a/src/viewer.js b/src/viewer.js index e4ba27af..c56072bd 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -132,6 +132,7 @@ $.Viewer = function( options ) { this.bodyHeight = document.body.style.height; this.bodyOverflow = document.body.style.overflow; this.docOverflow = document.documentElement.style.overflow; + this.previousBody = []; this._fsBoundsDelta = new $.Point( 1, 1 ); this._prevContainerSize = null; @@ -557,8 +558,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { }, /** + * Toggle full page mode. * @function * @name OpenSeadragon.Viewer.prototype.setFullPage + * @param {Boolean} fullPage + * If true, enter full page mode. If false, exit full page mode. */ setFullPage: function( fullPage ) { @@ -568,8 +572,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle = this.container.style, canvasStyle = this.canvas.style, oldBounds, - newBounds; + newBounds, + nodes, + i; + //dont bother modifying the DOM if we are already in full page mode. if ( fullPage == this.isFullPage() ) { return; } @@ -592,12 +599,24 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle.position = "fixed"; containerStyle.zIndex = "99999999"; + //when entering full screen on the ipad it wasnt sufficient to leave + //the body intact as only only the top half of the screen would + //respond to touch events on the canvas, while the bottom half treated + //them as touch events on the document body. Thus we remove and store + //the bodies elements and replace them when we leave full screen. + this.previousBody = []; + nodes = document.body.childNodes.length; + for ( i = 0; i < nodes; i ++ ){ + this.previousBody.push( document.body.childNodes[ 0 ] ); + document.body.removeChild( document.body.childNodes[ 0 ] ); + } body.appendChild( this.container ); this._prevContainerSize = $.getWindowSize(); // mouse will be inside container now $.delegate( this, onContainerEnter )(); + } else { bodyStyle.overflow = this.bodyOverflow; @@ -612,6 +631,11 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, { containerStyle.position = "relative"; containerStyle.zIndex = ""; + document.body.removeChild( this.container ); + nodes = this.previousBody.length; + for ( i = 0; i < nodes; i++ ){ + document.body.appendChild( this.previousBody.shift() ); + } this.element.appendChild( this.container ); this._prevContainerSize = $.getElementSize( this.element );