sliderjs
Version:
lightweight slideshow engine using CSS, Canvas and WebGL Transitions
1,834 lines (1,620 loc) • 74.1 kB
JavaScript
/*!
Copyright 2010-2012 Gaetan Renaudeau
http://sliderjs.org/
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
window.sliderjs = {};
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
(function (ns) {
// Matches dashed string for camelizing
var rdashAlpha = /-([a-z]|[0-9])/ig,
rmsPrefix = /^-ms-/,
stringToListSplit = /[, ]+/,
fcamelCase = function( all, letter ) {
return ( letter + "" ).toUpperCase();
};
/*
* util functions.
* This internal implementation is light and native but can fail on some old browser.
* Hence, you could override them with a plugin (jQuery, Prototype, ...)
*/
var $ = ns.util = {
version: 'native',
/// DOM ///
html: function (element, html) {
if(element) element.innerHTML = html;
},
// add only the first node of html in element and return this node.
append: function (element, html) {
var div = document.createElement("div");
div.innerHTML = html;
return element.appendChild(div.firstChild);
},
find: function (element, css) {
return element.querySelectorAll(css);
},
addClass: function (element, c) {
element.classList.add(c);
},
removeClass: function (element, c) {
element.classList.remove(c);
},
camelCase: function (string) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},
css: function (element, prop, value) {
prop = $.camelCase(prop);
if(arguments.length==1) return element.style[prop];
element.style[prop] = value;
},
show: function (element) {
element.style.display = "block";
},
hide: function (element) {
element.style.display = "none";
},
width: function (element, w) {
if (arguments.length==1) return element.style.width;
element.style.width = w;
},
height: function (element, h) {
if (arguments.length==1) return element.style.height;
element.style.height = h;
},
on: function (element, eventName, fun) {
element.addEventListener(eventName, fun);
},
off: function (element, eventName, fun) {
element.removeEventListener(eventName, fun);
},
/// Utils ///
indexOf: function (t, a) {
if (t.indexOf) return t.indexOf(a);
for(var i=0; i<t.length; i++)
if(t[i]==a)
return i;
return -1;
},
// Make a pub/sub event system (https://gist.github.com/1000193)
makeEvent: function (_){return {
pub:function (a,b,c,d){for(d=-1,c=[].concat(_[a]);c[++d];)c[d](b)},
sub:function (a,b){(_[a]||(_[a]=[])).push(b)},
del:function (a,b){if(_[a]){var i = $.indexOf(_[a], b);i>=0 && _[a].splice(i, 1);}}
}},
each: function (a,f){
if(a.forEach) a.forEach(f); else for(var i=0; i<a.length; ++i) f(a[i], i, a);
},
map: function (a,f){
var t=[]; $.each(a, function (v,i,l){ t[i]=f(v,i,l); }); return t;
},
extend: function (obj) {
for(var a=1; a<arguments.length; ++a) {
var source = arguments[a];
for (var prop in source)
if (source[prop] !== void 0) obj[prop] = source[prop];
}
return obj;
},
// difference between two arrays (a - b)
dif: function (a, b) {
var t = [];
for (var i = 0; i<a.length; ++i)
if ($.indexOf(b, a[i]) < 0)
t.push(a[i]);
return t;
},
nop: function () {},
now: function (){ return +new Date() },
uuid: function (n){ return function (){ return ++n; } }(0),
// Simplified version of "Simple JavaScript Templating" by John Resig - http://ejohn.org/ - MIT Licensed
tmpl: function (str){
return new Function("obj","var p=[];with(obj||{}){p.push('"+
str.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+"');}return p.join('');");
},
getList: function (o) {
return typeof(o)=="string" ? o.split(stringToListSplit) : (o instanceof Array ? o : [])
},
createRegistrable: function() {
var keys = [], hashmap = {};
return function(name, o){ switch (arguments.length) {
case 0: return keys;
case 1: return hashmap[name];
case 2: keys.push(name); hashmap[name]=o;
}};
},
parseTime: function(time) {
var typ = typeof time;
if (typ == "number") return time;
if (typ == "string") {
var i = time.indexOf("ms");
if (i>0) return parseFloat(time.substring(0, i));
i = time.indexOf("s");
if (i>0) return 1000*parseFloat(time.substring(0, i));
}
return NaN;
}
}
}(window.sliderjs));
(function (ns) {
/**
* slider.Slider(nodeContainer, { ... })
*/
ns.Slider = function (container, options) {
return new Slider(container, options);
}
/**
* Slider Constructor
*/
var Slider = function (container, options) {
var self = this,
$ = ns.util,
T, // selector templating
E, // pub/sub event system
modules = {}; // store modules
if(!container) throw "container node required in first argument";
if(!options) options = {};
// Init events
E = $.makeEvent({});
self.trigger = function (event, data) {
E.pub(event, data); return self;
}
self.on = function (event, callback) {
E.sub(event, callback); return self;
}
self.off = function (event, callback) {
E.del(event, callback); return self;
}
self.once = function (event, callback) {
var removeCallback = function () {
self.off(event, removeCallback).off(event, callback);
}
return self.on(event, callback).on(event, removeCallback);
}
// Init templating
self.T = T = new SelectorTemplating(container, function () {
self.trigger("templated", container);
});
// opt method
self.opt = function (optname, newValue) {
var value = options[optname];
if(arguments.length==1) return value;
if (modules[optname])
newValue = modules[optname].m.accept(newValue, value);
options[optname] = newValue;
var message = { key: optname, old: value, value: newValue };
self.trigger(optname+"Changed", message);
return self;
}
// Get a module
self.module = function (name) {
var module = modules[name];
return module && module.m;
}
// Init modules
self.modules = (function () {
var list = function (name) {
var t = [];
for (var name in modules)
t.push(name);
return t;
}
list.start = function (names) {
$.each($.getList(names), function (name) {
var moduleFunction = ns.modules(name), onChanged, module;
if (!moduleFunction) throw "module "+name+" not found.";
modules[name] && this.stop(name);
// Create module
module = $.extend({ //extend the module with module defaults
init: $.nop,
change: $.nop,
destroy: $.nop,
accept: function (value) { return value },
fn: function (val) {
var args = [name];
arguments.length && args.push(val);
return self.opt.apply(self, args);
}
}, moduleFunction(new ns.Sandbox(self, name), $));
// Create module function
self[name] = module.fn;
// Set default
options[name] = module.accept(name in options ? options[name] : module.def);
// Bind change
self.on(name+"Changed", onChanged = function (o) { module.change(o.value, o.old) });
// Init module
module.init();
modules[name] = { m: module, c: onChanged };
self.trigger(name+"Started");
});
T.compile();
return self;
}
list.stop = function (names) {
$.each($.getList(names), function (name) {
var data = modules[name];
if (data) {
data.m.destroy();
self.off(name+"Changed", data.c);
delete options[name];
delete self[name];
delete modules[name];
self.trigger(name+"Stopped");
}
});
T.compile();
return self;
}
return list;
}());
// start all modules
var modulesList = ns.modules();
var all = options.modules ? $.getList(options.modules) : modulesList;
for (var o in options) { // add all options given in arguments
if ($.indexOf(all, o) <= 0 && $.indexOf(modulesList, o) >= 0)
all.push(o);
}
all = $.dif(all, $.getList(options.exceptModules));
self.modules.start(all);
}
/**
* Static module creation
* a sliderjs module define the behaviour of an option.
* the name of a module MUST be the same as the related option name.
*/
ns.modules = sliderjs.util.createRegistrable();
/**
* The sandbox given to the module.
* This is the interface between a module and a slider instance.
*/
ns.Sandbox = function (slider, modulename) {
var self = this;
self.template = slider.T;
// some proxified methods
self.on = function () { slider.on.apply(slider, arguments); return self }
self.once = function () { slider.once.apply(slider, arguments); return self }
self.off = function () { slider.off.apply(slider, arguments); return self }
self.trigger = function () { slider.trigger.apply(slider, arguments); return self }
self.opt = function (key, value) {
if(arguments.length==1) return slider.opt(key);
slider.opt(key, value); return self;
}
// .value([v]) is a shortcut to .opt(modulename, [v])
self.value = function (value) {
var args = [ modulename ]; arguments.length && args.push(value);
return self.opt.apply(self, args);
}
// Request a module function
self.ask = function (modulename, funname) {
var module = slider.module(modulename);
if(module) return module[funname];
}
}
}(window.sliderjs));
(function ($) {
/**
* SelectorTemplating.js
* License: GPL v3
* Author: gaetanrenaudeau.fr - 2012
* Link: https://gist.github.com/gists/1731611
*/
this.SelectorTemplating = function (container, onTemplated) {
var self = this,
rootTemplate, // type of Arguments
fragments = []; // Array of { selector: ".container", tmpl: function(i){ return "html"; } }
self.add = function (containerSelector, template, callback, priority) {
fragments.push({
tmpl: template,
cb: callback,
selector: containerSelector,
priority: priority || 0
});
return self;
}
self.remove = function (template) {
var i = $.indexOf($.map(fragments, function (t) { return t.tmpl }), template);
if (i >= 0) fragments.splice(i, 1);
return self;
}
self.refresh = function (template) {
self.compile();
}
// Template the whole thing
self.compile = function () {
var cbs = [],
i = 0,
waitList, el, nodes, created,
somethingChangedThisLoop;
// empty the container
self.destroy();
// all fragments goes to a waitlist, we need to append them all.
// When a selector matches nodes, we append the template in nodes and remove from the waitlist.
waitList = [].concat(fragments);
waitList.sort(function (a, b) {
return (a.priority != b.priority) ?
(b.priority - a.priority) : // higher priority first
(a.selector < b.selector ? -1 : 1); // try to put root selector first (guess)
});
while (waitList.length) {
el = waitList[i];
// Try to find the container
nodes = !el.selector ? [container] : $.find(container, el.selector);
if (nodes.length) {
somethingChangedThisLoop = true;
// template into each containers.
created = $.map(nodes, function(node, i) {
return $.append(node, el.tmpl(i));
});
el.nodes = created;
// remove from the waitlist
waitList.splice(i, 1);
i = 0;
}
else {
// continue the loop if we have waitList and something has changed the last loop.
if (++i >= waitList.length) {
i = 0;
if(!somethingChangedThisLoop) break;
somethingChangedThisLoop = false;
}
}
}
$.each(fragments, function (template) {
template.cb && template.cb(template.nodes, container);
});
onTemplated && onTemplated();
return self;
}
// Clean the template
self.destroy = function () {
$.html(container, '');
$.each(fragments, function (template) {
template.nodes = [];
});
return self;
}
}
}(sliderjs.util));
//~~~ canvas transition
sliderjs.modules("canvasTransition", function (sandbox, $) {
var mode,
container,
canvas, ctx,
images = [],
renderId,
drawing = false,
slide,
supported = function () {
if (navigator && navigator.userAgent) {
var m = navigator.userAgent.match(/Android ([0-9\.]+)/);
if (m && parseFloat(m[1])<2.2) return false; // Old android versions suck with Canvas
}
var c = document.createElement("canvas");
return !!(c.getContext && c.getContext("2d"));
}(),
canvasTransitionSandbox,
defaultValue = {
commutative: true,
params: {}
},
template = $.tmpl('<canvas class="slides"></canvas>');
var CanvasRenderHelper = {
clippedTransition: function (clipFunction) {
return function (o, params) {
var self = this;
var c = self.ctx;
self.drawImage(o.from);
c.save();
c.beginPath();
clipFunction.call(self, o, c, params);
c.clip();
self.drawImage(o.to);
c.restore();
}
}
};
function findHelper (t) {
for(var k in CanvasRenderHelper)
if(t[k])
return CanvasRenderHelper[k](t[k]);
}
function syncHeight (h) {
canvas.height = h;
!drawing && drawCurrentImage();
}
function syncWidth (w) {
canvas.width = w;
!drawing && drawCurrentImage();
}
function onHeightChanged (o) { canvas && syncHeight(o.value); }
function onWidthChanged (o) { canvas && syncWidth(o.value); }
function drawCurrentImage () {
var image = images[slide ? slide.value : sandbox.opt("slide")];
image && drawImage(image);
}
function drawImage (img) {
var w = canvas.width;
ctx.drawImage(img, 0, 0, w, w*img.height/img.width);
}
function clean () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function CanvasTransitionSandbox() {
var self = this;
self.sandbox = sandbox;
self.$ = $;
self.canvas = canvas;
self.ctx = ctx;
self.drawImage = drawImage;
self.clean = clean;
}
function onTemplated (nodes, container) {
canvas = nodes[0];
if (!canvas || !canvas.getContext)
return;
syncWidth(sandbox.opt("width"));
syncHeight(sandbox.opt("height"));
ctx = canvas.getContext("2d");
updateDOM();
canvasTransitionSandbox = new CanvasTransitionSandbox();
}
function updateDOM () {
if (!canvas) return;
if (mode=="canvas") {
$.show(canvas);
drawCurrentImage();
}
else {
$.hide(canvas);
}
}
function startRender (fromSlide, toSlide, transition) {
var transitionStart = $.now(),
transitionDuration = sandbox.opt('duration') || 0,
transitionFunction = transition.render,
transitionEasing = sandbox.opt('easing'),
data = transition.init && transition.init.call(canvasTransitionSandbox, fromSlide, toSlide),
from = images[fromSlide],
to = images[toSlide],
reverse = transition.commutative && fromSlide > toSlide;
transitionEasing = transitionEasing && transitionEasing.get || function (t) { return t };
if (!from || !to) return;
if (reverse) {
var tmp = to;
to = from;
from = tmp;
}
drawing = true;
(function render (id) {
var now = $.now();
if(id != renderId || now < transitionStart) return;
var p = (now-transitionStart)/transitionDuration;
if (p < 1) {
requestAnimFrame(function () { render(id) }, canvas);
p = transitionEasing(p);
transitionFunction.call(canvasTransitionSandbox, {
from: from,
to: to,
progress: reverse ? 1-p : p,
data: data
}, transition.params);
}
else {
drawing = false;
clean();
drawImage(reverse ? from : to);
sandbox.trigger("transitionEnd", { mode: "canvas" });
}
} (renderId = $.uuid()));
}
function onSlideChanged (o) {
slide = o;
if (mode=="canvas")
ctx && startRender(o.old, o.value, sandbox.value());
}
function onImagesLoaded (o) {
images = o.images;
updateDOM();
}
function onTransitionMode (m) {
if (m == mode) return; // still the same mode
mode = m;
updateDOM();
}
return {
init: function () {
sandbox.template.add(".sliderjs div.slides", template, onTemplated);
sandbox.
on("slideChanged", onSlideChanged).
on("heightChanged", onHeightChanged).
on("widthChanged", onWidthChanged).
on("imagesLoaded", onImagesLoaded).
on("transitionMode", onTransitionMode);
this.change(sandbox.value());
},
destroy: function () {
sandbox.template.remove(template);
sandbox.
off("slideChanged", onSlideChanged).
off("heightChanged", onHeightChanged).
off("widthChanged", onWidthChanged).
off("imagesLoaded", onImagesLoaded).
off("transitionMode", onTransitionMode);
},
accept: function (value, old) {
if (!value) return value;
if(typeof(value)=="string")
value = { name: value };
var t = sliderjs.canvasTransitions(value.name);
if (!t) t = value;
// Check if a canvas render helper is used
if (!t.render) t.render = findHelper(t);
value = $.extend(defaultValue, value, t, value);
if(!value.render) throw new Error("no canvas transition was found.");
return value;
},
change: function (value) {
if (value===undefined) return;
updateDOM();
sandbox.trigger("transitionMode", "canvas");
}
}
});
/**
* define a new canvas transition
*/
sliderjs.canvasTransitions = sliderjs.util.createRegistrable();
/*~~~cssTransition~~~ Handle CSS transitions
*
* GET: get the current CSS transition.
* SET: change the current transition to the given CSS transition
* - string: the name of the transition. It is defined in the transitions/css/{name}.* file and is related to the ("tr-"+name) CSS class.
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("cssTransition", function (sandbox, $) {
var node,
mode,
prefix = "tr-",
imageNodes,
slideNodes,
vendors = ["-webkit-", "-moz-", "-ms-", "-o-", ""];
function syncTransition () {
var duration = sandbox.opt("duration") || 0;
var ease = sandbox.opt("easing");
if (ease)
ease = ease.bezier;
slideNodes && $.each(slideNodes, function (node) {
$.each(vendors, function (vendor) {
$.css(node, vendor+"transition-duration", duration+"ms");
ease && $.css(node, vendor+"transition-timing-function", "cubic-bezier("+ease+")");
});
});
}
function syncClass (value, old) {
if (!node) return;
$.removeClass(node, prefix+"clear")
old && $.removeClass(node, prefix+old);
value && $.addClass(node, prefix+value);
}
function onDurationChanged (o) {
syncTransition();
}
function onEasingChanged (o) {
syncTransition();
}
function onTransitionMode (m) {
if (m == mode) return; // still the same mode
if ((mode=m) != "css") syncClass("clear", sandbox.value());
updateDOM();
}
var browser = (function(ua){
if (ua.match(/.*(Chrome|Safari).*/)) return "webkit";
if (ua.match(/.*Firefox.*/)) return "firefox";
if (navigator.appName === "Opera") return "opera";
return "webkit";
}(navigator.userAgent||""));
var transitionend = { webkit: "webkitTransitionEnd", opera: "oTransitionEnd", firefox: "transitionend" }[browser];
function bindTransitionEnd(n) {
$.on(n, transitionend, function(e) {
var c = e.target.className;
if (c.indexOf("slide")!=-1 && c.indexOf("current")!=-1 && e.propertyName=="z-index") {
sandbox.trigger("transitionEnd", { mode: "css" });
}
});
}
function updateDOM () {
syncTransition ();
if (mode=="css")
$.each(imageNodes||[], $.show);
else
$.each(imageNodes||[], $.hide);
}
function onTemplated (container) {
node = $.find(container, ".sliderjs")[0];
slideNodes = $.find(container, ".slide");
imageNodes = $.find(container, ".slide img");
syncClass(mode=="css" ? sandbox.value() : "clear");
updateDOM();
bindTransitionEnd(node);
}
return {
accept: function (value) {
return value && typeof(value)=="object" ? value.name : value;
},
init: function () {
sandbox.
on("templated", onTemplated).
on("durationChanged", onDurationChanged).
on("easingChanged", onEasingChanged).
on("transitionMode", onTransitionMode);
this.change(sandbox.value());
},
destroy: function () {
sandbox.
off("templated", onTemplated).
off("durationChanged", onDurationChanged).
off("easingChanged", onEasingChanged).
off("transitionMode", onTransitionMode);
},
change: function (value, old) {
if (value===undefined) return;
syncClass(value, old);
sandbox.trigger("transitionMode", "css");
}
}
});
sliderjs.cssTransitions = sliderjs.util.createRegistrable();
/**~~~duration~~~ handle the transition duration - the animation between 2 slides.
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("duration", function(sandbox, $) {
return {
def: 1000,
accept: $.parseTime
}
});
/**~~~easing~~~ define the transition easing function
*
* GET an object with a .get(x) method and a .bezier array field.
* .get is the CSS transition timing function
* .bezier the bezier curve representing this easing function
*
* SET can be:
* - a predefined easing (linear, ease-in, ease-out, ...)
* - a JS function
* - a [ X1, Y1, X2, Y2 ] array representing the bezier curve transition easing function
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
(function () {
var Easing = {
"ease": [0.25, 0.1, 0.25, 1.0],
"linear": [0.00, 0.0, 1.00, 1.0],
"ease-in": [0.42, 0.0, 1.00, 1.0],
"ease-out": [0.00, 0.0, 0.58, 1.0],
"ease-in-out": [0.42, 0.0, 0.58, 1.0]
}
sliderjs.modules("easing", function (sandbox, $) {
return {
def: "ease",
accept: function (value) {
if (typeof(value) == "function")
return { get: value };
if (typeof(value) == "string")
value = { bezier: Easing[value] };
else if (value instanceof Array)
value = { bezier: value };
if (value && value.bezier && !value.get) {
var b = value.bezier;
value.get = new KeySpline(b[0], b[1], b[2], b[3]).get;
}
return value;
}
}
});
/**
* KeySpline - use bezier curve for transition easing function
* is inspired from Firefox's nsSMILKeySpline.cpp
* Usage:
* var spline = new KeySpline(0.25, 0.1, 0.25, 1.0)
* spline.get(x) => returns the easing value | x must be in [0, 1] range
*/
function KeySpline (mX1, mY1, mX2, mY2) {
this.get = function(aX) {
if (mX1 == mY1 && mX2 == mY2) return aX; // linear
return CalcBezier(GetTForX(aX), mY1, mY2);
}
function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
function C(aA1) { return 3.0 * aA1; }
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
function CalcBezier(aT, aA1, aA2) {
return ((A(aA1, aA2)*aT + B(aA1, aA2))*aT + C(aA1))*aT;
}
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
function GetSlope(aT, aA1, aA2) {
return 3.0 * A(aA1, aA2)*aT*aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}
function GetTForX(aX) {
// Newton raphson iteration
var aGuessT = aX;
for (var i = 0; i < 4; ++i) {
var currentSlope = GetSlope(aGuessT, mX1, mX2);
if (currentSlope == 0.0) return aGuessT;
var currentX = CalcBezier(aGuessT, mX1, mX2) - aX;
aGuessT -= currentX / currentSlope;
}
return aGuessT;
}
}
}());
/*~~~fullscreen~~~
*
* GET:
* SET:
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("fullscreen", function (sandbox, $) {
var cl = "fullscreen";
var oldW, oldH;
var node;
var nativeFullScreenSupported = isFullscreen() !== undefined;
function requestFullScreen (node) {
if (node.requestFullScreen)
return node.requestFullScreen();
if (node.webkitRequestFullScreen)
return node.webkitRequestFullScreen();
if (node.mozRequestFullScreen)
return node.mozRequestFullScreen();
}
function cancelFullScreen () {
if (document.cancelFullScreen)
return document.cancelFullScreen();
if (document.webkitCancelFullScreen)
return document.webkitCancelFullScreen();
if (document.mozCancelFullScreen)
return document.mozCancelFullScreen();
}
function isFullscreen () {
return (document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen);
}
function onWindowResize () {
var w = window.innerWidth, h = window.innerHeight;
sandbox.opt("width", w).opt("height", h);
}
function setFullScreen (on) {
if(on) {
oldW = sandbox.opt("width");
oldH = sandbox.opt("height");
$.addClass(node, cl);
onWindowResize();
$.off(window, "resize", onWindowResize);
$.on(window, "resize", onWindowResize);
}
else {
oldW && sandbox.opt("width", oldW);
oldH && sandbox.opt("height", oldH);
$.removeClass(node, cl);
$.off(window, "resize", onWindowResize);
}
}
function get () {
if (nativeFullScreenSupported)
return isFullscreen();
return sandbox.opt();
}
function set (value) {
if (!node) return;
if ( get() == value ) return;
if (nativeFullScreenSupported) {
if (value) {
requestFullScreen(node);
}
else {
cancelFullScreen();
}
}
else {
setFullScreen(value);
}
}
function onTemplated (container) {
node = $.find(container, ".sliderjs")[0];
set(sandbox.value());
$.on(node, "webkitfullscreenchange", onFullScreenEventChange);
$.on(node, "mozfullscreenchange", onFullScreenEventChange);
$.on(node, "fullscreenchange", onFullScreenEventChange);
}
function onFullScreenEventChange (e) {
if (isFullscreen()) {
setFullScreen(true);
}
else {
setFullScreen(false);
}
}
function onKeydown (e) {
if (e.keyCode == 13) {
setFullScreen(false);
}
}
return {
def: false,
init: function () {
sandbox.on("templated", onTemplated);
$.on(window, "keydown", onKeydown);
},
destroy: function () {
sandbox.off("templated", onTemplated);
$.off(window, "keydown", onKeydown);
},
change: function (value) {
set(value);
}
}
});
/*~~~fullscreenControl~~~
*
* GET:
* SET:
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("fullscreenControl", function (sandbox, $) {
var defaultValue = {
tmpl: $.tmpl('<a class="fullscreenButton icon" href="javascript:;"></a>')
};
function template () {
var v = sandbox.value();
return v.tmpl($.extend({}, v, defaultValue));
}
function onTemplated (nodes, container) {
var node = nodes[0];
if (!node) return;
$.on(node, "click", function (e) {
e.preventDefault();
sandbox.opt("fullscreen", !sandbox.opt("fullscreen"));
});
}
function onKeyDown (e) {
if (e.keyCode == 27 && sandbox.opt("fullscreen")) {
sandbox.opt("fullscreen", false);
}
}
return {
init: function () {
sandbox.template.add(".sliderjs div.left", template, onTemplated, 9);
$.on(window, "keydown", onKeyDown);
},
accept: function (value) {
return $.extend({}, defaultValue, value);
},
change: function (value, old) {
sandbox.template.refresh(template);
},
destroy: function () {
sandbox.template.remove(template);
$.off(window, "keydown", onKeyDown);
}
}
});
//~~~ glsl transition
sliderjs.modules("glslTransition", function (sandbox, $) {
var mode,
container,
drawing = false,
canvas, gl,
program,
supported = function () {
var c = document.createElement("canvas");
return !!getWebGLContext(c);
}(),
locations = {}, // cache for locations
slide,
images = [],
defaultValue = {
commutative: true,
params: {}
};
var template = $.tmpl('<canvas class="slides"></canvas>');
function syncHeight (h) {
canvas.height = h;
gl && syncViewport();
!drawing && drawCurrentImage();
}
function syncWidth (w) {
canvas.width = w;
gl && syncViewport();
!drawing && drawCurrentImage();
}
function onHeightChanged (o) {
canvas && syncHeight(o.value);
}
function onWidthChanged (o) {
canvas && syncWidth(o.value);
}
function onSlideChanged (o) {
slide = o;
mode=="glsl" && gl &&
startRender(o.old, o.value, sandbox.value());
}
function onImagesLoaded (o) {
images = o.images;
updateDOM();
}
function onTransitionMode (m) {
if (m == mode) return; // still the same mode
mode = m;
updateDOM();
}
function onTemplated (nodes, container) {
canvas = nodes[0];
if (!canvas || !canvas.getContext) return;
$.on(canvas, "webglcontextlost", function (evt) {
console.log("webglcontextlost");
gl = null;
evt.preventDefault();
});
$.on(canvas, "webglcontextrestored", function (evt) {
console.log("webglcontextrestored");
gl = getWebGLContext(canvas);
loadTransition(sandbox.value());
});
syncWidth(sandbox.opt("width"));
syncHeight(sandbox.opt("height"));
updateDOM();
gl = getWebGLContext(canvas);
if (!gl) {
window["console"] && console.log("Unable to get a WebGL context.");
}
else {
loadTransition(sandbox.value());
}
}
function updateDOM () {
if (!canvas) return;
if (mode=="glsl") {
$.show(canvas);
drawCurrentImage();
}
else {
$.hide(canvas);
}
}
function setRectangle (x, y, width, height) {
var x1 = x, x2 = x + width, y1 = y, y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
function loadTransition (transition) {
if(!transition) return;
var w = canvas.width, h = canvas.height;
// Clean old program
if (program) {
gl.deleteProgram(program);
program = null;
locations = {};
}
// Create new program
program = loadProgram([
loadShader('attribute vec2 position;attribute vec2 texCoord_in;uniform vec2 resolution;varying vec2 texCoord;void main() {vec2 zeroToOne = position / resolution;vec2 zeroToTwo = zeroToOne * 2.0;vec2 clipSpace = zeroToTwo - 1.0;gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);texCoord = texCoord_in;}', gl.VERTEX_SHADER),
loadShader(transition.shader, gl.FRAGMENT_SHADER)
]);
gl.useProgram(program);
// bind custom params
bindParams(transition.params);
// buffer
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]), gl.STATIC_DRAW);
// texCoord
var texCoordLocation = gl.getAttribLocation(program, "texCoord_in");
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// position
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
var positionLocation = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
syncViewport();
drawCurrentImage();
}
function syncViewport () {
var w = canvas.width, h = canvas.height;
gl.viewport(0, 0, w, h);
var resolutionLocation = gl.getUniformLocation(program, "resolution");
gl.uniform2f(resolutionLocation, w, h);
var img = images[sandbox.opt("slide")];
if (img) h = Math.floor(w * img.height / img.width);
setRectangle(0, 0, w, h);
}
// custom params: only handle types: vecN[float], float
function bindParams (params) {
for (var k in params) {
var value = params[k];
var loc = gl.getUniformLocation(program, k);
if (value instanceof Array) {
if (typeof(value[0])=="number") {
var fname = "uniform"+value.length+"f";
gl[fname] && gl[fname].apply(this, [loc].concat(value));
}
}
else if (typeof(value)=="number") {
gl.uniform1f(loc, value);
}
}
}
function loadShader (shaderSource, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, shaderSource);
gl.compileShader(shader);
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
lastError = gl.getShaderInfoLog(shader);
throw shader + "':" + lastError;
gl.deleteShader(shader);
return null;
}
return shader;
}
function loadProgram (shaders) {
var program = gl.createProgram();
$.each(shaders, function (shader) {
gl.attachShader(program, shader);
});
gl.linkProgram(program);
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
throw "Linking error:" + gl.getProgramInfoLog (program);
gl.deleteProgram(program);
return null;
}
return program;
}
function createTexture (image) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// FIXME: the following line throw an error for cross domain images...
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
function setImages (from, to) {
if (!from) return false;
if (!to) return false;
var fromTexture = createTexture(from),
toTexture = createTexture(to);
if(!locations.from) locations.from = gl.getUniformLocation(program, "from");
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, fromTexture);
gl.uniform1i(locations.from, 0);
if(!locations.to) locations.to = gl.getUniformLocation(program, "to");
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, toTexture);
gl.uniform1i(locations.to, 1);
syncViewport();
return true;
}
function setProgress (p) {
if(!locations.progress) locations.progress = gl.getUniformLocation(program, "progress");
gl.uniform1f(locations.progress, p);
}
function drawImage (image) {
setProgress(0);
setImages(image, image) && draw();
}
function drawCurrentImage () {
var image = images[slide ? slide.value : sandbox.opt("slide")];
image && gl && drawImage(image);
}
function draw () {
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function getWebGLContext (canvas) {
if (!canvas.getContext) return;
var names = ["webgl", "experimental-webgl"];
for (var i = 0; i < names.length; ++i) {
try {
var ctx = canvas.getContext(names[i]);
if (ctx) return ctx;
} catch(e) {
}
}
}
function startRender (fromSlide, toSlide, transition) {
var renderId = $.uuid(),
transitionStart = $.now(),
transitionDuration = sandbox.opt('duration') || 0,
transitionEasing = sandbox.opt('easing'),
reverse = transition.commutative && fromSlide > toSlide;
transitionEasing = transitionEasing && transitionEasing.get || function (t) { return t };
if (!setImages(images[reverse ? toSlide : fromSlide], images[reverse ? fromSlide : toSlide]))
return;
drawing = true;
(function render (id) {
var now = $.now();
if(id != renderId || now < transitionStart) return;
var p = (now-transitionStart)/transitionDuration;
if (p<1) {
requestAnimFrame(function () { render(id) }, canvas);
p = transitionEasing(p);
setProgress(reverse ? 1-p : p);
draw();
}
else {
drawing = false;
drawCurrentImage();
sandbox.trigger("transitionEnd", { mode: "glsl" });
}
} (renderId));
}
return {
init: function () {
sandbox.template.add(".sliderjs div.slides", template, onTemplated);
sandbox.
on("slideChanged", onSlideChanged).
on("heightChanged", onHeightChanged).
on("widthChanged", onWidthChanged).
on("imagesLoaded", onImagesLoaded).
on("transitionMode", onTransitionMode);
this.change(sandbox.value());
},
destroy: function () {
sandbox.template.remove(template);
sandbox.
off("slideChanged", onSlideChanged).
off("heightChanged", onHeightChanged).
off("widthChanged", onWidthChanged).
off("imagesLoaded", onImagesLoaded).
off("transitionMode", onTransitionMode);
},
accept: function (value, old) {
if (!value) return value;
if (typeof(value) == "string") value = { name: value };
value = $.extend({}, defaultValue, value, sliderjs.glslTransitions(value.name), value);
if(!value.shader) throw new Error("no shader was found.");
return value;
},
change: function (value) {
if (value===undefined) return;
updateDOM();
gl && loadTransition(sandbox.value());
sandbox.trigger("transitionMode", "glsl");
},
supported: function () { return supported }
}
});
/**
* define a new fragment shader
*/
sliderjs.glslTransitions = sliderjs.util.createRegistrable();
/*~~~height~~~ handle the height of the slider
*
* GET: the current height (integer)
* SET: a new height (integer)
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("height", function (sandbox, $) {
return {
def: 430
}
});
/*~~~loader~~~ handle loading display
*
* GET: a templating object
* SET:
* - a string describing the strategy of the loader : "waitingAll", "waitingFirst", "waitingCurrent"
* - a templating object to patch. (if you want to modify the loader template)
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
// passing a string will have different kind of loader (percent, image, ...)
sliderjs.modules("loader", function (sandbox, $) {
var node, sliderjs, event, state,
id = -1, loading = true;
var defaultValue = {
tmpl: $.tmpl('<div class="loader"><p><span class="spinner"></span> <span class="percent"><%= percent %></span> %</p></div>'),
percent: 0,
onLoading: function (state, node) {
$.html($.find(node, ".percent")[0], ""+Math.floor(100*state.sum/state.total));
},
mode: "waitingAll"
}
function template () {
var v = sandbox.value();
return v.tmpl(v);
}
function syncHeight (h) {
if (!node) return;
var hpx = h ? h+"px" : "";
$.height(node, hpx);
}
function onHeightChanged (o) {
syncHeight(o.value);
}
function onTemplated (nodes, container) {
node = nodes[0];
sliderjs = $.find(container, ".sliderjs")[0];
syncHeight(sandbox.opt("height"));
loading && startLoading();
}
function eventForStrategy (s) {
if(s=="waitingFirst") return "firstSlideLoaded";
if(s=="waitingCurrent") return "currentSlideLoaded";
if(s=="waitingAll") return "imagesLoaded";
return null;
}
function loadingForStrategy (s) {
switch (s) {
case "waitingFirst":
case "waitingCurrent":
case "waitingAll":
return true;
}
return false;
}
function endLoading () {
sliderjs && $.removeClass(sliderjs, "loading");
sandbox.trigger("loaderLoaded");
state = null;
}
function startLoading () {
sliderjs && $.addClass(sliderjs, "loading");
sandbox.trigger("loaderLoading");
}
function syncLoader (o) {
if (!sliderjs) return;
if (state && (state.sum == state.total)) {
endLoading();
}
else {
var onLoading = sandbox.value().onLoading;
state && onLoading && onLoading(state, sliderjs);
if(o.id !== id) {
startLoading();
id = o.id;
}
}
}
function onImagesLoading (o) {
if (o) state = o.state;
loading && syncLoader(o);
}
function onImagesLoaded (o) {
if (o) state = o.state;
loading && syncLoader(o);
}
function handler (o) {
endLoading();
}
function syncMode (mode) {
loading = loadingForStrategy(mode);
event = eventForStrategy(mode);
sandbox.off(event, handler).once(event, handler);
}
return {
def: defaultValue,
init: function () {
sandbox.template.add(".sliderjs", template, onTemplated, 10);
sandbox.
on("heightChanged", onHeightChanged).
on("imagesLoading", onImagesLoading).
on("imagesLoaded", onImagesLoaded);
var value = sandbox.value();
value.mode && syncMode(value.mode);
},
destroy: function () {
sandbox.template.remove(template);
sandbox.
off("heightChanged", onHeightChanged).
off("imagesLoading", onImagesLoading).
off("imagesLoaded", onImagesLoaded);
},
accept: function (value) {
if (typeof(value) == "string") value = { mode: value };
return $.extend(defaultValue, value);
},
change: function (value, old) {
sandbox.template.refresh(template);
value.mode && syncMode(value.mode);
}
}
});
/*~~~loop~~~ start / stop the auto slide mode with a specific duration
*
* GET: the millisecond time for the current loop (0 is the loop is stopped)
* SET:
* - an integer: set the duration between each slides (in ms) and start the loop
* - "start": start the loop with the latest set duration (or the default)
* - "stop": stop the loop if running
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("loop", function (sandbox, $) {
var defaultValue = 5000, lastHumanNav = 0, timeout;
function onPagesClicked () {
lastHumanNav = $.now();
}
function loop () {
if($.now()-lastHumanNav > 2000) sandbox.opt("slide", "next");
iterateLoop();
}
function iterateLoop () {
timeout = setTimeout(loop, sandbox.value());
}
function stop () {
timeout && clearTimeout(timeout);
timeout = null;
}
function start () {
lastHumanNav = 0;
stop();
iterateLoop();
}
return {
def: "start",
init: function () {
sandbox.on("pagesClicked", onPagesClicked);
this.change(sandbox.value());
},
destroy: function () {
stop();
sandbox.off("pagesClicked", onPagesClicked);
},
accept: function (value, old) {
if(value == "start") return defaultValue;
if(value == "stop") return 0;
value = $.parseTime(value);
if (!isNaN(value) && value > 0)
return defaultValue = value;
return 0;
},
change: function (value, old) {
if(value) start();
else stop();
}
}
});
/*~~~loopBar~~~
*
* GET:
* SET:
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("loopBar", function (sandbox, $) {
var interval,
lastSlide = $.now(),
oldP,
node, inner,
loop = sandbox.opt("loop"),
defaultValue = {
width: 100,
tmpl: $.tmpl('<span class="loopBar" style="width: <%= width %>px;"><span class="loopBar-inner"></span></span>')
};
function template () {
var v = sandbox.value();
return v.tmpl(v);
}
function onTemplated (nodes, container) {
node = nodes[0];
if (!node) return;
inner = $.find(node, ".loopBar-inner")[0];
render();
}
function onLoopChanged (o) {
loop = o.value;
lastSlide = $.now();
inner && updateProgress();
}
function onSlideChanged (o) {
lastSlide = $.now();
inner && updateProgress();
}
function updateProgress () {
var p = loop ? (Math.max(0, Math.min(1, ($.now()-lastSlide)/loop))) : 0;
(p !== oldP) && $.width(inner, Math.round((oldP=p)*10000)/100+"%");
}
function render () {
if (inner) {
requestAnimFrame(render, inner);
updateProgress();
}
}
return {
init: function () {
sandbox.template.add(".sliderjs .header", template, onTemplated, -6);
sandbox.
on("loopChanged", onLoopChanged).
on("slideChanged", onSlideChanged);
render();
},
accept: function (value) {
return $.extend({}, defaultValue, value);
},
change: function (value, old) {
sandbox.template.refresh(template);
},
destroy: function () {
sandbox.template.remove(template);
sandbox.
off("loopChanged", onLoopChanged).
off("slideChanged", onSlideChanged);
}
}
});
/*~~~nextControl~~~
*
* GET:
* SET:
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("nextControl", function (sandbox, $) {
var defaultValue = {
text: "→",
tmpl: $.tmpl('<a class="nextSlide" href="javascript:;"><%= text %></a>')
};
function template () {
var v = sandbox.value();
return v.tmpl(v);
}
function onTemplated (nodes, container) {
var node = nodes[0];
if (!node) return;
$.on(node, "click", function (e) {
e.preventDefault();
sandbox.opt("slide", "next").trigger("pagesClicked");
});
}
return {
init: function () {
sandbox.template.add(".sliderjs .footer", template, onTemplated, -9);
},
accept: function (value) {
return $.extend({}, defaultValue, value);
},
change: function (value, old) {
sandbox.template.refresh(template);
},
destroy: function () {
sandbox.template.remove(template);
}
}
});
/*~~~pages~~~ handle navigation
*
* GET: a templating object
* SET: a templating object
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("pages", function (sandbox, $) {
var node, pages,
defaultValue = {
tmpl: $.tmpl('<span class="pages"> <% if(obj.slides){for(var i=0;i<slides.length;++i){var s=slides[i]; %> <a href="<%= s.image %>" class="page"><%= i+1 %></a> <% }} %> </span>')
};
function template () {
var v = sandbox.value();
return v.tmpl($.extend({}, v, {
slides: sandbox.opt("slides")
}));
}
function onTemplated (nodes) {
node = nodes[0];
if (!node) return;
pages = $.find(node, ".page");
$.each(pages, function (n, i) {
var slide = i;
$.on(n, "click", function (e) {
e.preventDefault();
sandbox.opt("slide", slide).trigger("pagesClicked");
});
});
var current = pages[sandbox.opt("slide")];
current && $.addClass(current, "current");
}
function onSlidesChanged (o) {
sandbox.template.refresh(template);
}
function onSlideChanged (o) {
if(!pages) return;
var old = pages[o.old];
var value = pages[o.value];
old && $.removeClass(old, "current");
value && $.addClass(value, "current");
}
return {
def: defaultValue,
init: function () {
sandbox.template.add(".sliderjs .footer", template, onTemplated, -9);
sandbox.
on("slidesChanged", onSlidesChanged).
on("slideChanged", onSlideChanged);
},
destroy: function () {
sandbox.template.remove(template);
sandbox.
off("slidesChanged", onSlidesChanged).
off("slideChanged", onSlideChanged);
},
accept: function (value) {
return $.extend(defaultValue, value);
},
change: function (value, old) {
sandbox.template.refresh(template);
}
}
});
/*~~~playPauseControl~~~
*
* GET:
* SET:
*
* <renaudeau.gaetan@gmail.com> - 2012
*/
sliderjs.modules("playPauseControl", function (sandbox, $) {
var node, defaultValue = {
tmpl: $.tmpl('<a class="playPauseControl icon" href="javascript:;"></a>')
};
function template () {
var v = sandbox.value();
return v.tmpl($.extend({}, v, defaultValue));
}
function running () {
return sandbox.opt("loop")>0;
}
function syncButton () {
if (!node) return;
if (running()) {
$.addClass(node, "pause");
$.removeClass(node, "play");
}
else {
$.addClass(node, "play");
$.removeClass(node, "pause");
}
}
function onTemplated (nodes, container) {
node = nodes[0];
if (!node) return;
syncButton();
$.on(node, "click", function (e) {
e.preventDefault();
sandbox.opt("loop", !running