/** @license glitch.js v0.1 Released under MIT License Copyright (c) 2012 Simon Hewitt. http://www.twitter.com/sjhewitt */ (function($){ /*global html2canvas */ var noop = function(){}, /** * Set default properties on an object * @param {Object} obj The target object * @param {Object} defaults The default properties * @return {Object} The target obj */ defaults = function(obj, defaults) { for (var prop in defaults) { if (obj[prop] == null) obj[prop] = defaults[prop]; } return obj; }, /** * Generates an integer between min and max * * @param {Number} min The lower bound * @param {Number} max The upper bound * @return {Number} A random number */ getRandInt = function(min, max) { return (Math.floor(Math.random() * (max - min) + min)); }; /** * Apply the glitch effect to a canvas object * * @param {HTMLCanvasElement} canvas The canvas (or HTMLImageElement) to apply the glitch to * @param {number} amount The amount to glitch the canvas (default: 6) * @return {HTMLCanvasElement} A canvas containing a glitched version * of the original canvas */ var _glitch = function(canvas, amount) { var // cache the width and height of the canvas locally x, y, w = canvas.width, h = canvas.height, // _len is an iterator limit, initially storing the number of slices // to create i, _len = amount || 6, // pick a random amount to offset the color channel channelOffset = (getRandInt(-_len*2, _len*2) * w * + getRandInt(-_len, _len)) * 4, // the maximum amount to offset a chunk of the image is a function of its width maxOffset = _len * _len / 100 * w, // vars for the width and height of the chunk that gets offset chunkWidth, chunkHeight, // create a temporary canvas to hold the image we're working on tempCanvas = document.createElement("canvas"), tempCtx = tempCanvas.getContext("2d"), srcData, targetImageData, data; // set the dimensions of the working canvas tempCanvas.width = w; tempCanvas.height = h; // draw the initial image onto the working canvas tempCtx.drawImage(canvas, 0, 0, w, h); // store the data of the original image for use when offsetting a channel srcData = tempCtx.getImageData(0, 0, w, h).data; // randomly offset slices horizontally for (i = 0; i < _len; i++) { // pick a random y coordinate to slice at y = getRandInt(0, h); // pick a random height of the slice chunkHeight = Math.min(getRandInt(1, h / 4), h - y); // pick a random horizontal distance to offset the slice x = getRandInt(1, maxOffset); chunkWidth = w - x; // draw the first chunk tempCtx.drawImage(canvas, 0, y, chunkWidth, chunkHeight, x, y, chunkWidth, chunkHeight); // draw the rest tempCtx.drawImage(canvas, chunkWidth, y, x, chunkHeight, 0, y, x, chunkHeight); } // get hold of the ImageData for the working image targetImageData = tempCtx.getImageData(0, 0, w, h); // and get a local reference to the rgba data array data = targetImageData.data; // Copy a random color channel from the original image into // the working canvas, offsetting it by a random amount // // ImageData arrays are a single dimension array that contains // 4 values for each pixel. // so, by initializing `i` to a random number between 0 and 2, // and incrementing by 4 on each iteration, we can replace only // a single channel in the image for(i = getRandInt(0, 3), _len = srcData.length; i < _len; i += 4) { data[i+channelOffset] = srcData[i]; } // Make the image brighter by doubling the rgb values for(i = 0; i < _len; i++) { data[i++] *= 2; data[i++] *= 2; data[i++] *= 2; } // TODO: The above loops are the most costly in this function, iterating // over all the pixels in the image twice. // It maybe possible to optimize this by combining both loops into one, // and only processing every other line, as alternate lines are replaced // with black in the 'scan lines' block belop // copy the tweaked ImageData back into the context tempCtx.putImageData(targetImageData, 0, 0); // add scan lines tempCtx.fillStyle = "rgb(0,0,0)"; for (i = 0; i < h; i += 2) { tempCtx.fillRect (0, i, w, 1); } return tempCanvas; }; /** * Creates a canvas containing a glitched version of the element * @param {DOMElement} el The element to glitch * @param {Object} options An object containing the complete callback, * the amount to glitch the image, and any * html2canvas options */ var glitch = function(el, options) { options = defaults(options || {}, { // the amount to glitch the image amount: 6, // a callback that takes the glitched canvas as its only argument complete: noop }); // callback for when the element has been rendered options.onrendered = function(canvas) { options.complete(_glitch(canvas, options.amount)); }; // render the element onto a canvas html2canvas(el[0] ? el : [el], options); }; /** * Replace el with a glitched version of it * @param {DOMElement} el The element to glitch * @param {Object} options An object containing the options for glitch */ glitch.replace = function(el, options) { options = options || {}; // store a reference to the complete callback so we can use the same // options for the glitch function call var _complete = options.complete; options.complete = function(canvas) { if($ && el instanceof $) { el.after(canvas).detach(); } else { // no jQuery... el.parentNode.insertBefore(canvas, el); el.parentNode.removeChild(el); } if(_complete){ _complete(); } }; glitch(el, options); }; /** * Replace `el` with `newEl` by overlaying a glitched version of `el`, then * animating it out to reveal `newEl` * * The animation will take into account elements of different sizes by sliding * the container to reveal it, however it looks best if the elements to be * transitioned between are of similar sizes * * @param {jQuery} el The original element that will be glitched * @param {jQuery} newEl The element to show * @param {Object} options An object containing the options for the animation * and any options for html2canvas */ glitch.transition = function(el, newEl, options) { // set the default options options = defaults(options || {}, { // the amount to glitch the image amount: 6, // A callback when the animation is complete complete: noop, // The delay after rendering the glitched element until starting the transition delay: 300, // The duration of the transition effect duration: 500, // The z-index to apply to the overlay. You might need to tweak this if // you have things that appear above the element, or are using high // z-indexes in your page zIndex: 1000, // the transition effect to use. This may be "fade" or "slide" effect: "fade", // The size of the top border. Set to 0 to disable, only used in slide mode borderSize: 2, // The color of the top border, only used in slide mode borderColor: "green" }); // add the new element to the dom so we can properly calculate its dimensions newEl.insertAfter(el); // store a reference to the complete callback so we can use the same // options for the glitch function call var _complete = options.complete, // get the dimensions of the elements so we can resize the targetContainer // to reveal all the content after the glitch transition origHeight = el.outerHeight(true), origWidth = el.outerWidth(true), targetHeight = newEl.outerHeight(true), targetWidth = newEl.outerWidth(true), origOverflow = newEl.css("overflow"); // take the new element out of the dom again newEl.detach(); // create a callback that will options.complete = function(canvas){ // position the canvas absolutely within the container var $canvas = $(canvas).css("position", "absolute"), offset = el.offset(), // create a container element that contains the canvas and position it // over the element we're replacing container = $("
").css({ "border-top": options.borderSize ? options.borderSize + "px solid " + options.borderColor : "none", position: "absolute", left: offset.left, top: offset.top - options.borderSize, width: canvas.width, height: canvas.height, overflow: "hidden", "z-index": options.zIndex }) // add the canvas as a child to the container .html(canvas) // add the container to the dom .appendTo("body") // delay the animation a bit .delay(options.delay), targetContainer = $("
").css({ width: origWidth, height: origHeight, overflow: "hidden", border: "none", "box-sizing": "border-box" }) .html(newEl), // the default transition effect is to fade out animation = { opacity: 0 }, animateOptions = { duration: options.duration, complete: function(){ // when the animation is done: // remove the container from the dom container.remove(); // then animate the height of the new element back to its measured // height and width targetContainer.animate({ height: targetHeight, width: targetWidth }, { duration: 100, complete: function(){ // take the targetContainer element out of the dom newEl.detach().insertAfter(targetContainer); targetContainer.remove(); // call the complete callback _complete(); // and clear all references options = $canvas = container = null; } }); } }; if(options.effect === "slide") { // for the slide effect, we move move the container down animation = { top: offset.top + canvas.height, height: 0 }; // and on each step of the animation, we need to offset the top so it // remains in the same place on the screen animateOptions.step = function(now, fx){ if(fx.prop === "top") { $canvas.css("top", fx.start - now); } }; } // apply the animation container.animate(animation, animateOptions); // replace the original element with the new one // we use detatch so that the event handlers on the old // element are retained. targetContainer.insertAfter(el); el.detach(); }; // create a glitched version of the start element glitch(el, options); }; window.glitch = glitch; if($) { /** * jQuery glitch.js plugin * * This can be called in the following ways: * * Replace the element with a glitched version * $("#el").glitch() * * Create a glitched canvas and pass it to a callback function * $("#el").glitch(function(canvas){}) * * Transition effect * $("#el").glitch('transition', $("#newEl"), {}) */ $.fn.glitch = function(method) { var args = Array.prototype.splice.call(arguments, 1); method = method || 'replace'; return this.each(function(){ if(method instanceof $) { glitch.transition($(this), method, args[0]); } else if(typeof method == 'function') { // just a callback passed in glitch($(this), { complete: method }); } else if(typeof method == 'object') { // an options object passed in glitch($(this), method); } else if(glitch.hasOwnProperty(method)) { // explicitly call a method glitch[method].apply(null, [$(this)].concat(args)); } else { $.error('Method ' + method + ' does not exist on jQuery.glitch'); } }); }; } })(window.jQuery);