mirror of
				https://github.com/dawidolko/Website-Templates.git
				synced 2025-10-27 16:03:10 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			949 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			949 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * Basic structure: TC_Class is the public class that is returned upon being called
 | |
|  * 
 | |
|  * So, if you do
 | |
|  *      var tc = $(".timer").TimeCircles();
 | |
|  *      
 | |
|  * tc will contain an instance of the public TimeCircles class. It is important to
 | |
|  * note that TimeCircles is not chained in the conventional way, check the
 | |
|  * documentation for more info on how TimeCircles can be chained.
 | |
|  * 
 | |
|  * After being called/created, the public TimerCircles class will then- for each element
 | |
|  * within it's collection, either fetch or create an instance of the private class.
 | |
|  * Each function called upon the public class will be forwarded to each instance
 | |
|  * of the private classes within the relevant element collection
 | |
|  **/
 | |
| (function($) {
 | |
| 
 | |
|     var useWindow = window;
 | |
|     
 | |
|     // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
 | |
|     if (!Object.keys) {
 | |
|         Object.keys = (function() {
 | |
|             'use strict';
 | |
|             var hasOwnProperty = Object.prototype.hasOwnProperty,
 | |
|                     hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
 | |
|                     dontEnums = [
 | |
|                         'toString',
 | |
|                         'toLocaleString',
 | |
|                         'valueOf',
 | |
|                         'hasOwnProperty',
 | |
|                         'isPrototypeOf',
 | |
|                         'propertyIsEnumerable',
 | |
|                         'constructor'
 | |
|                     ],
 | |
|                     dontEnumsLength = dontEnums.length;
 | |
| 
 | |
|             return function(obj) {
 | |
|                 if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
 | |
|                     throw new TypeError('Object.keys called on non-object');
 | |
|                 }
 | |
| 
 | |
|                 var result = [], prop, i;
 | |
| 
 | |
|                 for (prop in obj) {
 | |
|                     if (hasOwnProperty.call(obj, prop)) {
 | |
|                         result.push(prop);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (hasDontEnumBug) {
 | |
|                     for (i = 0; i < dontEnumsLength; i++) {
 | |
|                         if (hasOwnProperty.call(obj, dontEnums[i])) {
 | |
|                             result.push(dontEnums[i]);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 return result;
 | |
|             };
 | |
|         }());
 | |
|     }
 | |
|     
 | |
|     // Used to disable some features on IE8
 | |
|     var limited_mode = false;
 | |
|     var tick_duration = 200; // in ms
 | |
|     
 | |
|     var debug = (location.hash === "#debug");
 | |
|     function debug_log(msg) {
 | |
|         if (debug) {
 | |
|             console.log(msg);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     var allUnits = ["Days", "Hours", "Minutes", "Seconds"];
 | |
|     var nextUnits = {
 | |
|         Seconds: "Minutes",
 | |
|         Minutes: "Hours",
 | |
|         Hours: "Days",
 | |
|         Days: "Years"
 | |
|     };
 | |
|     var secondsIn = {
 | |
|         Seconds: 1,
 | |
|         Minutes: 60,
 | |
|         Hours: 3600,
 | |
|         Days: 86400,
 | |
|         Months: 2678400,
 | |
|         Years: 31536000
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * Converts hex color code into object containing integer values for the r,g,b use
 | |
|      * This function (hexToRgb) originates from:
 | |
|      * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
 | |
|      * @param {string} hex color code
 | |
|      */
 | |
|     function hexToRgb(hex) {
 | |
|         // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
 | |
|         var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
 | |
|         hex = hex.replace(shorthandRegex, function(m, r, g, b) {
 | |
|             return r + r + g + g + b + b;
 | |
|         });
 | |
| 
 | |
|         var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
 | |
|         return result ? {
 | |
|             r: parseInt(result[1], 16),
 | |
|             g: parseInt(result[2], 16),
 | |
|             b: parseInt(result[3], 16)
 | |
|         } : null;
 | |
|     }
 | |
|     
 | |
|     function isCanvasSupported() {
 | |
|         var elem = document.createElement('canvas');
 | |
|         return !!(elem.getContext && elem.getContext('2d'));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Function s4() and guid() originate from:
 | |
|      * http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
 | |
|      */
 | |
|     function s4() {
 | |
|         return Math.floor((1 + Math.random()) * 0x10000)
 | |
|                 .toString(16)
 | |
|                 .substring(1);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Creates a unique id
 | |
|      * @returns {String}
 | |
|      */
 | |
|     function guid() {
 | |
|         return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
 | |
|                 s4() + '-' + s4() + s4() + s4();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Array.prototype.indexOf fallback for IE8
 | |
|      * @param {Mixed} mixed
 | |
|      * @returns {Number}
 | |
|      */
 | |
|     if (!Array.prototype.indexOf) {
 | |
|         Array.prototype.indexOf = function(elt /*, from*/)
 | |
|         {
 | |
|             var len = this.length >>> 0;
 | |
| 
 | |
|             var from = Number(arguments[1]) || 0;
 | |
|             from = (from < 0)
 | |
|                     ? Math.ceil(from)
 | |
|                     : Math.floor(from);
 | |
|             if (from < 0)
 | |
|                 from += len;
 | |
| 
 | |
|             for (; from < len; from++)
 | |
|             {
 | |
|                 if (from in this &&
 | |
|                         this[from] === elt)
 | |
|                     return from;
 | |
|             }
 | |
|             return -1;
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     function parse_date(str) {
 | |
|         var match = str.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{1,2}:[0-9]{2}:[0-9]{2}$/);
 | |
|         if (match !== null && match.length > 0) {
 | |
|             var parts = str.split(" ");
 | |
|             var date = parts[0].split("-");
 | |
|             var time = parts[1].split(":");
 | |
|             return new Date(date[0], date[1] - 1, date[2], time[0], time[1], time[2]);
 | |
|         }
 | |
|         // Fallback for different date formats
 | |
|         var d = Date.parse(str);
 | |
|         if (!isNaN(d))
 | |
|             return d;
 | |
|         d = Date.parse(str.replace(/-/g, '/').replace('T', ' '));
 | |
|         if (!isNaN(d))
 | |
|             return d;
 | |
|         // Cant find anything
 | |
|         return new Date();
 | |
|     }
 | |
| 
 | |
|     function parse_times(diff, old_diff, total_duration, units, floor) {
 | |
|         var raw_time = {};
 | |
|         var raw_old_time = {};
 | |
|         var time = {};
 | |
|         var pct = {};
 | |
|         var old_pct = {};
 | |
|         var old_time = {};
 | |
| 
 | |
|         var greater_unit = null;
 | |
|         for(var i = 0; i < units.length; i++) {
 | |
|             var unit = units[i];
 | |
|             var maxUnits;
 | |
| 
 | |
|             if (greater_unit === null) {
 | |
|                 maxUnits = total_duration / secondsIn[unit];
 | |
|             }
 | |
|             else {
 | |
|                 maxUnits = secondsIn[greater_unit] / secondsIn[unit];
 | |
|             }
 | |
| 
 | |
|             var curUnits = (diff / secondsIn[unit]);
 | |
|             var oldUnits = (old_diff / secondsIn[unit]);
 | |
|             
 | |
|             if(floor) {
 | |
|                 if(curUnits > 0) curUnits = Math.floor(curUnits);
 | |
|                 else curUnits = Math.ceil(curUnits);
 | |
|                 if(oldUnits > 0) oldUnits = Math.floor(oldUnits);
 | |
|                 else oldUnits = Math.ceil(oldUnits);
 | |
|             }
 | |
|             
 | |
|             if (unit !== "Days") {
 | |
|                 curUnits = curUnits % maxUnits;
 | |
|                 oldUnits = oldUnits % maxUnits;
 | |
|             }
 | |
| 
 | |
|             raw_time[unit] = curUnits;
 | |
|             time[unit] = Math.abs(curUnits);
 | |
|             raw_old_time[unit] = oldUnits;
 | |
|             old_time[unit] = Math.abs(oldUnits);
 | |
|             pct[unit] = Math.abs(curUnits) / maxUnits;
 | |
|             old_pct[unit] = Math.abs(oldUnits) / maxUnits;
 | |
| 
 | |
|             greater_unit = unit;
 | |
|         }
 | |
| 
 | |
|         return {
 | |
|             raw_time: raw_time,
 | |
|             raw_old_time: raw_old_time,
 | |
|             time: time,
 | |
|             old_time: old_time,
 | |
|             pct: pct,
 | |
|             old_pct: old_pct
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     var TC_Instance_List = {};
 | |
|     function updateUsedWindow() {
 | |
|         if(typeof useWindow.TC_Instance_List !== "undefined") {
 | |
|             TC_Instance_List = useWindow.TC_Instance_List;
 | |
|         }
 | |
|         else {
 | |
|             useWindow.TC_Instance_List = TC_Instance_List;
 | |
|         }
 | |
|         initializeAnimationFrameHandler(useWindow);
 | |
|     };
 | |
|     
 | |
|     function initializeAnimationFrameHandler(w) {
 | |
|         var vendors = ['webkit', 'moz'];
 | |
|         for (var x = 0; x < vendors.length && !w.requestAnimationFrame; ++x) {
 | |
|             w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'];
 | |
|             w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'];
 | |
|         }
 | |
| 
 | |
|         if (!w.requestAnimationFrame || !w.cancelAnimationFrame) {
 | |
|             w.requestAnimationFrame = function(callback, element, instance) {
 | |
|                 if (typeof instance === "undefined")
 | |
|                     instance = {data: {last_frame: 0}};
 | |
|                 var currTime = new Date().getTime();
 | |
|                 var timeToCall = Math.max(0, 16 - (currTime - instance.data.last_frame));
 | |
|                 var id = w.setTimeout(function() {
 | |
|                     callback(currTime + timeToCall);
 | |
|                 }, timeToCall);
 | |
|                 instance.data.last_frame = currTime + timeToCall;
 | |
|                 return id;
 | |
|             };
 | |
|             w.cancelAnimationFrame = function(id) {
 | |
|                 clearTimeout(id);
 | |
|             };
 | |
|         }
 | |
|     };
 | |
|     
 | |
| 
 | |
|     var TC_Instance = function(element, options) {
 | |
|         this.element = element;
 | |
|         this.container;
 | |
|         this.listeners = null;
 | |
|         this.data = {
 | |
|             paused: false,
 | |
|             last_frame: 0,
 | |
|             animation_frame: null,
 | |
|             interval_fallback: null,
 | |
|             timer: false,
 | |
|             total_duration: null,
 | |
|             prev_time: null,
 | |
|             drawn_units: [],
 | |
|             text_elements: {
 | |
|                 Days: null,
 | |
|                 Hours: null,
 | |
|                 Minutes: null,
 | |
|                 Seconds: null
 | |
|             },
 | |
|             attributes: {
 | |
|                 canvas: null,
 | |
|                 context: null,
 | |
|                 item_size: null,
 | |
|                 line_width: null,
 | |
|                 radius: null,
 | |
|                 outer_radius: null
 | |
|             },
 | |
|             state: {
 | |
|                 fading: {
 | |
|                     Days: false,
 | |
|                     Hours: false,
 | |
|                     Minutes: false,
 | |
|                     Seconds: false
 | |
|                 }
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         this.config = null;
 | |
|         this.setOptions(options);
 | |
|         this.initialize();
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.clearListeners = function() {
 | |
|         this.listeners = { all: [], visible: [] };
 | |
|     };
 | |
|     
 | |
|     TC_Instance.prototype.addTime = function(seconds_to_add) {
 | |
|         if(this.data.attributes.ref_date instanceof Date) {
 | |
|             var d = this.data.attributes.ref_date;
 | |
|             d.setSeconds(d.getSeconds() + seconds_to_add);
 | |
|         }
 | |
|         else if(!isNaN(this.data.attributes.ref_date)) {
 | |
|             this.data.attributes.ref_date += (seconds_to_add * 1000);
 | |
|         }
 | |
|     };
 | |
|     
 | |
|     TC_Instance.prototype.initialize = function(clear_listeners) {
 | |
|         // Initialize drawn units
 | |
|         this.data.drawn_units = [];
 | |
|         for(var i = 0; i < Object.keys(this.config.time).length; i++) {
 | |
|             var unit = Object.keys(this.config.time)[i];
 | |
|             if (this.config.time[unit].show) {
 | |
|                 this.data.drawn_units.push(unit);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Avoid stacking
 | |
|         $(this.element).children('div.time_circles').remove();
 | |
| 
 | |
|         if (typeof clear_listeners === "undefined")
 | |
|             clear_listeners = true;
 | |
|         if (clear_listeners || this.listeners === null) {
 | |
|             this.clearListeners();
 | |
|         }
 | |
|         this.container = $("<div>");
 | |
|         this.container.addClass('time_circles');
 | |
|         this.container.appendTo(this.element);
 | |
|         
 | |
|         // Determine the needed width and height of TimeCircles
 | |
|         var height = this.element.offsetHeight;
 | |
|         var width = this.element.offsetWidth;
 | |
|         if (height === 0)
 | |
|             height = $(this.element).height();
 | |
|         if (width === 0)
 | |
|             width = $(this.element).width();
 | |
| 
 | |
|         if (height === 0 && width > 0)
 | |
|             height = width / this.data.drawn_units.length;
 | |
|         else if (width === 0 && height > 0)
 | |
|             width = height * this.data.drawn_units.length;
 | |
|         
 | |
|         // Create our canvas and set it to the appropriate size
 | |
|         var canvasElement = document.createElement('canvas');
 | |
|         canvasElement.width = width;
 | |
|         canvasElement.height = height;
 | |
|         
 | |
|         // Add canvas elements
 | |
|         this.data.attributes.canvas = $(canvasElement);
 | |
|         this.data.attributes.canvas.appendTo(this.container);
 | |
|         
 | |
|         // Check if the browser has browser support
 | |
|         var canvasSupported = isCanvasSupported();
 | |
|         // If the browser doesn't have browser support, check if explorer canvas is loaded
 | |
|         // (A javascript library that adds canvas support to browsers that don't have it)
 | |
|         if(!canvasSupported && typeof G_vmlCanvasManager !== "undefined") {
 | |
|             G_vmlCanvasManager.initElement(canvasElement);
 | |
|             limited_mode = true;
 | |
|             canvasSupported = true;
 | |
|         }
 | |
|         if(canvasSupported) {
 | |
|             this.data.attributes.context = canvasElement.getContext('2d');
 | |
|         }
 | |
| 
 | |
|         this.data.attributes.item_size = Math.min(width / this.data.drawn_units.length, height);
 | |
|         this.data.attributes.line_width = this.data.attributes.item_size * this.config.fg_width;
 | |
|         this.data.attributes.radius = ((this.data.attributes.item_size * 0.8) - this.data.attributes.line_width) / 2;
 | |
|         this.data.attributes.outer_radius = this.data.attributes.radius + 0.5 * Math.max(this.data.attributes.line_width, this.data.attributes.line_width * this.config.bg_width);
 | |
| 
 | |
|         // Prepare Time Elements
 | |
|         var i = 0;
 | |
|         for (var key in this.data.text_elements) {
 | |
|             if (!this.config.time[key].show)
 | |
|                 continue;
 | |
| 
 | |
|             var textElement = $("<div>");
 | |
|             textElement.addClass('textDiv_' + key);
 | |
|             textElement.css("top", Math.round(0.35 * this.data.attributes.item_size));
 | |
|             textElement.css("left", Math.round(i++ * this.data.attributes.item_size));
 | |
|             textElement.css("width", this.data.attributes.item_size);
 | |
|             textElement.appendTo(this.container);
 | |
| 
 | |
|             var headerElement = $("<h4>");
 | |
|             headerElement.text(this.config.time[key].text); // Options
 | |
|             headerElement.css("font-size", Math.round(this.config.text_size * this.data.attributes.item_size));
 | |
|             headerElement.css("line-height", Math.round(this.config.text_size * this.data.attributes.item_size) + "px");
 | |
|             headerElement.appendTo(textElement);
 | |
| 
 | |
|             var numberElement = $("<span>");
 | |
|             numberElement.css("font-size", Math.round(3 * this.config.text_size * this.data.attributes.item_size));
 | |
|             numberElement.css("line-height", Math.round(this.config.text_size * this.data.attributes.item_size) + "px");
 | |
|             numberElement.appendTo(textElement);
 | |
| 
 | |
|             this.data.text_elements[key] = numberElement;
 | |
|         }
 | |
| 
 | |
|         this.start();
 | |
|         if (!this.config.start) {
 | |
|             this.data.paused = true;
 | |
|         }
 | |
|         
 | |
|         // Set up interval fallback
 | |
|         var _this = this;
 | |
|         this.data.interval_fallback = useWindow.setInterval(function(){
 | |
|             _this.update.call(_this, true);
 | |
|         }, 100);
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.update = function(nodraw) {
 | |
|         if(typeof nodraw === "undefined") {
 | |
|             nodraw = false;
 | |
|         }
 | |
|         else if(nodraw && this.data.paused) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         if(limited_mode) {
 | |
|             //Per unit clearing doesn't work in IE8 using explorer canvas, so do it in one time. The downside is that radial fade cant be used
 | |
|             this.data.attributes.context.clearRect(0, 0, this.data.attributes.canvas[0].width, this.data.attributes.canvas[0].hright);
 | |
|         }
 | |
|         var diff, old_diff;
 | |
| 
 | |
|         var prevDate = this.data.prev_time;
 | |
|         var curDate = new Date();
 | |
|         this.data.prev_time = curDate;
 | |
| 
 | |
|         if (prevDate === null)
 | |
|             prevDate = curDate;
 | |
| 
 | |
|         // If not counting past zero, and time < 0, then simply draw the zero point once, and call stop
 | |
|         if (!this.config.count_past_zero) {
 | |
|             if (curDate > this.data.attributes.ref_date) {
 | |
|                 for(var i = 0; i < this.data.drawn_units.length; i++) {
 | |
|                     var key = this.data.drawn_units[i];
 | |
| 
 | |
|                     // Set the text value
 | |
|                     this.data.text_elements[key].text("0");
 | |
|                     var x = (i * this.data.attributes.item_size) + (this.data.attributes.item_size / 2);
 | |
|                     var y = this.data.attributes.item_size / 2;
 | |
|                     var color = this.config.time[key].color;
 | |
|                     this.drawArc(x, y, color, 0);
 | |
|                 }
 | |
|                 this.stop();
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Compare current time with reference
 | |
|         diff = (this.data.attributes.ref_date - curDate) / 1000;
 | |
|         old_diff = (this.data.attributes.ref_date - prevDate) / 1000;
 | |
| 
 | |
|         var floor = this.config.animation !== "smooth";
 | |
| 
 | |
|         var visible_times = parse_times(diff, old_diff, this.data.total_duration, this.data.drawn_units, floor);
 | |
|         var all_times = parse_times(diff, old_diff, secondsIn["Years"], allUnits, floor);
 | |
| 
 | |
|         var i = 0;
 | |
|         var j = 0;
 | |
|         var lastKey = null;
 | |
| 
 | |
|         var cur_shown = this.data.drawn_units.slice();
 | |
|         for (var i in allUnits) {
 | |
|             var key = allUnits[i];
 | |
| 
 | |
|             // Notify (all) listeners
 | |
|             if (Math.floor(all_times.raw_time[key]) !== Math.floor(all_times.raw_old_time[key])) {
 | |
|                 this.notifyListeners(key, Math.floor(all_times.time[key]), Math.floor(diff), "all");
 | |
|             }
 | |
| 
 | |
|             if (cur_shown.indexOf(key) < 0)
 | |
|                 continue;
 | |
| 
 | |
|             // Notify (visible) listeners
 | |
|             if (Math.floor(visible_times.raw_time[key]) !== Math.floor(visible_times.raw_old_time[key])) {
 | |
|                 this.notifyListeners(key, Math.floor(visible_times.time[key]), Math.floor(diff), "visible");
 | |
|             }
 | |
|             
 | |
|             if(!nodraw) {
 | |
|                 // Set the text value
 | |
|                 this.data.text_elements[key].text(Math.floor(Math.abs(visible_times.time[key])));
 | |
| 
 | |
|                 var x = (j * this.data.attributes.item_size) + (this.data.attributes.item_size / 2);
 | |
|                 var y = this.data.attributes.item_size / 2;
 | |
|                 var color = this.config.time[key].color;
 | |
| 
 | |
|                 if (this.config.animation === "smooth") {
 | |
|                     if (lastKey !== null && !limited_mode) {
 | |
|                         if (Math.floor(visible_times.time[lastKey]) > Math.floor(visible_times.old_time[lastKey])) {
 | |
|                             this.radialFade(x, y, color, 1, key);
 | |
|                             this.data.state.fading[key] = true;
 | |
|                         }
 | |
|                         else if (Math.floor(visible_times.time[lastKey]) < Math.floor(visible_times.old_time[lastKey])) {
 | |
|                             this.radialFade(x, y, color, 0, key);
 | |
|                             this.data.state.fading[key] = true;
 | |
|                         }
 | |
|                     }
 | |
|                     if (!this.data.state.fading[key]) {
 | |
|                         this.drawArc(x, y, color, visible_times.pct[key]);
 | |
|                     }
 | |
|                 }
 | |
|                 else {
 | |
|                     this.animateArc(x, y, color, visible_times.pct[key], visible_times.old_pct[key], (new Date()).getTime() + tick_duration);
 | |
|                 }
 | |
|             }
 | |
|             lastKey = key;
 | |
|             j++;
 | |
|         }
 | |
| 
 | |
|         // Dont request another update if we should be paused
 | |
|         if(this.data.paused || nodraw) {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // We need this for our next frame either way
 | |
|         var _this = this;
 | |
|         var update = function() {
 | |
|             _this.update.call(_this);
 | |
|         };
 | |
| 
 | |
|         // Either call next update immediately, or in a second
 | |
|         if (this.config.animation === "smooth") {
 | |
|             // Smooth animation, Queue up the next frame
 | |
|             this.data.animation_frame = useWindow.requestAnimationFrame(update, _this.element, _this);
 | |
|         }
 | |
|         else {
 | |
|             // Tick animation, Don't queue until very slightly after the next second happens
 | |
|             var delay = (diff % 1) * 1000;
 | |
|             if (delay < 0)
 | |
|                 delay = 1000 + delay;
 | |
|             delay += 50;
 | |
| 
 | |
|             _this.data.animation_frame = useWindow.setTimeout(function() {
 | |
|                 _this.data.animation_frame = useWindow.requestAnimationFrame(update, _this.element, _this);
 | |
|             }, delay);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.animateArc = function(x, y, color, target_pct, cur_pct, animation_end) {
 | |
|         if (this.data.attributes.context === null)
 | |
|             return;
 | |
| 
 | |
|         var diff = cur_pct - target_pct;
 | |
|         if (Math.abs(diff) > 0.5) {
 | |
|             if (target_pct === 0) {
 | |
|                 this.radialFade(x, y, color, 1);
 | |
|             }
 | |
|             else {
 | |
|                 this.radialFade(x, y, color, 0);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             var progress = (tick_duration - (animation_end - (new Date()).getTime())) / tick_duration;
 | |
|             if (progress > 1)
 | |
|                 progress = 1;
 | |
| 
 | |
|             var pct = (cur_pct * (1 - progress)) + (target_pct * progress);
 | |
|             this.drawArc(x, y, color, pct);
 | |
| 
 | |
|             //var show_pct =
 | |
|             if (progress >= 1)
 | |
|                 return;
 | |
|             var _this = this;
 | |
|             useWindow.requestAnimationFrame(function() {
 | |
|                 _this.animateArc(x, y, color, target_pct, cur_pct, animation_end);
 | |
|             }, this.element);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.drawArc = function(x, y, color, pct) {
 | |
|         if (this.data.attributes.context === null)
 | |
|             return;
 | |
| 
 | |
|         var clear_radius = Math.max(this.data.attributes.outer_radius, this.data.attributes.item_size / 2);
 | |
|         if(!limited_mode) {
 | |
|             this.data.attributes.context.clearRect(
 | |
|                     x - clear_radius,
 | |
|                     y - clear_radius,
 | |
|                     clear_radius * 2,
 | |
|                     clear_radius * 2
 | |
|                     );
 | |
|         }
 | |
|         
 | |
|         if (this.config.use_background) {
 | |
|             this.data.attributes.context.beginPath();
 | |
|             this.data.attributes.context.arc(x, y, this.data.attributes.radius, 0, 2 * Math.PI, false);
 | |
|             this.data.attributes.context.lineWidth = this.data.attributes.line_width * this.config.bg_width;
 | |
| 
 | |
|             // line color
 | |
|             this.data.attributes.context.strokeStyle = this.config.circle_bg_color;
 | |
|             this.data.attributes.context.stroke();
 | |
|         }
 | |
| 
 | |
|         // Direction
 | |
|         var startAngle, endAngle, counterClockwise;
 | |
|         var defaultOffset = (-0.5 * Math.PI);
 | |
|         var fullCircle = 2 * Math.PI;
 | |
|         startAngle = defaultOffset + (this.config.start_angle / 360 * fullCircle);
 | |
|         var offset = (2 * pct * Math.PI);
 | |
| 
 | |
|         if (this.config.direction === "Both") {
 | |
|             counterClockwise = false;
 | |
|             startAngle -= (offset / 2);
 | |
|             endAngle = startAngle + offset;
 | |
|         }
 | |
|         else {
 | |
|             if (this.config.direction === "Clockwise") {
 | |
|                 counterClockwise = false;
 | |
|                 endAngle = startAngle + offset;
 | |
|             }
 | |
|             else {
 | |
|                 counterClockwise = true;
 | |
|                 endAngle = startAngle - offset;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.data.attributes.context.beginPath();
 | |
|         this.data.attributes.context.arc(x, y, this.data.attributes.radius, startAngle, endAngle, counterClockwise);
 | |
|         this.data.attributes.context.lineWidth = this.data.attributes.line_width;
 | |
| 
 | |
|         // line color
 | |
|         this.data.attributes.context.strokeStyle = color;
 | |
|         this.data.attributes.context.stroke();
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.radialFade = function(x, y, color, from, key) {
 | |
|         // TODO: Make fade_time option
 | |
|         var rgb = hexToRgb(color);
 | |
|         var _this = this; // We have a few inner scopes here that will need access to our instance
 | |
| 
 | |
|         var step = 0.2 * ((from === 1) ? -1 : 1);
 | |
|         var i;
 | |
|         for (i = 0; from <= 1 && from >= 0; i++) {
 | |
|             // Create inner scope so our variables are not changed by the time the Timeout triggers
 | |
|             (function() {
 | |
|                 var delay = 50 * i;
 | |
|                 var rgba = "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + (Math.round(from * 10) / 10) + ")";
 | |
|                 useWindow.setTimeout(function() {
 | |
|                     _this.drawArc(x, y, rgba, 1);
 | |
|                 }, delay);
 | |
|             }());
 | |
|             from += step;
 | |
|         }
 | |
|         if (typeof key !== undefined) {
 | |
|             useWindow.setTimeout(function() {
 | |
|                 _this.data.state.fading[key] = false;
 | |
|             }, 50 * i);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.timeLeft = function() {
 | |
|         if (this.data.paused && typeof this.data.timer === "number") {
 | |
|             return this.data.timer;
 | |
|         }
 | |
|         var now = new Date();
 | |
|         return ((this.data.attributes.ref_date - now) / 1000);
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.start = function() {
 | |
|         useWindow.cancelAnimationFrame(this.data.animation_frame);
 | |
|         useWindow.clearTimeout(this.data.animation_frame)
 | |
| 
 | |
|         // Check if a date was passed in html attribute or jquery data
 | |
|         var attr_data_date = $(this.element).data('date');
 | |
|         if (typeof attr_data_date === "undefined") {
 | |
|             attr_data_date = $(this.element).attr('data-date');
 | |
|         }
 | |
|         if (typeof attr_data_date === "string") {
 | |
|             this.data.attributes.ref_date = parse_date(attr_data_date);
 | |
|         }
 | |
|         // Check if this is an unpause of a timer
 | |
|         else if (typeof this.data.timer === "number") {
 | |
|             if (this.data.paused) {
 | |
|                 this.data.attributes.ref_date = (new Date()).getTime() + (this.data.timer * 1000);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             // Try to get data-timer
 | |
|             var attr_data_timer = $(this.element).data('timer');
 | |
|             if (typeof attr_data_timer === "undefined") {
 | |
|                 attr_data_timer = $(this.element).attr('data-timer');
 | |
|             }
 | |
|             if (typeof attr_data_timer === "string") {
 | |
|                 attr_data_timer = parseFloat(attr_data_timer);
 | |
|             }
 | |
|             if (typeof attr_data_timer === "number") {
 | |
|                 this.data.timer = attr_data_timer;
 | |
|                 this.data.attributes.ref_date = (new Date()).getTime() + (attr_data_timer * 1000);
 | |
|             }
 | |
|             else {
 | |
|                 // data-timer and data-date were both not set
 | |
|                 // use config date
 | |
|                 this.data.attributes.ref_date = this.config.ref_date;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Start running
 | |
|         this.data.paused = false;
 | |
|         this.update.call(this);
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.restart = function() {
 | |
|         this.data.timer = false;
 | |
|         this.start();
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.stop = function() {
 | |
|         if (typeof this.data.timer === "number") {
 | |
|             this.data.timer = this.timeLeft(this);
 | |
|         }
 | |
|         // Stop running
 | |
|         this.data.paused = true;
 | |
|         useWindow.cancelAnimationFrame(this.data.animation_frame);
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.destroy = function() {
 | |
|         this.clearListeners();
 | |
|         this.stop();
 | |
|         useWindow.clearInterval(this.data.interval_fallback);
 | |
|         this.data.interval_fallback = null;
 | |
|         
 | |
|         this.container.remove();
 | |
|         $(this.element).removeAttr('data-tc-id');
 | |
|         $(this.element).removeData('tc-id');
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.setOptions = function(options) {
 | |
|         if (this.config === null) {
 | |
|             this.default_options.ref_date = new Date();
 | |
|             this.config = $.extend(true, {}, this.default_options);
 | |
|         }
 | |
|         $.extend(true, this.config, options);
 | |
| 
 | |
|         // Use window.top if use_top_frame is true
 | |
|         if(this.config.use_top_frame) {
 | |
|             useWindow = window.top;
 | |
|         }
 | |
|         else {
 | |
|             useWindow = window;
 | |
|         }
 | |
|         updateUsedWindow();
 | |
|         
 | |
|         this.data.total_duration = this.config.total_duration;
 | |
|         if (typeof this.data.total_duration === "string") {
 | |
|             if (typeof secondsIn[this.data.total_duration] !== "undefined") {
 | |
|                 // If set to Years, Months, Days, Hours or Minutes, fetch the secondsIn value for that
 | |
|                 this.data.total_duration = secondsIn[this.data.total_duration];
 | |
|             }
 | |
|             else if (this.data.total_duration === "Auto") {
 | |
|                 // If set to auto, total_duration is the size of 1 unit, of the unit type bigger than the largest shown
 | |
|                 for(var i = 0; i < Object.keys(this.config.time).length; i++) {
 | |
|                     var unit = Object.keys(this.config.time)[i];
 | |
|                     if (this.config.time[unit].show) {
 | |
|                         this.data.total_duration = secondsIn[nextUnits[unit]];
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 // If it's a string, but neither of the above, user screwed up.
 | |
|                 this.data.total_duration = secondsIn["Years"];
 | |
|                 console.error("Valid values for TimeCircles config.total_duration are either numeric, or (string) Years, Months, Days, Hours, Minutes, Auto");
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.addListener = function(f, context, type) {
 | |
|         if (typeof f !== "function")
 | |
|             return;
 | |
|         if (typeof type === "undefined")
 | |
|             type = "visible";
 | |
|         this.listeners[type].push({func: f, scope: context});
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.notifyListeners = function(unit, value, total, type) {
 | |
|         for (var i = 0; i < this.listeners[type].length; i++) {
 | |
|             var listener = this.listeners[type][i];
 | |
|             listener.func.apply(listener.scope, [unit, value, total]);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     TC_Instance.prototype.default_options = {
 | |
|         ref_date: new Date(),
 | |
|         start: true,
 | |
|         animation: "smooth",
 | |
|         count_past_zero: true,
 | |
|         circle_bg_color: "#60686F",
 | |
|         use_background: true,
 | |
|         fg_width: 0.1,
 | |
|         bg_width: 1.2,
 | |
|         text_size: 0.07,
 | |
|         total_duration: "Auto",
 | |
|         direction: "Clockwise",
 | |
|         use_top_frame: false,
 | |
|         start_angle: 0,
 | |
|         time: {
 | |
|             Days: {
 | |
|                 show: true,
 | |
|                 text: "Days",
 | |
|                 color: "#FC6"
 | |
|             },
 | |
|             Hours: {
 | |
|                 show: true,
 | |
|                 text: "Hours",
 | |
|                 color: "#9CF"
 | |
|             },
 | |
|             Minutes: {
 | |
|                 show: true,
 | |
|                 text: "Minutes",
 | |
|                 color: "#BFB"
 | |
|             },
 | |
|             Seconds: {
 | |
|                 show: true,
 | |
|                 text: "Seconds",
 | |
|                 color: "#F99"
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     // Time circle class
 | |
|     var TC_Class = function(elements, options) {
 | |
|         this.elements = elements;
 | |
|         this.options = options;
 | |
|         this.foreach();
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.getInstance = function(element) {
 | |
|         var instance;
 | |
| 
 | |
|         var cur_id = $(element).data("tc-id");
 | |
|         if (typeof cur_id === "undefined") {
 | |
|             cur_id = guid();
 | |
|             $(element).attr("data-tc-id", cur_id);
 | |
|         }
 | |
|         if (typeof TC_Instance_List[cur_id] === "undefined") {
 | |
|             var options = this.options;
 | |
|             var element_options = $(element).data('options');
 | |
|             if (typeof element_options === "string") {
 | |
|                 element_options = JSON.parse(element_options);
 | |
|             }
 | |
|             if (typeof element_options === "object") {
 | |
|                 options = $.extend(true, {}, this.options, element_options);
 | |
|             }
 | |
|             instance = new TC_Instance(element, options);
 | |
|             TC_Instance_List[cur_id] = instance;
 | |
|         }
 | |
|         else {
 | |
|             instance = TC_Instance_List[cur_id];
 | |
|             if (typeof this.options !== "undefined") {
 | |
|                 instance.setOptions(this.options);
 | |
|             }
 | |
|         }
 | |
|         return instance;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.addTime = function(seconds_to_add) {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.addTime(seconds_to_add);
 | |
|         });
 | |
|     };
 | |
|     
 | |
|     TC_Class.prototype.foreach = function(callback) {
 | |
|         var _this = this;
 | |
|         this.elements.each(function() {
 | |
|             var instance = _this.getInstance(this);
 | |
|             if (typeof callback === "function") {
 | |
|                 callback(instance);
 | |
|             }
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.start = function() {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.start();
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.stop = function() {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.stop();
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.restart = function() {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.restart();
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.rebuild = function() {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.initialize(false);
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.getTime = function() {
 | |
|         return this.getInstance(this.elements[0]).timeLeft();
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.addListener = function(f, type) {
 | |
|         if (typeof type === "undefined")
 | |
|             type = "visible";
 | |
|         var _this = this;
 | |
|         this.foreach(function(instance) {
 | |
|             instance.addListener(f, _this.elements, type);
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.destroy = function() {
 | |
|         this.foreach(function(instance) {
 | |
|             instance.destroy();
 | |
|         });
 | |
|         return this;
 | |
|     };
 | |
| 
 | |
|     TC_Class.prototype.end = function() {
 | |
|         return this.elements;
 | |
|     };
 | |
| 
 | |
|     $.fn.TimeCircles = function(options) {
 | |
|         return new TC_Class(this, options);
 | |
|     };
 | |
| }(jQuery));
 |