/* ----------------------------------------------------------------------------- * Freezeframe * freezeframe.js v2.0.2 * 2014 Chris Antonellis * * Freezeframe.js is a script that automatically pauses animated GIFs and * enables them to start animating on mouse hover. * * Website: http://freezeframe.chrisantonellis.com/ * Documentation: http://freezeframe.chrisantonellis.com/documentation/ * * Licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License * http://creativecommons.org/licenses/by-sa/3.0/deed.en_US * -------------------------------------------------------------------------- */ FreezeFrame = (function($) { var _ff; var canvas; var context; var class_name; var trigger_event; var support_touch_devices; var animation_play_duration; var loading_background_color; var loading_background_image; var loading_background_position; var loading_fade_in_speed; var animation_icon_image; var animation_icon_position; var animation_fade_out_speed; var is_touch_device; var freezeframe_count; function FreezeFrame( _options ) { _ff = this; _options.class_name == null ? this.class_name = "freezeframe" : this.class_name = _options.class_name; _options.trigger_event == null ? this.trigger_event = "hover" : this.trigger_event = _options.trigger_event.toLowerCase(); _options.support_touch_devices == null ? this.support_touch_devices = true : this.support_touch_devuces = _options.support_touch_devices.toLowerCase(); _options.animation_play_duration == null ? this.animation_play_duration = 10000 : this.animation_play_duration = parseInt(_options.animation_play_duration); _options.loading_background_color == null ? this.loading_background_color = "#666" : this.loading_background_color = _options.loading_background_color.toLowerCase(); _options.loading_background_image == null ? this.loading_background_image = "data:image/gif;base64,R0lGODlhEAAQAPIAAGZmZv///4mJidbW1v///8PDw7CwsKampiH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" : this.loading_background_image = _options.loading_background_image; _options.loading_background_position == null ? this.loading_background_position = "center center" : this.loading_background_position = _options.loading_background_position.toLowerCase(); _options.animation_icon_image == null ? this.animation_icon_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAl9JREFUeNrs2s2K2lAUAGATb3TE2Cr+oaJQNFOh1MVsxK0OuJbSdX2V9gG67GqW3RbaB7B9gy7qgKJF8G/EarHVWGMyJj230EXLLITm6q05F44/IVw5X3JPjiGCZVkuJw/R5fCBAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAiAAAjwjyMej79MpVKP/hcAwe6boul0+p3P5/vmdruv4evbVqvVdRRALpd7HQqFpn6/XxUEwdI07UZV1ffNZvMzjwDE7gk9Hs8mGAwuYCl8hTNB32630nw+f1IqlRY8QtgOQAjRZVleJxKJ77AcVLptOBwup9PpPR4hbAcQRdGUJOk2HA5riqKokUjEgKK4GY/HKo8QhMGcv4oKIJg0+Xw+r8H7bSaT0XiEICwnhyLoCgQCptfrNSgCjxDkED8ChdGiwSMEOaQ2jxDkGOtuH4jJZHJ/Nps9LRaLi+Vy2WDVUB0FYB+IXq+36ff7P0ajURj2eQbF9EO73W6cFMBdENA8mXQbNFDier0mhULhQaVSeZzNZj+d3Bnwe+i6LkDCAqx9QmMwGJwlk8nzarV6Cf3EDHZ5DvHx5ADuSjwWiz0sl8uX8H9izjLxowLskfgL1okfBYCnxA8KwGPiBwGg9xpWq5XIY+IsAQT6stvt6BGX4Nrt4jFxZgCmaYqGYRDo4nzdbncny3KGx8SZAcCRl6CH9yuKcl6r1S6i0egXHhP/Y53aGfV6/U2n02nA5yuIC7vntzsEBo/KXkG84vaI/12w8Flhhw8EQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEcNr4KcAABsB8S2vxAV8AAAAASUVORK5CYII=" : this.animation_icon_image = _options.animation_icon_image; _options.animation_icon_position == null ? this.animation_icon_position = "top left" : this.animation_icon_position = _options.animation_icon_position.toLowerCase(); _options.loading_fade_in_speed == null ? this.loading_fade_in_speed = 500 : this.loading_fade_in_speed = parseInt(_options.loading_fade_in_speed); _options.animation_fade_out_speed == null ? this.animation_fade_out_speed = 250 : this.animation_fade_out_speed = parseInt(_options.animation_fade_out_speed); this.is_touch_device = isMouseEventSupported("ontouchstart"); this.freezeframe_count = 0; }; /** -------------------------------------------------------------------------- * @function .setup() * Sets up the freezeframe instance by adding a canvas element to the * document and capturing references to the canvas and its 2D context * ------------------------------------------------------------------------ */ FreezeFrame.prototype.setup = function() { $("",{id:"freezeframe-canvas"}) .css("display", "none") .prependTo($("body")); this.canvas = $("canvas#freezeframe-canvas"); this.context = this.canvas[0].getContext('2d'); }; /** -------------------------------------------------------------------------- * .run() * Starts freezeframe processing for each image. Copies the image to * the freezeframe canvas and converts it to a data url * ------------------------------------------------------------------------ */ FreezeFrame.prototype.run = function() { var images = []; var ext; var figure_background = _ff.loading_background_color; if( _ff.loading_background_image !== false ) { figure_background += " url('" + _ff.loading_background_image + "') " + _ff.loading_background_position + " no-repeat"; } // Select all images with a class matching the option class_name images = $('img[class*="' + _ff.class_name + '"]') .not('[class*="' + _ff.class_name + '_done"]'); // Process each image by resetting the animation sequence, copying to the // canvas, converting to a data url, and attaching that data url to the // image itself as an attribute $(images).each(function(index) { // Change image class so it won't be reprocessed if .run() is run again $(this).removeClass(_ff.class_name).addClass(_ff.class_name + "_done"); // Set cross-origin to anon to load images from remote services that send // the correct header. Not working correctly, needs more testing $(this).crossOrigin = "anonymous"; // Determine file extension ext = $(this)[0].src.split("."); ext = ext[ext.length - 1].toLowerCase(); // Remove non GIF files if(ext !== "gif") { images.splice(index, 1); } else { var freezeframe_figure = $("
") .attr("class", "freezeframe-container " + _ff.freezeframe_count) .css({"display": "inline-block", "overflow": "hidden", "margin": 0, "background-size": "100% auto", "width": "100%", "max-height": "100%", "background": figure_background}); $(this).css({"opacity": 0, "display": "block"}) .wrap(freezeframe_figure); freezeframe_figure = $(this).parent(); // If an animation icon image is available, attach it if(_ff.animation_icon_image !== false) { var animation_icon = $("
") .attr("class", "freezeframe-animation-icon") .css({"display": "block", "position": "absolute", "background": "transparent " + "url('" + _ff.animation_icon_image + "') " + _ff.animation_icon_position + " no-repeat", "pointer-events": "none", "z-index": 100, "opacity": 0}); $(freezeframe_figure).prepend(animation_icon); animation_icon = $(this).siblings($(".freezeframe-animation-icon")); } // Increment counter so each image gets a unique number _ff.freezeframe_count++; // Create a temporary refernce to the image as non-object to pass to .drawImage var _self = this; // Using imagesLoaded by Desandro because .load doesn't work on cached images $(this).imagesLoaded(function() { $(this).off("imagesLoaded"); _ff.canvas[0].width = $(this)[0].clientWidth; _ff.canvas[0].height = $(this)[0].clientHeight; $(this).attr("animated", $(this).attr("src")) .attr("src", $(this).attr("src")); _ff.context.drawImage(_self, 0, 0, $(this)[0].clientWidth, $(this)[0].clientHeight); $(this).attr("src", _ff.canvas[0].toDataURL()); // If an animation icon image is available, show it if(_ff.animation_icon_image !== false) { $(animation_icon).css({ "width": parseInt($(this)[0].width), "height": parseInt($(this)[0].height)}) .animate({"opacity": 1}, _ff.loading_fade_in_speed); } // When fade in sequence is complete, enable interaction $(this).animate({"opacity": 1}, _ff.loading_fade_in_speed, function() { $(freezeframe_figure).css("background", "url('" + $(this).attr("src") + "')"); $(this).css("opacity", 0) .attr("src", $(this).attr("animated")); // Touch Device or Click Event if((_ff.support_touch_devices && _ff.is_touch_device) || _ff.trigger_event.toLowerCase() == "click") { var stop_animation; var animating = false; $(this).click(function() { // If not currently animating, start animating if(!animating) { $(this).attr("src", $(this).attr("src")) .css("opacity", 1); if(_ff.animation_icon_image !== false) { $(animation_icon).css("opacity", 0); } stop_animation = setInterval(function() { clearInterval(stop_animation); this.animate({"opacity": 0}, _ff.animation_fade_out_speed); if(_ff.animation_icon_image !== false) { $(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed); } animating = false; }, _ff.animation_play_duration); animating = true; // If currently animating, stop animating } else { clearInterval(stop_animation); $(this).animate({"opacity": 0}, _ff.animation_fade_out_speed); if(_ff.animation_icon_image !== false) { $(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed); } animating = false; } }); } else { // Hover Event $(this).mouseenter(function() { $(this).attr("src", $(this).attr("src")) .css("opacity", 1); if(_ff.animation_icon_image !== false) { $(animation_icon).css("opacity", 0); } }); $(this).mouseleave(function() { $(this).animate({"opacity": 0}, _ff.animation_fade_out_speed); if(_ff.animation_icon_image !== false) { $(animation_icon).animate({"opacity": 1}, _ff.animation_fade_out_speed); } }); } }); }); } }); }; return FreezeFrame; })(jQuery); /** ---------------------------------------------------------------------------- * Setup & Run Freezeframe * -------------------------------------------------------------------------- */ jQuery(document).ready(function($) { typeof(freezeframe_options) == 'undefined' ? freezeframe_options = {} : null; freezeframe = new FreezeFrame(freezeframe_options); freezeframe.setup(); freezeframe.run(); }); /** ---------------------------------------------------------------------------- * jQuery imagesLoaded plugin v2.1.1 * http://github.com/desandro/imagesloaded * * MIT License. by Paul Irish et al. * -------------------------------------------------------------------------- */ ;(function($, undefined) { 'use strict'; var BLANK = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=='; $.fn.imagesLoaded = function( callback ) { var $this = this, deferred = $.isFunction($.Deferred) ? $.Deferred() : 0, hasNotify = $.isFunction(deferred.notify), $images = $this.find('img').add( $this.filter('img') ), loaded = [], proper = [], broken = []; if ($.isPlainObject(callback)) { $.each(callback, function (key, value) { if (key === 'callback') { callback = value; } else if (deferred) { deferred[key](value); } }); } function doneLoading() { var $proper = $(proper), $broken = $(broken); if ( deferred ) { if ( broken.length ) { deferred.reject( $images, $proper, $broken ); } else { deferred.resolve( $images ); } } if ( $.isFunction( callback ) ) { callback.call( $this, $images, $proper, $broken ); } } function imgLoadedHandler( event ) { imgLoaded( event.target, event.type === 'error' ); } function imgLoaded( img, isBroken ) { if ( img.src === BLANK || $.inArray( img, loaded ) !== -1 ) { return; } loaded.push( img ); if ( isBroken ) { broken.push( img ); } else { proper.push( img ); } $.data( img, 'imagesLoaded', { isBroken: isBroken, src: img.src } ); if ( hasNotify ) { deferred.notifyWith( $(img), [ isBroken, $images, $(proper), $(broken) ] ); } if ( $images.length === loaded.length ) { setTimeout( doneLoading ); $images.unbind( '.imagesLoaded', imgLoadedHandler ); } } if ( !$images.length ) { doneLoading(); } else { $images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoadedHandler ) .each( function( i, el ) { var src = el.src; var cached = $.data( el, 'imagesLoaded' ); if ( cached && cached.src === src ) { imgLoaded( el, cached.isBroken ); return; } if ( el.complete && el.naturalWidth !== undefined ) { imgLoaded( el, el.naturalWidth === 0 || el.naturalHeight === 0 ); return; } if ( el.readyState || el.complete ) { el.src = BLANK; el.src = src; } }); } return deferred ? deferred.promise( $this ) : $this; }; })(jQuery); /** ---------------------------------------------------------------------------- * isMouseEventSupported by kangax * http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ * -------------------------------------------------------------------------- */ function isMouseEventSupported(eventName) { var el = document.createElement('div'); eventName = 'on' + eventName; var isSupported = (eventName in el); if (!isSupported) { el.setAttribute(eventName, 'return;'); isSupported = typeof el[eventName] == 'function'; } el = null; return isSupported; }