// Constructor for CoolClock objects window.CoolClock = function(options) { return this.init(options); } // Config contains some defaults, and clock skins CoolClock.config = { tickDelay: 1000, longTickDelay: 15000, defaultRadius: 85, renderRadius: 100, defaultSkin: "chunkySwiss", defaultFont: "15px sans-serif", // Should be in skin probably... // (TODO: allow skinning of digital display) skins: { // There are more skins in moreskins.js // Try making your own skin by copy/pasting one of these and tweaking it swissRail: { outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 }, hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } }, chunkySwiss: { outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } }, chunkySwissOnBlack: { outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 }, smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 }, largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 }, hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 }, minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 }, secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } } }, // Test for IE so we can nurse excanvas in a couple of places isIE: !!document.all, // Will store (a reference to) each clock here, indexed by the id of the canvas element clockTracker: {}, // For giving a unique id to coolclock canvases with no id noIdCount: 0 }; // Define the CoolClock object's methods CoolClock.prototype = { // Initialise using the parameters parsed from the colon delimited class init: function(options) { // Parse and store the options this.canvasId = options.canvasId; this.skinId = options.skinId || CoolClock.config.defaultSkin; this.font = options.font || CoolClock.config.defaultFont; this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius; this.renderRadius = options.renderRadius || CoolClock.config.renderRadius; this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true; this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null; this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false; this.logClock = typeof options.logClock == "boolean" ? options.logClock : false; this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false; this.showAmPm = typeof options.showAmPm == "boolean" ? options.showAmPm : false; this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ]; // Get the canvas element this.canvas = document.getElementById(this.canvasId); // Make the canvas the requested size. It's always square. this.canvas.setAttribute("width",this.displayRadius*2); this.canvas.setAttribute("height",this.displayRadius*2); this.canvas.style.width = this.displayRadius*2 + "px"; this.canvas.style.height = this.displayRadius*2 + "px"; // Determine by what factor to relate skin values to canvas positions. // renderRadius is the max skin positional value before leaving the // canvas. displayRadius is half the width and height of the canvas in // pixels. If they are equal, there is a 1:1 relation of skin position // values to canvas pixels. Setting both to 200 allows 100px of space // around clock skins to add your own things: this is due to current // skins maxing out at a positional value of 100. this.scale = this.displayRadius / this.renderRadius; // Initialise canvas context this.ctx = this.canvas.getContext("2d"); this.ctx.scale(this.scale,this.scale); // Keep track of this object CoolClock.config.clockTracker[this.canvasId] = this; // should we be running the clock? this.active = true; this.tickTimeout = null; // Start the clock going this.tick(); return this; }, // Draw a circle at point x,y with params as defined in skin fullCircleAt: function(x,y,skin) { this.ctx.save(); this.ctx.globalAlpha = skin.alpha; this.ctx.lineWidth = skin.lineWidth; if (!CoolClock.config.isIE) { this.ctx.beginPath(); } if (CoolClock.config.isIE) { // excanvas doesn't scale line width so we will do it here this.ctx.lineWidth = this.ctx.lineWidth * this.scale; } this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false); if (CoolClock.config.isIE) { // excanvas doesn't close the circle so let's fill in the tiny gap this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false); } if (skin.fillColor) { this.ctx.fillStyle = skin.fillColor this.ctx.fill(); } if (skin.color) { this.ctx.strokeStyle = skin.color; this.ctx.stroke(); } this.ctx.restore(); }, // Draw some text centered vertically and horizontally drawTextAt: function(theText,x,y,skin) { if (!skin) skin = this.getSkin(); this.ctx.save(); this.ctx.font = skin.font || this.font; var tSize = this.ctx.measureText(theText); // TextMetrics rarely returns a height property: use baseline instead. if (!tSize.height) { tSize.height = 0; this.ctx.textBaseline = 'middle'; } this.ctx.fillText(theText, x - tSize.width/2, y - tSize.height/2); this.ctx.restore(); }, lpad2: function(num) { return (num < 10 ? '0' : '') + num; }, tickAngle: function(second) { // Log algorithm by David Bradshaw var tweak = 3; // If it's lower the one second mark looks wrong (?) if (this.logClock) { return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)); } else if (this.logClockRev) { // Flip the seconds then flip the angle (trickiness) second = (60 - second) % 60; return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak))); } else { return second/60.0; } }, timeText: function(hour,min,sec) { var c = CoolClock.config; return '' + (this.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' + this.lpad2(min) + (c.showSecondHand ? ':' + this.lpad2(sec) : '') + (this.showAmPm ? (hour < 12 ? ' am' : ' pm') : '') ; }, // Draw a radial line by rotating then drawing a straight line // Ha ha, I think I've accidentally used Taus, (see http://tauday.com/) radialLineAtAngle: function(angleFraction,skin) { this.ctx.save(); this.ctx.translate(this.renderRadius,this.renderRadius); this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5)); this.ctx.globalAlpha = skin.alpha; this.ctx.strokeStyle = skin.color; this.ctx.lineWidth = skin.lineWidth; if (CoolClock.config.isIE) // excanvas doesn't scale line width so we will do it here this.ctx.lineWidth = this.ctx.lineWidth * this.scale; if (skin.radius) { this.fullCircleAt(skin.startAt,0,skin) } else { this.ctx.beginPath(); this.ctx.moveTo(skin.startAt,0) this.ctx.lineTo(skin.endAt,0); this.ctx.stroke(); } this.ctx.restore(); }, render: function(hour,min,sec) { // Get the skin var skin = this.getSkin(); // Clear this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2); // Draw the outer edge of the clock if (skin.outerBorder) this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder); // Draw the tick marks. Every 5th one is a big one for (var i=0;i<60;i++) { (i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator); !(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator); } // Write the time if (this.showDigital) { this.drawTextAt( this.timeText(hour,min,sec), this.renderRadius, this.renderRadius+this.renderRadius/2 ); } var hourA = (hour%12)*5 + min/12.0, minA = min + sec/60.0, secA = sec; // Draw the hands if (skin.hourHand) this.radialLineAtAngle(this.tickAngle(hourA),skin.hourHand); if (skin.minuteHand) this.radialLineAtAngle(this.tickAngle(minA),skin.minuteHand); if (this.showSecondHand && skin.secondHand) this.radialLineAtAngle(this.tickAngle(secA),skin.secondHand); // Hands decoration - not in IE if (!CoolClock.config.isIE) { if (skin.hourDecoration) this.radialLineAtAngle(this.tickAngle(hourA), skin.hourDecoration); if (skin.minDecoration) this.radialLineAtAngle(this.tickAngle(minA), skin.minDecoration); if (this.showSecondHand && skin.secondDecoration) this.radialLineAtAngle(this.tickAngle(secA),skin.secondDecoration); } if (this.extraRender) { this.extraRender(hour,min,sec); } }, // Check the time and display the clock refreshDisplay: function() { var now = new Date(); if (this.gmtOffset != null) { // Use GMT + gmtOffset var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60)); this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds()); } else { // Use local time this.render(now.getHours(),now.getMinutes(),now.getSeconds()); } }, // Set timeout to trigger a tick in the future nextTick: function() { this.tickTimeout = setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay); }, // Check the canvas element hasn't been removed stillHere: function() { return document.getElementById(this.canvasId) != null; }, // Stop this clock stop: function() { this.active = false; clearTimeout(this.tickTimeout); }, // Start this clock start: function() { if (!this.active) { this.active = true; this.tick(); } }, // Main tick handler. Refresh the clock then setup the next tick tick: function() { if (this.stillHere() && this.active) { this.refreshDisplay() this.nextTick(); } }, getSkin: function() { var skin = CoolClock.config.skins[this.skinId]; if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin]; return skin; } }; // Find all canvas elements that have the CoolClock class and turns them into clocks CoolClock.findAndCreateClocks = function() { // (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery) var canvases = document.getElementsByTagName("canvas"); for (var i=0;i // If you do have jQuery and it's loaded already then we can do it right now if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks);