Created
June 6, 2012 15:01
-
-
Save nikcorg/2882405 to your computer and use it in GitHub Desktop.
Lightbox script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Lightbox script I made back in the day when I wasn't happy with the ones I could find | |
* already out there. Also my first exercise using the Module-pattern, copied from jQuery, | |
* which is the reason for the overtly complex and stupid structure. | |
* | |
* Contains bugs and weird solutions. (Some of which I just had to fix, in case someone | |
* actually sees this code.) | |
* | |
* This source code is stored here more as a curiosity than for real world usage. | |
* | |
* Example usage: | |
* | |
* LightBoxer({ | |
* dimmerColor: '#000', | |
* requireRelAttribute: true, | |
* throbberSrc: '/assets/img/lightbox/throbber.gif' | |
* }).autoSetup(); | |
*/ | |
(function (window) { | |
"use strict"; | |
var LightBoxerSettings = { | |
throbberSrc: 'throbber.gif', | |
dimmerColor: 'rgba(51, 51, 51, 0.95)', | |
imageMargin: 10, | |
imagePadding: 4, | |
zOrder: { | |
root : 99999, | |
dimmer : 100, | |
throbber: 110, | |
image : 120, | |
controls: 130 | |
}, | |
autoRun: false, | |
requireRelAttribute: true | |
}, | |
has = Object.prototype.hasOwnProperty, | |
LightBoxer = function (options) { | |
var key; | |
options = options || {}; | |
for (key in options) { | |
if (has.call(options, key) && has.call(LightBoxerSettings, key)) { | |
LightBoxerSettings[key] = options[key]; | |
} | |
} | |
return new LightBoxer.fn.init(); | |
}, | |
LightBoxerDimmer = function (LB) { | |
this.init(LB); | |
}, | |
LightBoxerImageContainer = function (LB) { | |
this.init(LB); | |
}, | |
LightBoxerControls = function (LB) { | |
this.init(LB); | |
}, | |
LightBoxerThrobber = function (LB) { | |
this.init(LB); | |
}, | |
LightBoxerUtils = { }, | |
domReady, | |
document = window.document; | |
LightBoxer.fn = LightBoxer.prototype = { | |
init: function () { | |
this.rootNode = document.createElement('div'); | |
this.rootNode.id = 'LightBoxer'; | |
LightBoxerUtils.applyStyles(this.rootNode, { | |
cursor: 'pointer', | |
height: '100%', | |
left: '0', | |
position: 'fixed', | |
top: '0', | |
width: '100%', | |
zIndex: LightBoxerSettings.zOrder.root | |
}); | |
this.dimmer = new LightBoxerDimmer(this); | |
this.imagecontainer = new LightBoxerImageContainer(this); | |
this.throbber = new LightBoxerThrobber(this); | |
this.controls = new LightBoxerControls(this); | |
this.nodes = []; | |
this.nodecursor = -1; | |
return this; | |
}, | |
close: function () { | |
this.dimmer.hide(); | |
this.imagecontainer.hide(); | |
this.throbber.hide(); | |
this.controls.hide(); | |
if (this.rootNode.parentNode) { | |
this.rootNode.parentNode.removeChild(this.rootNode); | |
} | |
}, | |
cursor: function (step) { | |
if (this.nodecursor + step >= 0 && this.nodecursor + step < this.nodes.length) { | |
this.nodecursor += step; | |
return true; | |
} | |
return false; | |
}, | |
displayNext: function () { | |
return this.cursor(+1) && | |
this.displayImage(this.nodes[this.nodecursor].href, this.extractTitle(this.nodes[this.nodecursor])); | |
}, | |
displayPrevious: function () { | |
return this.cursor(-1) && | |
this.displayImage(this.nodes[this.nodecursor].href, this.extractTitle(this.nodes[this.nodecursor])); | |
}, | |
hasNext: function () { | |
return this.nodes.length > 0 && this.nodecursor < this.nodes.length - 1; | |
}, | |
hasPrevious: function () { | |
return this.nodes.length > 0 && this.nodecursor > 0; | |
}, | |
displayImage: function (url, title) { | |
var i, l; | |
// Find image in nodelist to set cursor in correct | |
// position for the next/prev navigation | |
this.nodecursor = -1; | |
for (i = 0, l = this.nodes.length; i < l; i += 1) { | |
if (this.nodes[i].href === url) { | |
this.nodecursor = i; | |
break; | |
} | |
} | |
this.throbber.show(); | |
this.dimmer.show(); | |
this.controls.show(); | |
this.imagecontainer.loadImage(url, LightBoxerUtils.delegate(this, this.imageLoadedHandler)); | |
this.displayTitle(title); | |
document.body.appendChild(this.rootNode); | |
}, | |
displayTitle: function (title) { | |
this.controls.addTitle(title); | |
}, | |
imageLoadedHandler: function () { | |
this.throbber.hide(); | |
this.imagecontainer.scaleAndCenter(); | |
this.imagecontainer.show(); | |
}, | |
extractTitle: function (node) { | |
var imgs, | |
ret = null; | |
if (node.title && node.title.length > 0) { | |
ret = node.title; | |
} else { | |
imgs = node.getElementsByTagName('img'); | |
if (imgs.length === 1 && imgs[0].title && imgs[0].title.length > 0) { | |
ret = imgs[0].title; | |
} else if (imgs.length === 1 && imgs[0].alt && imgs[0].alt.length > 0) { | |
ret = imgs[0].alt; | |
} | |
} | |
return ret; | |
}, | |
addLBNode: function (node) { | |
this.nodes.push(node); | |
}, | |
autoSetup: function () { | |
var LB = this; | |
domReady.subscribe(function () { | |
var links = document.getElementsByTagName('a'), | |
i, l, node, | |
onClickHandler = function (e) { | |
var imageUrl = this.href, | |
imageTitle = LB.extractTitle(this); | |
e.preventDefault(); | |
LB.displayImage(imageUrl, imageTitle); | |
}; | |
for (i = 0, l = links.length; i < l; i += 1) { | |
node = links[i]; | |
if ((! LightBoxerSettings.requireRelAttribute || node.rel === 'lightbox') && | |
(/jpg|peg|gif|png/).test(node.href.substr(-3)) | |
) { | |
LB.addLBNode(node); | |
LightBoxerUtils.bindEvent(node, 'click', LightBoxerUtils.delegate(node, onClickHandler)); | |
} | |
} | |
}); | |
} | |
}; | |
LightBoxer.fn.init.prototype = LightBoxer.prototype; | |
LightBoxerControls.prototype = { | |
node: null, | |
LB: null, | |
buttons: { | |
close: null, | |
next: null, | |
previous: null | |
}, | |
keyboard: { | |
handler: null, | |
left: 37, | |
right: 39, | |
escape: 27 | |
}, | |
resizeBound: null, | |
init: function (LB) { | |
var kbdListener; | |
this.LB = LB; | |
this.node = this.createNode(); | |
this.resizeBound = LightBoxerUtils.delegate(this, this.resizeHandler); | |
kbdListener = function (event) { | |
event = event || window.event; | |
switch (event.keyCode) { | |
case this.keyboard.left: | |
this.previousHandler(); | |
break; | |
case this.keyboard.right: | |
this.nextHandler(); | |
break; | |
case this.keyboard.escape: | |
this.LB.close(); | |
break; | |
} | |
}; | |
this.keyboard.handler = LightBoxerUtils.delegate(this, kbdListener); | |
}, | |
show: function () { | |
if (! this.node.parentNode) { | |
this.LB.rootNode.appendChild(this.node); | |
this.resizeHandler(); | |
this.buttons.previous.disabled = ! this.LB.hasPrevious(); | |
this.buttons.next.disabled = ! this.LB.hasNext(); | |
LightBoxerUtils.bindEvent(window, 'keydown', this.keyboard.handler); | |
LightBoxerUtils.bindEvent(window, 'resize', this.resizeBound); | |
} | |
}, | |
hide: function () { | |
if (this.node.parentNode) { | |
this.LB.rootNode.removeChild(this.node); | |
this.removeTitle(); | |
LightBoxerUtils.unbindEvent(window, 'keydown', this.keyboard.handler); | |
LightBoxerUtils.unbindEvent(window, 'resize', this.resizeBound); | |
} | |
}, | |
addTitle: function (value) { | |
var title; | |
this.removeTitle(); | |
if (value) { | |
title = document.createElement('p'); | |
title.innerHTML = value || 'Untitled'; | |
LightBoxerUtils.applyStyles(title, { | |
cssFloat: 'left', | |
styleFloat: 'left', | |
display: 'inline', | |
margin: '0' | |
}); | |
this.node.appendChild(title); | |
} | |
}, | |
removeTitle: function () { | |
var titles = this.node.getElementsByTagName('p'), | |
i, l; | |
if (titles.length > 0) { | |
for (i = 0, l = titles.length; i < l; i += 1) { | |
titles[i].parentNode.removeChild(titles[i]); | |
} | |
} | |
}, | |
nextHandler: function () { | |
this.LB.displayNext.apply(this.LB); | |
this.buttonStateHandler(); | |
}, | |
previousHandler: function () { | |
this.LB.displayPrevious.apply(this.LB); | |
this.buttonStateHandler(); | |
}, | |
buttonStateHandler: function () { | |
this.buttons.next.disabled = ! this.LB.hasNext(); | |
this.buttons.previous.disabled = ! this.LB.hasPrevious(); | |
}, | |
resizeHandler: function () { | |
LightBoxerUtils.applyStyles(this.node, { | |
width: (LightBoxerUtils.getViewportDimensions().width - 8) + 'px' | |
}); | |
}, | |
createNode: function () { | |
var buttonContainer, | |
controls; | |
try { | |
return LightBoxerUtils.getElementOrFail('LightBoxerControls'); | |
} catch (e) { | |
} | |
this.buttons.close = document.createElement('button'); | |
this.buttons.close.innerHTML = 'Close'; | |
this.buttons.next = document.createElement('button'); | |
this.buttons.next.innerHTML = 'Next'; | |
this.buttons.previous = document.createElement('button'); | |
this.buttons.previous.innerHTML = 'Previous'; | |
LightBoxerUtils.applyStyles(this.buttons.close, { | |
backgroundColor: '#f00', | |
color: '#fff', | |
marginLeft: '30px' | |
}); | |
LightBoxerUtils.bindEvent(this.buttons.close, 'click', LightBoxerUtils.delegate(this.LB, this.LB.close)); | |
LightBoxerUtils.bindEvent(this.buttons.next, 'click', LightBoxerUtils.delegate(this, this.nextHandler)); | |
LightBoxerUtils.bindEvent(this.buttons.previous, 'click', LightBoxerUtils.delegate(this, this.previousHandler)); | |
buttonContainer = document.createElement('div'); | |
buttonContainer.appendChild(this.buttons.previous); | |
buttonContainer.appendChild(this.buttons.next); | |
buttonContainer.appendChild(this.buttons.close); | |
controls = document.createElement('div'); | |
controls.id = 'LightBoxerControls'; | |
controls.appendChild(buttonContainer); | |
LightBoxerUtils.applyStyles(buttonContainer, { | |
cssFloat: 'right', | |
styleFloat: 'right', | |
display: 'inline' | |
}); | |
LightBoxerUtils.applyStyles(controls, { | |
backgroundColor: '#fff', | |
borderBottom: '2px solid #888', | |
color: '#333', | |
cursor: 'default', | |
left: '0', | |
padding: '4px', | |
position: 'absolute', | |
top: '0', | |
zIndex: LightBoxerSettings.zOrder.controls | |
}); | |
LightBoxerUtils.bindEvent(window, 'resize', LightBoxerUtils.delegate(this, this.resizeHandler)); | |
return controls; | |
} | |
}; | |
LightBoxerDimmer.prototype = { | |
node: null, | |
LB: null, | |
init: function (LB) { | |
this.LB = LB; | |
this.node = this.createNode(); | |
LightBoxerUtils.bindEvent(this.node, 'click', LightBoxerUtils.delegate(LB, LB.close)); | |
}, | |
show: function () { | |
if (! this.node.parentNode) { | |
this.LB.rootNode.appendChild(this.node); | |
} | |
}, | |
hide: function () { | |
if (this.node.parentNode) { | |
this.LB.rootNode.removeChild(this.node); | |
} | |
}, | |
createNode: function () { | |
var dimmer; | |
try { | |
return LightBoxerUtils.getElementOrFail('LightBoxerDimmer'); | |
} catch (e) { | |
} | |
dimmer = document.createElement('div'); | |
dimmer.id = 'LightBoxerDimmer'; | |
LightBoxerUtils.applyStyles(dimmer, { | |
backgroundColor: LightBoxerSettings.dimmerColor, | |
height: '100%', | |
left: '0', | |
position: 'absolute', | |
top: '0', | |
width: '100%', | |
zIndex: LightBoxerSettings.zOrder.dimmer | |
}); | |
return dimmer; | |
} | |
}; | |
LightBoxerImageContainer.prototype = { | |
node: null, | |
LB: null, | |
init: function (LB) { | |
this.LB = LB; | |
this.node = this.createNode(); | |
LightBoxerUtils.bindEvent(window, 'resize', LightBoxerUtils.delegate(this, this.scaleAndCenter)); | |
LightBoxerUtils.bindEvent(this.node, 'click', LightBoxerUtils.delegate(LB, LB.close)); | |
}, | |
show: function () { | |
if (! this.node.parentNode) { | |
this.LB.rootNode.appendChild(this.node); | |
} | |
}, | |
hide: function () { | |
if (this.node.parentNode) { | |
this.LB.rootNode.removeChild(this.node); | |
} | |
}, | |
loadImage: function (url, callback) { | |
var img = document.createElement('img'); | |
img.src = url; | |
LightBoxerUtils.bindEvent(img, 'load', function () { | |
this.originalDimensions = { | |
height: this.height, | |
width: this.width | |
}; | |
callback.call(); | |
} | |
); | |
this.node.innerHTML = ''; | |
this.node.appendChild(img); | |
this.hide(); | |
}, | |
scaleAndCenter: function () { | |
var img = this.node.getElementsByTagName('img'), | |
dims = LightBoxerUtils.getViewportDimensions(), | |
yAdj = 0, | |
ratio, imageDims, xPos, yPos; | |
// Reduce the controls height from the viewport height | |
if (! isNaN(this.LB.controls.node.clientHeight)) { | |
yAdj = this.LB.controls.node.clientHeight + 2; | |
} | |
if (img && img.length > 0) { | |
img = img[0]; | |
ratio = 1; | |
imageDims = { | |
width : img.originalDimensions.width, | |
height: img.originalDimensions.height | |
}; | |
if (imageDims.width > dims.width || imageDims.height > dims.height) { | |
ratio = | |
(Math.min(dims.width, dims.height) - | |
LightBoxerSettings.imagePadding * 2 - | |
LightBoxerSettings.imageMargin * 2 - | |
yAdj) / | |
Math.max(imageDims.height, imageDims.width); | |
ratio = ratio < 1 ? ratio : 1; | |
} | |
img.width = Math.floor(imageDims.width * ratio); | |
img.height = Math.floor(imageDims.height * ratio); | |
xPos = Math.floor((dims.width - img.width) / 2); | |
yPos = yAdj + Math.floor(( | |
(dims.height - yAdj / 2) - ( | |
img.height + | |
LightBoxerSettings.imagePadding * 2 + | |
LightBoxerSettings.imageMargin * 2 | |
)) / 2); | |
LightBoxerUtils.applyStyles(img, { | |
background: '#fff', | |
padding: LightBoxerSettings.imagePadding + 'px', | |
position: 'absolute', | |
left: xPos + 'px', | |
top: yPos + 'px' | |
}); | |
} | |
}, | |
createNode: function () { | |
var imagecontainer; | |
try { | |
return LightBoxerUtils.getElementOrFail('LightBoxerImageContainer'); | |
} catch (e) { | |
} | |
imagecontainer = document.createElement('div'); | |
imagecontainer.id = 'LightBoxerImageContainer'; | |
LightBoxerUtils.applyStyles(imagecontainer, { | |
height: '100%', | |
left: '0', | |
position: 'absolute', | |
top: '0', | |
width: '100%', | |
zIndex: LightBoxerSettings.zOrder.image | |
}); | |
return imagecontainer; | |
} | |
}; | |
LightBoxerThrobber.prototype = { | |
node: null, | |
LB: null, | |
init: function (LB) { | |
this.LB = LB; | |
this.node = this.createNode(); | |
}, | |
show: function () { | |
if (! this.node.parentNode) { | |
this.LB.rootNode.appendChild(this.node); | |
this.center(); | |
} | |
}, | |
hide: function () { | |
if (this.node.parentNode) { | |
this.LB.rootNode.removeChild(this.node); | |
} | |
}, | |
center: function () { | |
var img = this.node.getElementsByTagName('img')[0], | |
vpDims = LightBoxerUtils.getViewportDimensions(), | |
myDims = { width: img.width, height: img.height }; | |
LightBoxerUtils.applyStyles(this.node, { | |
height: myDims.height + 'px', | |
left: ((vpDims.width - myDims.width) / 2) + 'px', | |
top: ((vpDims.height - myDims.height) / 2) + 'px', | |
width: myDims.width + 'px' | |
}); | |
}, | |
createNode: function () { | |
var throbber, | |
container; | |
try { | |
return LightBoxerUtils.getElementOrFail('LightBoxerThrobber'); | |
} catch (e) { | |
} | |
throbber = document.createElement('img'); | |
throbber.src = LightBoxerSettings.throbberSrc; | |
container = document.createElement('div'); | |
container.id = 'LightBoxerThrobber'; | |
container.appendChild(throbber); | |
LightBoxerUtils.applyStyles(container, { | |
backgroundColor: '#fff', | |
border: '1px solid #888', | |
position: 'absolute', | |
zIndex: LightBoxerSettings.zOrder.throbber | |
}); | |
LightBoxerUtils.bindEvent(window, 'resize', LightBoxerUtils.delegate(this, this.center)); | |
return container; | |
} | |
}; | |
LightBoxerUtils = { | |
bindEvent: function (target, event, callback) { | |
if (target.addEventListener) { | |
target.addEventListener(event, callback, false); | |
} else if (target.attachEvent) { | |
target.attachEvent('on' + event, callback); | |
} | |
}, | |
unbindEvent: function (target, event, callback) { | |
if (target.removeEventListener) { | |
target.removeEventListener(event, callback, false); | |
} else if (target.detachEvent) { | |
target.detachEvent('on' + event, callback); | |
} | |
}, | |
delegate: function (obj, method) { | |
return function () { | |
return method.apply(obj, arguments); | |
}; | |
}, | |
getElementOrFail: function (id) { | |
var el = document.getElementById(id); | |
if (el) { | |
return el; | |
} | |
throw new Error('Couldn\'t find element'); | |
}, | |
applyStyles: function (element, styles) { | |
var key; | |
for (key in styles) { | |
if (has.call(styles, key)) { | |
element.style[key] = styles[key]; | |
} | |
} | |
}, | |
getViewportDimensions: function () { | |
var width, | |
height; | |
if (document.documentElement.clientHeight) { | |
// Provided by most DOM browsers, including Internet Explorer | |
height = document.documentElement.clientHeight; | |
width = document.documentElement.clientWidth; | |
} else if (window.innerHeight) { | |
// Provided by most browsers, but importantly, not Internet Explorer | |
height = window.innerHeight; | |
width = window.innerWidth; | |
} else if (document.body.clientHeight) { | |
// Provided by many browsers, including Internet Explorer. | |
height = document.body.clientHeight; | |
width = document.body.clientWidth; | |
} | |
return { | |
width: width, | |
height: height | |
}; | |
} | |
}; | |
window.LightBoxer = LightBoxer; | |
domReady = (function (){ | |
var domReadyTriggered = false, | |
callBackQueue = []; | |
function onDomReady(callback) { | |
callBackQueue.push(callback); | |
fireDomReady(); | |
} | |
function fireDomReady() { | |
var i = 0, | |
l = callBackQueue.length; | |
if (! domReadyTriggered) { | |
return; | |
} | |
for (; i < l; i += 1) { | |
callBackQueue.pop().call(); | |
} | |
} | |
function watchDomReady(e) { | |
if (domReadyTriggered) { | |
return; | |
} | |
e = e || window.event; | |
if ( | |
(e.type === "DOMContentLoaded") || | |
(e.type === "readystatechange" && (/interactive|complete/).test(document.readyState)) | |
) { | |
domReadyTriggered = true; | |
fireDomReady(); | |
} | |
} | |
LightBoxerUtils.bindEvent(window, "readystatechange", watchDomReady); | |
LightBoxerUtils.bindEvent(window, "DOMContentLoaded", watchDomReady); | |
LightBoxerUtils.bindEvent(window, "load", watchDomReady); | |
return { | |
subscribe: onDomReady | |
}; | |
}()); | |
(function (autoRun) { | |
if (autoRun) { | |
domReady.subscribe(function () { | |
LightBoxer().autoSetup(); | |
}); | |
} | |
}(LightBoxerSettings.autoRun)); | |
}(window)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment