Separate fullscreen and fullpage methods (#91)

Fix scroll issue.
Handle fullscreen denial by the browser
This commit is contained in:
Antoine Vandecreme 2013-11-02 21:01:04 -04:00
parent d853224c15
commit d70845d7eb
3 changed files with 201 additions and 168 deletions

View File

@ -72,6 +72,7 @@
requestFullScreen: function() {}, requestFullScreen: function() {},
cancelFullScreen: function() {}, cancelFullScreen: function() {},
fullScreenEventName: '', fullScreenEventName: '',
fullScreenErrorEventName: '',
prefix: '' prefix: ''
}, },
browserPrefixes = 'webkit moz o ms khtml'.split(' '); browserPrefixes = 'webkit moz o ms khtml'.split(' ');
@ -95,6 +96,7 @@
// update methods to do something useful // update methods to do something useful
if (fullScreenApi.supportsFullScreen) { if (fullScreenApi.supportsFullScreen) {
fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange'; fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
fullScreenApi.fullScreenErrorEventName = fullScreenApi.prefix + 'fullscreenerror';
fullScreenApi.isFullScreen = function() { fullScreenApi.isFullScreen = function() {
switch (this.prefix) { switch (this.prefix) {

View File

@ -643,134 +643,40 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
*/ */
setFullPage: function( fullPage ) { setFullPage: function( fullPage ) {
var body = document.body, var body = document.body,
bodyStyle = body.style, bodyStyle = body.style,
docStyle = document.documentElement.style, docStyle = document.documentElement.style,
_this = this, _this = this,
hash, hash,
nodes, nodes,
i; i;
//dont bother modifying the DOM if we are already in full page mode. //dont bother modifying the DOM if we are already in full page mode.
if ( fullPage == this.isFullPage() ) { if ( fullPage == this.isFullPage() ) {
return; return this;
} }
var registerRecenterAfterFullPageChange = function() { this.raiseEvent( 'pre-full-page', { fullPage: fullPage } );
if ( _this.viewport ) {
var oldBounds = _this.viewport.getBounds();
var oldCenter = _this.viewport.getCenter();
// This function recenter the image as it was before switching mode.
// TODO: better adjust width and height. The new width and height
// should depend on the image dimensions and on the dimensions
// of the viewport before and after switching mode.
var resizeAfterFullscreenHandler = function() {
_this.removeHandler( "animation-finish", resizeAfterFullscreenHandler );
var viewport = _this.viewport;
if ( !viewport ) {
return;
}
// We try to remove blanks as much as possible
var imageHeight = 1 / _this.source.aspectRatio;
var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1;
var newHeight = oldBounds.height <= imageHeight ?
oldBounds.height : imageHeight;
var newBounds = new $.Rect(
oldCenter.x - ( newWidth / 2.0 ),
oldCenter.y - ( newHeight / 2.0 ),
newWidth,
newHeight
);
viewport.fitBounds( newBounds, true );
};
_this.addHandler( "animation-finish", resizeAfterFullscreenHandler );
}
};
// On chrome, we need to restore the DOM after $.fullScreenEventName
// has been raised otherwise it won't restore the scroll position.
var exitFullPage = function() {
_this.raiseEvent( 'pre-full-page', { fullPage: false } );
registerRecenterAfterFullPageChange();
bodyStyle.margin = _this.bodyMargin;
docStyle.margin = _this.docMargin;
bodyStyle.padding = _this.bodyPadding;
docStyle.padding = _this.docPadding;
bodyStyle.width = _this.bodyWidth;
bodyStyle.height = _this.bodyHeight;
body.removeChild( _this.element );
nodes = _this.previousBody.length;
for ( i = 0; i < nodes; i++ ){
body.appendChild( _this.previousBody.shift() );
}
$.removeClass( _this.element, 'fullpage' );
THIS[ _this.hash ].prevElementParent.insertBefore(
_this.element,
THIS[ _this.hash ].prevNextSibling
);
//If we've got a toolbar, we need to enable the user to use css to
//reset it to its original state
if( _this.toolbar && _this.toolbar.element ){
body.removeChild( _this.toolbar.element );
//Make sure the user has some ability to style the toolbar based
//on the mode
$.removeClass( _this.toolbar.element, 'fullpage' );
_this.toolbar.parentNode.insertBefore(
_this.toolbar.element,
_this.toolbar.nextSibling
);
delete _this.toolbar.parentNode;
delete _this.toolbar.nextSibling;
}
_this.element.style.width = THIS[ _this.hash ].prevElementWidth;
_this.element.style.height = THIS[ _this.hash ].prevElementHeight;
$.setPageScroll(_this.pageScroll);
THIS[ _this.hash ].fullPage = false;
// mouse will likely be outside now
$.delegate( _this, onContainerExit )( {} );
_this.raiseEvent( 'full-page', { fullPage: false } );
};
if ( fullPage ) { if ( fullPage ) {
this.raiseEvent( 'pre-full-page', { fullPage: true } );
registerRecenterAfterFullPageChange(); this.elementSize = $.getElementSize( this.element );
this.pageScroll = $.getPageScroll();
this.pageScroll = $.getPageScroll(); this.bodyMargin = bodyStyle.margin;
this.docMargin = docStyle.margin;
bodyStyle.margin = "0";
docStyle.margin = "0";
this.bodyMargin = bodyStyle.margin; this.bodyPadding = bodyStyle.padding;
this.docMargin = docStyle.margin; this.docPadding = docStyle.padding;
bodyStyle.margin = "0"; bodyStyle.padding = "0";
docStyle.margin = "0"; docStyle.padding = "0";
this.bodyPadding = bodyStyle.padding; this.bodyWidth = bodyStyle.width;
this.docPadding = docStyle.padding; this.bodyHeight = bodyStyle.height;
bodyStyle.padding = "0"; bodyStyle.width = "100%";
docStyle.padding = "0"; bodyStyle.height = "100%";
this.bodyWidth = bodyStyle.width;
this.bodyHeight = bodyStyle.height;
bodyStyle.width = "100%";
bodyStyle.height = "100%";
//when entering full screen on the ipad it wasnt sufficient to leave //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 //the body intact as only only the top half of the screen would
@ -783,14 +689,14 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
THIS[ this.hash ].prevElementWidth = this.element.style.width; THIS[ this.hash ].prevElementWidth = this.element.style.width;
THIS[ this.hash ].prevElementHeight = this.element.style.height; THIS[ this.hash ].prevElementHeight = this.element.style.height;
nodes = body.childNodes.length; nodes = body.childNodes.length;
for ( i = 0; i < nodes; i ++ ){ for ( i = 0; i < nodes; i++ ) {
this.previousBody.push( body.childNodes[ 0 ] ); this.previousBody.push( body.childNodes[ 0 ] );
body.removeChild( body.childNodes[ 0 ] ); body.removeChild( body.childNodes[ 0 ] );
} }
//If we've got a toolbar, we need to enable the user to use css to //If we've got a toolbar, we need to enable the user to use css to
//preserve it in fullpage mode //preserve it in fullpage mode
if( this.toolbar && this.toolbar.element ){ if ( this.toolbar && this.toolbar.element ) {
//save a reference to the parent so we can put it back //save a reference to the parent so we can put it back
//in the long run we need a better strategy //in the long run we need a better strategy
this.toolbar.parentNode = this.toolbar.element.parentNode; this.toolbar.parentNode = this.toolbar.element.parentNode;
@ -805,42 +711,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
$.addClass( this.element, 'fullpage' ); $.addClass( this.element, 'fullpage' );
body.appendChild( this.element ); body.appendChild( this.element );
if( $.supportsFullScreen ){ this.element.style.height = $.getWindowSize().y + 'px';
THIS[ this.hash ].onfullscreenchange = function() { this.element.style.width = $.getWindowSize().x + 'px';
/*
fullscreenchange events don't include the new fullscreen status so we need to
retrieve the current status from the fullscreen API. See:
https://developer.mozilla.org/en-US/docs/Web/Reference/Events/fullscreenchange
*/
if( $.isFullScreen() ){ if ( this.toolbar && this.toolbar.element ) {
_this.setFullPage( true );
} else {
document.removeEventListener(
$.fullScreenEventName,
THIS[ _this.hash ].onfullscreenchange
);
exitFullPage();
}
};
$.requestFullScreen( document.body );
// The target of the event is always the document,
// but it is possible to retrieve the fullscreen element through the API
// Note that the API is still vendor-prefixed in browsers implementing it
document.addEventListener(
$.fullScreenEventName,
THIS[ this.hash ].onfullscreenchange
);
this.element.style.height = '100%';
this.element.style.width = '100%';
} else {
this.element.style.height = $.getWindowSize().y + 'px';
this.element.style.width = $.getWindowSize().x + 'px';
}
if( this.toolbar && this.toolbar.element ){
this.element.style.height = ( this.element.style.height = (
$.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y $.getElementSize( this.element ).y - $.getElementSize( this.toolbar.element ).y
) + 'px'; ) + 'px';
@ -851,21 +725,119 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
// mouse will be inside container now // mouse will be inside container now
$.delegate( this, onContainerEnter )( {} ); $.delegate( this, onContainerEnter )( {} );
this.raiseEvent( 'full-page', { fullPage: true } );
} else { } else {
if( $.supportsFullScreen ){ bodyStyle.margin = this.bodyMargin;
$.cancelFullScreen(); docStyle.margin = this.docMargin;
} else {
exitFullPage(); bodyStyle.padding = this.bodyPadding;
docStyle.padding = this.docPadding;
bodyStyle.width = this.bodyWidth;
bodyStyle.height = this.bodyHeight;
body.removeChild( this.element );
nodes = this.previousBody.length;
for ( i = 0; i < nodes; i++ ) {
body.appendChild( this.previousBody.shift() );
} }
$.removeClass( this.element, 'fullpage' );
THIS[ this.hash ].prevElementParent.insertBefore(
this.element,
THIS[ this.hash ].prevNextSibling
);
//If we've got a toolbar, we need to enable the user to use css to
//reset it to its original state
if ( this.toolbar && this.toolbar.element ) {
body.removeChild( this.toolbar.element );
//Make sure the user has some ability to style the toolbar based
//on the mode
$.removeClass( this.toolbar.element, 'fullpage' );
this.toolbar.parentNode.insertBefore(
this.toolbar.element,
this.toolbar.nextSibling
);
delete this.toolbar.parentNode;
delete this.toolbar.nextSibling;
}
this.element.style.width = THIS[ this.hash ].prevElementWidth;
this.element.style.height = THIS[ this.hash ].prevElementHeight;
// After exiting fullPage or fullScreen, it can take some time
// before the browser can actually set the scroll.
var restoreScroll = function() {
$.setPageScroll( _this.pageScroll );
var pageScroll = $.getPageScroll();
if ( pageScroll.x !== _this.pageScroll.x ||
pageScroll.y !== _this.pageScroll.y ) {
$.requestAnimationFrame( restoreScroll );
}
};
$.requestAnimationFrame( restoreScroll );
THIS[ this.hash ].fullPage = false;
// mouse will likely be outside now
$.delegate( this, onContainerExit )( { } );
} }
this.raiseEvent( 'full-page', { fullPage: fullPage } );
return this; return this;
}, },
/**
* Toggle full screen mode if supported. Toggle full page mode otherwise.
* @function
* @name OpenSeadragon.Viewer.prototype.setFullScreen
* @param {Boolean} fullScreen
* If true, enter full screen mode. If false, exit full screen mode.
* @return {OpenSeadragon.Viewer} Chainable.
*/
setFullScreen: function( fullScreen ) {
var _this = this;
if ( !$.supportsFullScreen ) {
return this.setFullPage( fullScreen );
}
if ( $.isFullScreen() === fullScreen ) {
return this;
}
this.raiseEvent( 'pre-full-screen', { fullScreen: fullScreen } );
if ( fullScreen ) {
this.setFullPage( true );
var onFullScreenChange = function() {
var isFullScreen = $.isFullScreen();
if ( !isFullScreen ) {
$.removeEvent( document, $.fullScreenEventName, onFullScreenChange );
$.removeEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
_this.setFullPage( false );
}
_this.raiseEvent( 'full-screen', { fullScreen: isFullScreen } );
};
$.addEvent( document, $.fullScreenEventName, onFullScreenChange );
$.addEvent( document, $.fullScreenErrorEventName, onFullScreenChange );
$.requestFullScreen( document.body );
this.element.style.width = '100%';
this.element.style.height = '100%';
} else {
$.cancelFullScreen();
}
return this;
},
/** /**
* @function * @function
@ -990,7 +962,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
beginZoomingOutHandler = $.delegate( this, beginZoomingOut ), beginZoomingOutHandler = $.delegate( this, beginZoomingOut ),
doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ), doSingleZoomOutHandler = $.delegate( this, doSingleZoomOut ),
onHomeHandler = $.delegate( this, onHome ), onHomeHandler = $.delegate( this, onHome ),
onFullPageHandler = $.delegate( this, onFullPage ), onFullPageHandler = $.delegate( this, onFullScreen ),
onFocusHandler = $.delegate( this, onFocus ), onFocusHandler = $.delegate( this, onFocus ),
onBlurHandler = $.delegate( this, onBlur ), onBlurHandler = $.delegate( this, onBlur ),
navImages = this.navImages, navImages = this.navImages,
@ -1628,8 +1600,11 @@ function updateOnce( viewer ) {
containerSize = _getSafeElemSize( viewer.container ); containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) { if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
// maintain image position // maintain image position
viewer.viewport.resize( containerSize, true ); var oldBounds = viewer.viewport.getBounds();
var oldCenter = viewer.viewport.getCenter();
resizeViewportAndRecenter(viewer, containerSize, oldBounds, oldCenter);
THIS[ viewer.hash ].prevContainerSize = containerSize; THIS[ viewer.hash ].prevContainerSize = containerSize;
THIS[ viewer.hash ].forceRedraw = true;
} }
animated = viewer.viewport.update(); animated = viewer.viewport.update();
@ -1670,7 +1645,30 @@ function updateOnce( viewer ) {
//viewer.profiler.endUpdate(); //viewer.profiler.endUpdate();
} }
// This function resize the viewport and recenter the image
// as it was before resizing.
// TODO: better adjust width and height. The new width and height
// should depend on the image dimensions and on the dimensions
// of the viewport before and after switching mode.
function resizeViewportAndRecenter( viewer, containerSize, oldBounds, oldCenter ) {
var viewport = viewer.viewport;
viewport.resize( containerSize, true );
// We try to remove blanks as much as possible
var imageHeight = 1 / viewer.source.aspectRatio;
var newWidth = oldBounds.width <= 1 ? oldBounds.width : 1;
var newHeight = oldBounds.height <= imageHeight ?
oldBounds.height : imageHeight;
var newBounds = new $.Rect(
oldCenter.x - ( newWidth / 2.0 ),
oldCenter.y - ( newHeight / 2.0 ),
newWidth,
newHeight
);
viewport.fitBounds( newBounds, true );
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Navigation Controls // Navigation Controls
@ -1760,10 +1758,15 @@ function onHome() {
} }
function onFullPage() { function onFullScreen() {
this.setFullPage( !this.isFullPage() ); if ( this.isFullPage() && !$.isFullScreen() ) {
// Is fullPage but not fullScreen
this.setFullPage( false );
} else {
this.setFullScreen( !this.isFullPage() );
}
// correct for no mouseout event on change // correct for no mouseout event on change
if( this.buttons ){ if ( this.buttons ) {
this.buttons.emulateExit(); this.buttons.emulateExit();
} }
this.fullPageButton.element.focus(); this.fullPageButton.element.focus();

View File

@ -174,7 +174,7 @@
}); });
// ---------- // ----------
asyncTest('Fullscreen', function() { asyncTest('FullPage', function() {
viewer.addHandler("open", function () { viewer.addHandler("open", function () {
ok(!viewer.isFullPage(), 'Started out not fullpage'); ok(!viewer.isFullPage(), 'Started out not fullpage');
ok(!$(viewer.element).hasClass('fullpage'), ok(!$(viewer.element).hasClass('fullpage'),
@ -210,11 +210,6 @@
viewer.addHandler("pre-full-page", checkExitingPreFullPage); viewer.addHandler("pre-full-page", checkExitingPreFullPage);
viewer.addHandler("full-page", checkExitingFullPage); viewer.addHandler("full-page", checkExitingFullPage);
// Workaround: for some reason inside tests, the fullscreen
// mode is never activated, so disable it so that we can
// continue the tests.
OpenSeadragon.supportsFullScreen = false;
viewer.setFullPage(false); viewer.setFullPage(false);
}; };
viewer.addHandler("pre-full-page", checkEnteringPreFullPage); viewer.addHandler("pre-full-page", checkEnteringPreFullPage);
@ -225,6 +220,39 @@
viewer.open('/test/data/testpattern.dzi'); viewer.open('/test/data/testpattern.dzi');
}); });
asyncTest('FullScreen', function() {
if (!OpenSeadragon.supportsFullScreen) {
expect(0);
start();
return;
}
viewer.addHandler("open", function () {
ok(!OpenSeadragon.isFullScreen(), 'Started out not fullscreen');
var checkEnteringPreFullScreen = function(event) {
viewer.removeHandler('pre-full-screen', checkEnteringPreFullScreen);
ok(event.fullScreen, 'Switching to fullscreen');
ok(!OpenSeadragon.isFullScreen(), 'Not yet fullscreen');
};
// The fullscreen mode is always denied during tests so we are
// exiting directly.
var checkExitingFullScreen = function(event) {
viewer.removeHandler('full-screen', checkExitingFullScreen);
ok(!event.fullScreen, 'Exiting fullscreen');
ok(!OpenSeadragon.isFullScreen(), 'Disabled fullscreen');
start();
};
viewer.addHandler("pre-full-screen", checkEnteringPreFullScreen);
viewer.addHandler("full-screen", checkExitingFullScreen);
viewer.setFullScreen(true);
});
viewer.open('/test/data/testpattern.dzi');
});
// ---------- // ----------
asyncTest('Close', function() { asyncTest('Close', function() {
viewer.addHandler("open", function () { viewer.addHandler("open", function () {