Skip to content

Instantly share code, notes, and snippets.

@nikcorg
Created June 6, 2012 15:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikcorg/2882405 to your computer and use it in GitHub Desktop.
Save nikcorg/2882405 to your computer and use it in GitHub Desktop.
Lightbox script
/**
* 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