UNPKG

sliderjs

Version:

lightweight slideshow engine using CSS, Canvas and WebGL Transitions

342 lines (300 loc) 10.1 kB
//~~~ 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();