Permalink
Browse files

Initial implementation of individual callbacks

Also cleans up error handling quite a bit. Safari 5.1 is not currently working with error handling.
  • Loading branch information...
1 parent fdab08d commit fb564c872e1553ba13068f2970cebc1137e1164a @bdougherty committed Jan 21, 2013
Showing with 151 additions and 79 deletions.
  1. +40 −19 README.md
  2. +111 −60 src/bigscreen.js
View
59 README.md
@@ -10,18 +10,22 @@ BigScreen makes it easy to use full screen on your site or in your app. It smoot
## Download
-BigScreen is ~1 kb minified and gzipped. [Download it now](https://raw.github.com/bdougherty/BigScreen/master/dist/bigscreen.min.js).
+BigScreen is ~1 kb minified and gzipped. [Download it now](https://raw.github.com/bdougherty/BigScreen/master/bigscreen.min.js).
## Supported Browsers
* Chrome 15+
* Firefox 10+
* Safari 5.1+
+* Opera 12.1+
These browsers are also supported for video only:
* Safari 5.0
+* iOS 4.2+
+
+(See [caniuse](http://caniuse.com/#feat=fullscreen) for always up-to-date info)
## [Demo](http://brad.is/coding/BigScreen/)
@@ -49,59 +53,75 @@ var element = document.getElementById('target');
document.getElementById('button').addEventListener('click', function() {
if (BigScreen.enabled) {
- BigScreen.request(element);
- // You could also use .toggle(element)
+ BigScreen.request(element, onEnter, onExit, onError);
+ // You could also use .toggle(element, onEnter, onExit, onError)
}
else {
// fallback for browsers that don't support full screen
}
}, false);
```
-### Detecting full screen changes
+### Detecting full screen changes globally
```js
BigScreen.onenter = function() {
- // called when entering full screen
+ // called when the first element enters full screen
+}
+
+BigScreen.onchange = function() {
+ // called any time the full screen element changes
}
BigScreen.onexit = function() {
- // called when exiting full screen
+ // called when all elements have exited full screen
}
```
## Documentation
-### BigScreen.request(element)
+### BigScreen.request(element[, onEnter, onExit, onError])
-Request that an element go into full screen. If the element is `null` or `undefined`, the `documentElement` will be used instead.
+Request that an element go into full screen. If the element is falsy, the `documentElement` will be used instead.
You can only call this from a user-initiated event, otherwise the browser will deny the request. That means click, key, or touch events.
In addition, if your page is inside an `<iframe>` it will need to have the `allowfullscreen` (and `webkitallowfullscreen` and `mozallowfullscreen`) attribute set on the `<iframe>`.
-Finally, BigScreen will try to fall back to full screen for `<video>` if there is a child `<video>` in the element you pass and the browser supports it (see `BigScreen.videoEnabled)`). If BigScreen falls back, it will automatically load and play the video.
+Finally, BigScreen will try to fall back to full screen for `<video>` if there is a child `<video>` in the element you pass and the browser supports it (see `BigScreen.videoEnabled)`). If BigScreen falls back, it will automatically load the metadata of the video so the video can enter full screen.
+
+You can optionally pass callback functions for when this element enters or exits full screen, or if there is an error entering full screen. For all callbacks, the value of `this` will be set to the element that was requested. The actual element that entered full screen will be passed as the first parameter to `onEnter`.
### BigScreen.exit()
Will exit full screen. Note that if there are multiple elements in full screen, only the last one will exit full screen.
-### BigScreen.toggle(element)
+### BigScreen.toggle(element[, onEnter, onExit, onError])
Will request full screen if there is no element in full screen, otherwise it will exit full screen.
-### BigScreen.onenter()
+### BigScreen.onenter(element)
+
+Override to get notified when the first element goes into full screen. This will not fire if subsequent elements are added to the full screen stack (use `onchange` for that).
-Override to get notified when an element enters full screen. `BigScreen.element` will be set to the element that is entering full screen.
+### BigScreen.onchange(element)
+
+Override to get notified any time the full screen element changes. The element that is currently displaying in full screen will be passed as the first argument.
### BigScreen.onexit()
Override to get notified when fully exiting full screen (there are no more elements in full screen).
-### BigScreen.onerror()
+### BigScreen.onerror(element, reason)
+
+Override to get notified if there is an error sending an element into full screen. The possible values for reason are:
+
+* `not_supported`: full screen is not supported at all or for this element
+* `not_enabled`: request was made from a frame that does not have the allowfullscreen attribute, or the user has disabled full screen in their browser (but it is supported)
+* `not_allowed`: the request failed, probably because it was not called from a user-initiated event
-Override to get notified if there is an error sending an element into full screen. The value of `this` will be the element that generated the error.
+These are the same values passed to individual onError callbacks as well.
### BigScreen.element
@@ -113,22 +133,23 @@ A boolean that will tell you if it is possible to go into full screen. If your p
### BigScreen.videoEnabled(video)
-Safari 5.0 and iOS 4.2+ support putting `<video>` into full screen. `BigScreen.enabled` will report `false` in those browsers, but you can use this to check for `<video> `full screen support by passing the `<video>` itself, or an ancestor.
+Safari 5.0 and iOS 4.2+ support putting `<video>` into full screen. `BigScreen.enabled` will report `false` in those browsers, but you can use this to check for `<video>` full screen support by passing the `<video>` itself, or an ancestor.
-This function will report `false` if there is no child `<video>`, or if it is not possible to put a `<video>` in full screen. It will report `'maybe'` if the video's metadata has not been loaded, and `true` if it will be able to enter full screen.
+This function will report `false` if there is no child `<video>`, or if it is not possible to put a `<video>` in full screen. It will report `'maybe'` if it is possible, but the video's metadata has not been loaded, and `true` if it will be able to enter full screen.
-## Known Issues
-
-There is currently a bug in WebKit that causes the `webkitfullscreenchange` event to fire incorrectly when inside an `iframe`. BigScreen is able to work around the issue though. ([Chrome Bug](http://code.google.com/p/chromium/issues/detail?id=138368), Safari Bug: rdar://problem/11927884)
+## Known Fullscreen API Issues
Safari 6.0 does not work properly when putting multiple elements into full screen. [Open Radar bug report](http://openradar.appspot.com/radar?id=1878403).
+There is currently a bug in Safari (was in WebKit, but has been fixed and has been merged into Chrome as of 22) that causes the `webkitfullscreenchange` event to fire incorrectly when inside an `iframe`. BigScreen is able to work around the issue though. (Safari Bug: rdar://problem/11927884)
+
## Links
* [Using the Fullscreen API in web browsers](http://hacks.mozilla.org/2012/01/using-the-fullscreen-api-in-web-browsers/)
* [Using HTML5's Fullscreen API for Fun and Profit](http://sorcery.smugmug.com/2012/06/06/using-html5s-fullscreen-api-for-fun-and-profit/)
+* [screenfull.js](https://github.com/sindresorhus/screenfull.js)
* [Using full-screen mode - MDN](https://developer.mozilla.org/en/DOM/Using_full-screen_mode)
* [Fullscreen Specification - W3C](http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html)
View
171 src/bigscreen.js
@@ -50,9 +50,10 @@
if (map[i].request in testElement) {
fullscreen = map[i];
- // Double-check that all functions/events exist and if not, delete them
+ // Double-check that all functions/events exist and if not, delete them.
+ // Skip the events though, because Opera reports document.onfullscreenerror as undefined instead of null.
for (var item in fullscreen) {
- if (!('on' + fullscreen[item] in document) && !(fullscreen[item] in document) && !(fullscreen[item] in testElement)) {
+ if (item !== 'change' && item !== 'error' && !(fullscreen[item] in document) && !(fullscreen[item] in testElement)) {
delete fullscreen[item];
}
}
@@ -66,29 +67,8 @@
return fullscreen;
}());
- // From Underscore.js 1.3.3
- // http://underscorejs.org
- function debounce(func, wait, immediate) {
- var timeout;
- return function() {
- var context = this, args = arguments;
- var later = function() {
- timeout = null;
- if (!immediate) {
- func.apply(context, args);
- }
- };
- var callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) {
- func.apply(context, args);
- }
- };
- }
-
// Find a child <video> in the element passed.
- function getVideo(element) {
+ function _getVideo(element) {
var videoElement = null;
if (element.tagName === 'VIDEO') {
@@ -104,26 +84,30 @@
return videoElement;
}
- var lastFullscreenVideo = null;
+ var lastVideoElement = null;
var hasControls = null;
+ var emptyFunction = function() {};
+ var elements = [];
// Check to see if there is a <video> and if the video has webkitEnterFullscreen, try it.
// Metadata needs to be loaded for it to work, so load() if we need to.
function videoEnterFullscreen(element) {
- var videoElement = getVideo(element);
+ var videoElement = _getVideo(element);
if (videoElement && videoElement.webkitEnterFullscreen) {
try {
// We can tell when it enters and exits full screen on iOS using these events.
// Desktop Safari will fire the normal fullscreenchange event.
videoElement.addEventListener('webkitbeginfullscreen', function onBeginFullscreen(event) {
videoElement.removeEventListener('webkitbeginfullscreen', onBeginFullscreen, false);
+ bigscreen.onchange(videoElement);
callOnEnter(videoElement);
}, false);
videoElement.addEventListener('webkitendfullscreen', function onEndFullscreen(event) {
videoElement.removeEventListener('webkitendfullscreen', onEndFullscreen, false);
- callOnExit(videoElement);
+ bigscreen.onchange();
+ callOnExit();
}, false);
if (videoElement.readyState < videoElement.HAVE_METADATA) {
@@ -139,23 +123,23 @@
hasControls = !!videoElement.getAttribute('controls');
}
- lastFullscreenVideo = videoElement;
- videoElement.play();
+ lastVideoElement = videoElement;
+ // videoElement.play();
}
catch (err) {
- bigscreen.onerror.call(videoElement);
+ return callOnError('not_supported', element);
}
- return;
+ return true;
}
- bigscreen.onerror.call(element);
+ return callOnError('not_supported', element);
}
- // There is a bug in WebKit that will not fire a fullscreenchange event when the element exiting
+ // There is a bug in older WebKit that will not fire a fullscreenchange event when the element exiting
// is an iframe. This will listen for a window resize and fire exit if there is no current element.
- // Chrome bug: http://code.google.com/p/chromium/issues/detail?id=138368
- // Safari bug: rdar://11927884
+ // [Chrome bug](http://code.google.com/p/chromium/issues/detail?id=138368)
+ // [Safari bug](rdar://11927884)
function resizeExitHack() {
if (!bigscreen.element) {
callOnExit();
@@ -175,29 +159,82 @@
}
}
- var callOnEnter = debounce(function() {
- bigscreen.onenter.call(bigscreen);
- }, 500, true);
+ var callOnEnter = function(actualElement) {
+ // Return if the element entering has actually entered already. In older WebKit versions the
+ // browser will fire 2 webkitfullscreenchange events when entering full screen from inside an
+ // iframe. This is the result of the same bug as the resizeExitHack.
+ var lastElement = elements[elements.length - 1];
+ if ((actualElement === lastElement.element || actualElement === lastVideoElement) && lastElement.hasEntered) {
+ console.info('duplicate onEnter called');
+ return;
+ }
+
+ // Call the global enter handler if this is the first element
+ if (elements.length === 1) {
+ bigscreen.onenter(bigscreen.element);
+ }
+
+ var element = elements[elements.length - 1];
+ actualElement = actualElement || element.element;
+ element.enter.call(element.element, actualElement);
+ // Store that we've called the enter callback so we don't call it again
+ element.hasEntered = true;
+ };
- var callOnExit = debounce(function() {
+ var callOnExit = function() {
// Fix a bug present in some versions of WebKit that will show the native controls when
// exiting, even if they were not showing before.
- if (lastFullscreenVideo && !hasControls) {
- lastFullscreenVideo.setAttribute('controls', 'controls');
- lastFullscreenVideo.removeAttribute('controls');
+ if (lastVideoElement && !hasControls) {
+ lastVideoElement.setAttribute('controls', 'controls');
+ lastVideoElement.removeAttribute('controls');
}
- lastFullscreenVideo = null;
+ lastVideoElement = null;
hasControls = null;
- bigscreen.onexit.call(bigscreen);
- }, 500, true);
+ var element = elements.pop();
+
+ // Check to make sure that the element exists. This is to deal with when the function
+ // gets called a second time from the iframe resize hack.
+ if (element) {
+ element.exit.call(element.element);
+
+ // No more elements on the stack
+ if (!bigscreen.element) {
+ // Call the rest of the exit handlers in the stack
+ elements.forEach(function(element) {
+ element.exit.call(element.element);
+ });
+ elements = [];
+
+ // Call the global exit handler
+ bigscreen.onexit();
+ }
+ }
+ };
+
+ var callOnError = function(reason, element) {
+ var obj = elements.pop();
+ element = element || obj.element;
+
+ obj.error.call(element, reason);
+ bigscreen.onerror(element, reason);
+ };
var bigscreen = {
- request: function(element) {
+ request: function handleRequest(element, enterCallback, exitCallback, errorCallback) {
element = element || document.documentElement;
- // iOS only supports webkitEnterFullscreen on videos.
+ elements.push({
+ element: element,
+ enter: enterCallback || emptyFunction,
+ exit: exitCallback || emptyFunction,
+ error: errorCallback || emptyFunction
+ });
+
+ // iOS only supports webkitEnterFullscreen on videos, so try that.
+ // Browsers that don't support full screen at all will also go through this,
+ // but they will fire an error.
if (fn.request === undefined) {
return videoEnterFullscreen(element);
}
@@ -237,31 +274,32 @@
}
}
catch (err) {
- bigscreen.onerror.call(element);
+ callOnError('not_enabled', element);
}
},
- exit: function() {
+ exit: function handleExit() {
removeWindowResizeHack(); // remove here if exit is called manually, so two onexit events are not fired
document[fn.exit]();
},
- toggle: function(element) {
+ toggle: function handleToggle(element, enterCallback, exitCallback, errorCallback) {
if (bigscreen.element) {
bigscreen.exit();
}
else {
- bigscreen.request(element);
+ bigscreen.request(element, enterCallback, exitCallback, errorCallback);
}
},
// Mobile Safari and earlier versions of desktop Safari support sending a <video> into full screen.
// Checks can't be performed to verify full screen capabilities unless we know about that element,
// and it has loaded its metadata.
- videoEnabled: function(element) {
+ videoEnabled: function handleVideoEnabled(element) {
if (bigscreen.enabled) {
return true;
}
- var video = getVideo(element);
+ element = element || document.documentElement;
+ var video = _getVideo(element);
if (!video || video.webkitSupportsFullscreen === undefined) {
return false;
@@ -272,6 +310,7 @@
onenter: function() {},
onexit: function() {},
+ onchange: function() {},
onerror: function() {}
};
@@ -280,8 +319,8 @@
element: {
enumerable: true,
get: function() {
- if (lastFullscreenVideo && lastFullscreenVideo.webkitDisplayingFullscreen) {
- return lastFullscreenVideo;
+ if (lastVideoElement && lastVideoElement.webkitDisplayingFullscreen) {
+ return lastVideoElement;
}
return document[fn.element] || null;
@@ -307,10 +346,20 @@
}
if (fn.change) {
- document.addEventListener(fn.change, function(event) {
+ document.addEventListener(fn.change, function onFullscreenChange(event) {
+ bigscreen.onchange(bigscreen.element);
+
if (bigscreen.element) {
- callOnEnter();
- addWindowResizeHack();
+ // This is actually an exit if the element that is in full screen is the
+ // previous element in the stack
+ var previousElement = elements[elements.length - 2];
+ if (previousElement && previousElement.element === bigscreen.element) {
+ callOnExit();
+ }
+ else {
+ callOnEnter(bigscreen.element);
+ addWindowResizeHack();
+ }
}
else {
callOnExit();
@@ -319,8 +368,10 @@
}
if (fn.error) {
- document.addEventListener(fn.error, function(event) {
- bigscreen.onerror.call(event.target);
+ document.addEventListener(fn.error, function onFullscreenError(event) {
+ if (elements.length > 0) {
+ callOnError('not_allowed');
+ }
}, false);
}

0 comments on commit fb564c8

Please sign in to comment.