bigscreen.js | |
---|---|
A library to make it easier to use the JavaScript Fullscreen API. | (function(window, document, iframe) {
'use strict';
var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
var fn = (function() {
var map = [ |
Properties/events/functions as defined in the spec. Currently implemented in Opera. | {
request: 'requestFullscreen',
exit: 'exitFullscreen',
enabled: 'fullscreenEnabled',
element: 'fullscreenElement',
change: 'fullscreenchange',
error: 'fullscreenerror'
}, |
Properties/events/functions in newer versions of WebKit (Chrome and Safari 6). _Note the lowercase 's' in Fulscreen (to match the spec). | {
request: 'webkitRequestFullscreen',
exit: 'webkitExitFullscreen',
enabled: 'webkitFullscreenEnabled',
element: 'webkitFullscreenElement',
change: 'webkitfullscreenchange',
error: 'webkitfullscreenerror'
}, |
Properties/events/functions for older WebKit (Safari 5.1). Note the capital 'S' in FullScreen. | {
request: 'webkitRequestFullScreen',
exit: 'webkitCancelFullScreen',
element: 'webkitCurrentFullScreenElement',
change: 'webkitfullscreenchange',
error: 'webkitfullscreenerror'
}, |
Properties/events/functions for Firefox 10+. | {
request: 'mozRequestFullScreen',
exit: 'mozCancelFullScreen',
enabled: 'mozFullScreenEnabled',
element: 'mozFullScreenElement',
change: 'mozfullscreenchange',
error: 'mozfullscreenerror'
}
];
var fullscreen = false;
var testElement = document.createElement('video'); |
Loop over each set of properties/events/functions to find the set that has a working requestFullscreen function. Double-check that the rest of the functions and properties actually do exist, and if they don't, delete them. Skip checking events events though, because Opera reports document.onfullscreenerror as undefined instead of null. | for (var i = 0; i < map.length; i++) {
if (map[i].request in testElement) {
fullscreen = map[i];
for (var item in fullscreen) {
if (item !== 'change' && item !== 'error' && !(fullscreen[item] in document) && !(fullscreen[item] in testElement)) {
delete fullscreen[item];
}
}
break;
}
}
testElement = null;
return fullscreen;
}()); |
Find a child video in the element passed. | function _getVideo(element) {
var videoElement = null;
if (element.tagName === 'VIDEO') {
videoElement = element;
}
else {
var videos = element.getElementsByTagName('video');
if (videos[0]) {
videoElement = videos[0];
}
}
return videoElement;
}
var lastVideoElement = null;
var hasControls = null;
var emptyFunction = function() {};
var elements = []; |
Attempt to put a child video into full screen using webkitEnterFullscreen. The metadata must be loaded in order for it to work, so load it automatically if it isn't already. | function videoEnterFullscreen(element) {
var videoElement = _getVideo(element);
if (videoElement && videoElement.webkitEnterFullscreen) {
try { |
We can tell when the video enters and exits full screen on iOS using the | 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);
bigscreen.onchange();
callOnExit();
}, false);
if (videoElement.readyState < videoElement.HAVE_METADATA) {
videoElement.addEventListener('loadedmetadata', function onMetadataLoaded() {
videoElement.removeEventListener('loadedmetadata', onMetadataLoaded, false);
videoElement.webkitEnterFullscreen();
hasControls = !!videoElement.getAttribute('controls');
}, false);
videoElement.load();
}
else {
videoElement.webkitEnterFullscreen();
hasControls = !!videoElement.getAttribute('controls');
}
lastVideoElement = videoElement;
}
catch (err) {
return callOnError('not_supported', element);
}
return true;
}
return callOnError(fn.request === undefined ? 'not_supported' : 'not_enabled', element);
} |
There is a bug in older versions of WebKit that will fire | function resizeExitHack() {
if (!bigscreen.element) {
callOnExit();
removeWindowResizeHack();
}
} |
Add the listener for the resize hack, but only when inside an iframe in WebKit. | function addWindowResizeHack() {
if (iframe && fn.change === 'webkitfullscreenchange') {
window.addEventListener('resize', resizeExitHack, false);
}
}
function removeWindowResizeHack() {
if (iframe && fn.change === 'webkitfullscreenchange') {
window.removeEventListener('resize', resizeExitHack, false);
}
}
var callOnEnter = function(actualElement) { |
Return if the element entering has actually entered already. In older WebKit versions the
browser will fire 2 | var lastElement = elements[elements.length - 1];
if ((actualElement === lastElement.element || actualElement === lastVideoElement) && lastElement.hasEntered) {
return;
} |
Call the global enter handler only if this is the first element. | if (elements.length === 1) {
bigscreen.onenter(bigscreen.element);
} |
Call the stored callback for the request call and record that we did so we don't do it again if there is a duplicate call (see above). | lastElement.enter.call(lastElement.element, actualElement || lastElement.element);
lastElement.hasEntered = true;
};
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 (lastVideoElement && !hasControls) {
lastVideoElement.setAttribute('controls', 'controls');
lastVideoElement.removeAttribute('controls');
}
lastVideoElement = null;
hasControls = null;
var element = elements.pop(); |
Check to make sure that the element exists. This function will get called a second time from the iframe resize hack. | if (element) {
element.exit.call(element.element); |
When the browser has fully exited full screen, make sure to loop through and call the rest of the callbacks and then the global exit. | if (!bigscreen.element) {
elements.forEach(function(element) {
element.exit.call(element.element);
});
elements = [];
bigscreen.onexit();
}
}
}; |
Make a callback to the error handlers and clear the element from the stack when an error occurs. | var callOnError = function(reason, element) {
if (elements.length > 0) {
var obj = elements.pop();
element = element || obj.element;
obj.error.call(element, reason);
bigscreen.onerror(element, reason);
}
};
var bigscreen = { |
requestThe meat of BigScreen is here. Run through a bunch of checks to try to get something into full screen that's a child of the element passed in. | request: function(element, enterCallback, exitCallback, errorCallback) {
element = element || document.documentElement;
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);
} |
| if (iframe && document[fn.enabled] === false) {
return videoEnterFullscreen(element);
} |
If we're in an iframe, it needs to have the | if (iframe && fn.enabled === undefined) {
fn.enabled = 'webkitFullscreenEnabled';
element[fn.request]();
setTimeout(function() { |
It didn't work, so set | if (!document[fn.element]) {
document[fn.enabled] = false;
videoEnterFullscreen(element);
} |
It worked! set | else {
document[fn.enabled] = true;
}
}, 250);
return;
}
try { |
Safari 5.1 doesn't actually support asking for keyboard support,
so don't try it. The alternative is to add another | if (/5\.1[\.\d]* Safari/.test(navigator.userAgent)) {
element[fn.request]();
}
else {
element[fn.request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
} |
If there's no element after 100ms, it didn't work. This check is for Safari 5.1
which fails to fire a | setTimeout(function() {
if (!document[fn.element]) {
callOnError(iframe ? 'not_enabled' : 'not_allowed', element);
}
}, 100);
}
catch (err) {
callOnError('not_enabled', element);
}
}, |
exitPops the last full screen element off the stack. | exit: function() { |
Remove the resize hack here if exit is called manually, so it doesn't fire twice. | removeWindowResizeHack();
document[fn.exit]();
}, |
toggleShortcut function if you only plan on putting one element into full screen. | toggle: function(element, enterCallback, exitCallback, errorCallback) {
if (bigscreen.element) {
bigscreen.exit();
}
else {
bigscreen.request(element, enterCallback, exitCallback, errorCallback);
}
}, |
videoEnabledMobile Safari and earlier versions of desktop Safari support sending a | videoEnabled: function(element) {
if (bigscreen.enabled) {
return true;
}
element = element || document.documentElement;
var video = _getVideo(element);
if (!video || video.webkitSupportsFullscreen === undefined) {
return false;
}
return video.readyState < video.HAVE_METADATA ? 'maybe' : video.webkitSupportsFullscreen;
}, |
onenter, onexit, onchange, onerrorPopulate the global handlers with empty functions. | onenter: emptyFunction,
onexit: emptyFunction,
onchange: emptyFunction,
onerror: emptyFunction
}; |
Define the two properties | try {
Object.defineProperties(bigscreen, { |
elementGet the current element that is displaying full screen. | element: {
enumerable: true,
get: function() {
if (lastVideoElement && lastVideoElement.webkitDisplayingFullscreen) {
return lastVideoElement;
}
return document[fn.element] || null;
}
}, |
enabledCheck if element full screen is supported. | enabled: {
enumerable: true,
get: function() { |
Safari 5.1 supports full screen, but doesn't have a fullScreenEnabled property,
but it should work if not in an iframe. If it doesn't work when tried for the
first time, we'll set this to | if (fn.exit === 'webkitCancelFullScreen' && !iframe) {
return true;
}
return document[fn.enabled] || false;
}
}
});
} |
If the define fails, set them to | catch (err) {
bigscreen.element = null;
bigscreen.enabled = false;
} |
If there is a valid | if (fn.change) {
document.addEventListener(fn.change, function onFullscreenChange(event) {
bigscreen.onchange(bigscreen.element);
if (bigscreen.element) { |
This should be treated an exit if the element that is in full screen is the previous element in our stack. | var previousElement = elements[elements.length - 2];
if (previousElement && previousElement.element === bigscreen.element) {
callOnExit();
}
else {
callOnEnter(bigscreen.element);
addWindowResizeHack();
}
}
else {
callOnExit();
}
}, false);
} |
If there is a valid | if (fn.error) {
document.addEventListener(fn.error, function onFullscreenError(event) {
callOnError('not_allowed');
}, false);
} |
Externalize the BigScreen object. Use array notation to play nicer with Closure Compiler. | window['BigScreen'] = bigscreen;
}(window, document, self !== top));
|