diff --git a/inc/TimeCircles.js b/inc/TimeCircles.js index 24b7be2..04af628 100644 --- a/inc/TimeCircles.js +++ b/inc/TimeCircles.js @@ -14,18 +14,18 @@ * of the private classes within the relevant element collection **/ (function($) { - - // Used to disable drawing on IE8, which can't use canvas - var cant_draw = false; + + // 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) { + if (debug) { console.log(msg); } } - + var allUnits = ["Days", "Hours", "Minutes", "Seconds"]; var nextUnits = { Seconds: "Minutes", @@ -41,7 +41,7 @@ Months: 2678400, Years: 31536000 }; - + /** * Converts hex color code into object containing integer values for the r,g,b use * This function (hexToRgb) originates from: @@ -62,6 +62,11 @@ 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: @@ -69,8 +74,8 @@ */ function s4() { return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); + .toString(16) + .substring(1); } /** @@ -79,9 +84,37 @@ */ function guid() { return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - 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) { @@ -92,13 +125,15 @@ } // Fallback for different date formats var d = Date.parse(str); - if(!isNaN(d)) return d; + if (!isNaN(d)) + return d; d = Date.parse(str.replace(/-/g, '/').replace('T', ' ')); - if(!isNaN(d)) return d; + 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 = {}; @@ -106,39 +141,41 @@ var pct = {}; var old_pct = {}; var old_time = {}; - + var greater_unit = null; - for(var i in units) { + for (var i in units) { var unit = units[i]; var maxUnits; - - if(greater_unit === null) { + + 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) curUnits = Math.floor(curUnits); - if(floor) oldUnits = Math.floor(oldUnits); - - if(unit !== "Days"){ + if (floor) + curUnits = Math.floor(curUnits); + if (floor) + oldUnits = Math.floor(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, @@ -148,26 +185,27 @@ old_pct: old_pct }; } - + var TC_Instance_List = {}; // Try fetch/share instance - if(window !== window.top && typeof window.top.TC_Instance_List !== "undefined") { + if (window !== window.top && typeof window.top.TC_Instance_List !== "undefined") { TC_Instance_List = window.top.TC_Instance_List; } else { window.top.TC_Instance_List = TC_Instance_List; } - - (function(){ + + (function() { var vendors = ['webkit', 'moz']; - for(var x = 0; x < vendors.length && !window.top.requestAnimationFrame; ++x) { - window.top.requestAnimationFrame = window.top[vendors[x]+'RequestAnimationFrame']; - window.top.cancelAnimationFrame = window.top[vendors[x]+'CancelAnimationFrame']; - } - + for (var x = 0; x < vendors.length && !window.top.requestAnimationFrame; ++x) { + window.top.requestAnimationFrame = window.top[vendors[x] + 'RequestAnimationFrame']; + window.top.cancelAnimationFrame = window.top[vendors[x] + 'CancelAnimationFrame']; + } + if (!window.top.requestAnimationFrame || !window.top.cancelAnimationFrame) { window.top.requestAnimationFrame = function(callback, element, instance) { - if(typeof instance === "undefined") instance = { data: { last_frame: 0 } }; + 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 = window.top.setTimeout(function() { @@ -181,7 +219,7 @@ }; } })(); - + var TC_Instance = function(element, options) { this.element = element; @@ -218,53 +256,69 @@ } } }; - + this.config = null; this.setOptions(options); this.initialize(); }; - + TC_Instance.prototype.initialize = function(clear_listeners) { // Initialize drawn units this.data.drawn_units = []; - for(var unit in this.config.time) { - if(this.config.time[unit].show){ + for (var unit in this.config.time) { + 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.listeners = { all: [], visible: [] }; + + if (typeof clear_listeners === "undefined") + clear_listeners = true; + if (clear_listeners || this.listeners === null) { + this.listeners = {all: [], visible: []}; } this.container = $("
"); this.container.addClass('time_circles'); this.container.appendTo(this.element); - - this.data.attributes.canvas = $(""); - try { - this.data.attributes.context = this.data.attributes.canvas[0].getContext('2d'); - } - catch(e) { - cant_draw = true; - } + // 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) + 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; - 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; - this.data.attributes.canvas[0].height = height; - this.data.attributes.canvas[0].width = width; + // 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(this.data.attributes.canvas[0].width / this.data.drawn_units.length, this.data.attributes.canvas[0].height); + 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); @@ -272,45 +326,51 @@ // Prepare Time Elements var i = 0; for (var key in this.data.text_elements) { - if(!this.config.time[key].show) continue; - + if (!this.config.time[key].show) + continue; + var textElement = $("
"); 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 = $("

"); headerElement.text(this.config.time[key].text); // Options headerElement.css("font-size", Math.round(0.07 * this.data.attributes.item_size)); headerElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px"); headerElement.appendTo(textElement); - + var numberElement = $(""); numberElement.css("font-size", Math.round(0.21 * this.data.attributes.item_size)); numberElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px"); numberElement.appendTo(textElement); - + this.data.text_elements[key] = numberElement; } - + if (this.config.start && this.data.paused === false) this.start(); }; - + TC_Instance.prototype.update = function() { + 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 (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) { + if (curDate > this.data.attributes.ref_date) { for (var i in this.data.drawn_units) { // TODO: listeners! var key = this.data.drawn_units[i]; @@ -326,45 +386,46 @@ 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])) { + 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; - + + 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])) { + 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"); } - + // 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) { + + 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; @@ -384,36 +445,38 @@ lastKey = key; j++; } - + // 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") { + if (this.config.animation === "smooth") { // Smooth animation, Queue up the next frame this.data.animation_frame = window.top.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; + if (delay < 0) + delay = 1000 + delay; delay += 50; - - _this.data.animation_frame = window.top.setTimeout(function(){ + + _this.data.animation_frame = window.top.setTimeout(function() { _this.data.animation_frame = window.top.requestAnimationFrame(update, _this.element, _this); }, delay); } }; - + TC_Instance.prototype.animateArc = function(x, y, color, target_pct, cur_pct, animation_end) { - if(cant_draw) return; - + if (this.data.attributes.context === null) + return; + var diff = cur_pct - target_pct; - if(Math.abs(diff) > 0.5) { - if(target_pct === 0) { + if (Math.abs(diff) > 0.5) { + if (target_pct === 0) { this.radialFade(x, y, color, 1); } else { @@ -422,31 +485,36 @@ } else { var progress = (tick_duration - (animation_end - (new Date()).getTime())) / tick_duration; - if(progress > 1) progress = 1; - + 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; + if (progress >= 1) + return; var _this = this; - window.top.requestAnimationFrame(function(){ + window.top.requestAnimationFrame(function() { _this.animateArc(x, y, color, target_pct, cur_pct, animation_end); - }, this.element, null); + }, this.element); } }; - + TC_Instance.prototype.drawArc = function(x, y, color, pct) { - if(cant_draw) return; - - var clear_radius = Math.max(this.data.attributes.outer_radius, this.data.attributes.item_size / 2); - this.data.attributes.context.clearRect( - x - clear_radius, - y - clear_radius, - clear_radius * 2, - clear_radius * 2 - ); + 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); @@ -456,21 +524,21 @@ 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") { + + if (this.config.direction === "Both") { counterClockwise = false; startAngle -= (offset / 2); endAngle = startAngle + offset; } else { - if(this.config.direction === "Clockwise") { + if (this.config.direction === "Clockwise") { counterClockwise = false; endAngle = startAngle + offset; } @@ -507,7 +575,7 @@ }()); from += step; } - if(typeof key !== undefined) { + if (typeof key !== undefined) { window.top.setTimeout(function() { _this.data.state.fading[key] = false; }, 50 * i); @@ -518,11 +586,11 @@ var now = new Date(); return ((this.data.attributes.ref_date - now) / 1000); }; - + TC_Instance.prototype.start = function() { window.top.cancelAnimationFrame(this.data.animation_frame); window.top.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") { @@ -533,7 +601,7 @@ } // Check if this is an unpause of a timer else if (typeof this.data.timer === "number") { - if(this.data.paused) { + if (this.data.paused) { this.data.attributes.ref_date = (new Date()).getTime() + (this.data.timer * 1000); } } @@ -546,7 +614,7 @@ if (typeof attr_data_timer === "string") { attr_data_timer = parseFloat(attr_data_timer); } - if(typeof attr_data_timer === "number") { + if (typeof attr_data_timer === "number") { this.data.timer = attr_data_timer; this.data.attributes.ref_date = (new Date()).getTime() + (attr_data_timer * 1000); } @@ -559,7 +627,7 @@ // Start running this.data.paused = false; - this.update(); + this.update.call(this); }; TC_Instance.prototype.restart = function() { @@ -582,24 +650,24 @@ $(this.element).removeAttr('data-tc-id'); $(this.element).removeData('tc-id'); }; - + TC_Instance.prototype.setOptions = function(options) { - if(this.config === null) { + if (this.config === null) { this.default_options.ref_date = new Date(); this.config = $.extend(true, {}, this.default_options); } $.extend(true, this.config, options); - + this.data.total_duration = this.config.total_duration; - if(typeof this.data.total_duration === "string") { - if(typeof secondsIn[this.data.total_duration] !== "undefined") { + 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 unit in this.config.time) { - if(this.config.time[unit].show) { + for (var unit in this.config.time) { + if (this.config.time[unit].show) { this.data.total_duration = secondsIn[nextUnits[unit]]; break; } @@ -612,20 +680,22 @@ } } }; - + TC_Instance.prototype.addListener = function(f, context, type) { - if(typeof f !== "function") return; - if(typeof type === "undefined") type = "visible"; + 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++) { + 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, @@ -668,10 +738,10 @@ 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(); @@ -680,10 +750,10 @@ if (typeof TC_Instance_List[cur_id] === "undefined") { var options = this.options; var element_options = $(element).data('options'); - if(typeof element_options === "string") { + if (typeof element_options === "string") { element_options = JSON.parse(element_options); } - if(typeof element_options === "object") { + if (typeof element_options === "object") { options = $.extend(true, {}, this.options, element_options); } instance = new TC_Instance(element, options); @@ -708,7 +778,7 @@ }); return this; }; - + TC_Class.prototype.start = function() { this.foreach(function(instance) { instance.start(); @@ -722,34 +792,35 @@ }); 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"; + 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(); diff --git a/timecircles.jquery.json b/timecircles.jquery.json index f379c34..9d90cd8 100644 --- a/timecircles.jquery.json +++ b/timecircles.jquery.json @@ -1,6 +1,6 @@ { "name": "timecircles", - "version": "1.3.2", + "version": "1.4.0", "title": "jQuery TimeCircles", "author": { "name" : "Wim Barelds",