| 1 |
/** |
| 2 |
* CoolClock 3.0.1 |
| 3 |
* |
| 4 |
Copyright (c) 2010-2013, Simon Baird. |
| 5 |
All rights reserved. |
| 6 |
* |
| 7 |
* Applied path fix https://github.com/henrahmagix/CoolClock/commit/00d48a01b1aeadbeb0b7d167aca892de78659b76 |
| 8 |
* Added responsive styling by RavanH (lines 136/137) 26112014 |
| 9 |
* |
| 10 |
Redistribution and use in source and binary forms, with or without |
| 11 |
modification, are permitted provided that the following conditions are met: |
| 12 |
- Redistributions of source code must retain the above copyright notice, |
| 13 |
this list of conditions and the following disclaimer. |
| 14 |
- Redistributions in binary form must reproduce the above copyright notice, |
| 15 |
this list of conditions and the following disclaimer in the documentation |
| 16 |
and/or other materials provided with the distribution. |
| 17 |
- Neither the name Simon Baird nor the names of other contributors may be |
| 18 |
used to endorse or promote products derived from this software without |
| 19 |
specific prior written permission. |
| 20 |
* |
| 21 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 22 |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 23 |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 24 |
ARE DISCLAIMED. IN NO EVENT SHALL SIMON BAIRD BE LIABLE FOR ANY DIRECT, |
| 25 |
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 26 |
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 27 |
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 28 |
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 |
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 30 |
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 |
* |
| 32 |
* Display an analog clock using canvas. |
| 33 |
* http://randomibis.com/coolclock/ |
| 34 |
* |
| 35 |
*/ |
| 36 |
|
| 37 |
// Constructor for CoolClock objects |
| 38 |
window.CoolClock = function(options) { |
| 39 |
return this.init(options); |
| 40 |
} |
| 41 |
|
| 42 |
// Config contains some defaults, and clock skins |
| 43 |
CoolClock.config = { |
| 44 |
tickDelay: 1000, |
| 45 |
longTickDelay: 15000, |
| 46 |
defaultRadius: 85, |
| 47 |
renderRadius: 100, |
| 48 |
defaultSkin: "chunkySwiss", |
| 49 |
defaultFont: "15px sans-serif", |
| 50 |
|
| 51 |
skins: { |
| 52 |
// There are more skins in moreskins.js |
| 53 |
// Try making your own skin by copy/pasting one of these and tweaking it |
| 54 |
swissRail: { |
| 55 |
outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 }, |
| 56 |
smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 }, |
| 57 |
largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 }, |
| 58 |
hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, |
| 59 |
minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, |
| 60 |
secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, |
| 61 |
secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } |
| 62 |
}, |
| 63 |
chunkySwiss: { |
| 64 |
outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 }, |
| 65 |
smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, |
| 66 |
largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, |
| 67 |
hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, |
| 68 |
minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, |
| 69 |
secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, |
| 70 |
secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } |
| 71 |
}, |
| 72 |
chunkySwissOnBlack: { |
| 73 |
outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 }, |
| 74 |
smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 }, |
| 75 |
largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 }, |
| 76 |
hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 }, |
| 77 |
minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 }, |
| 78 |
secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, |
| 79 |
secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } |
| 80 |
} |
| 81 |
|
| 82 |
}, |
| 83 |
|
| 84 |
// Test for IE so we can nurse excanvas in a couple of places |
| 85 |
isIE: !!document.all, |
| 86 |
|
| 87 |
// Will store (a reference to) each clock here, indexed by the id of the canvas element |
| 88 |
clockTracker: {}, |
| 89 |
|
| 90 |
// For giving a unique id to coolclock canvases with no id |
| 91 |
noIdCount: 0 |
| 92 |
} |
| 93 |
|
| 94 |
// Main tick handler. Refresh all the clocks then setup the next tick |
| 95 |
CoolClock.tick = function() { |
| 96 |
|
| 97 |
for (var clockId in CoolClock.config.clockTracker) { |
| 98 |
var clock = CoolClock.config.clockTracker[clockId]; |
| 99 |
if (clock.stillHere() && clock.active) { |
| 100 |
clock.refreshDisplay() |
| 101 |
} |
| 102 |
} |
| 103 |
this.tickTimeout = setTimeout(CoolClock.tick, CoolClock.config.tickDelay); |
| 104 |
}; |
| 105 |
|
| 106 |
// Define the CoolClock object's methods |
| 107 |
CoolClock.prototype = { |
| 108 |
|
| 109 |
// Initialise using the parameters parsed from the colon delimited class |
| 110 |
init: function(options) { |
| 111 |
// Parse and store the options |
| 112 |
this.canvasId = options.canvasId; |
| 113 |
this.skinId = options.skinId || CoolClock.config.defaultSkin; |
| 114 |
this.font = options.font || CoolClock.config.defaultFont; |
| 115 |
this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius; |
| 116 |
this.renderRadius = options.renderRadius || CoolClock.config.renderRadius; |
| 117 |
this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true; |
| 118 |
this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null; |
| 119 |
this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false; |
| 120 |
this.showDigital24 = typeof options.showDigital24 == "boolean" ? options.showDigital24 : false; |
| 121 |
this.showDate = typeof options.showDate == "boolean" ? options.showDate : false; |
| 122 |
this.logClock = typeof options.logClock == "boolean" ? options.logClock : false; |
| 123 |
this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false; |
| 124 |
|
| 125 |
this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ]; |
| 126 |
|
| 127 |
// Get the canvas element |
| 128 |
this.canvas = document.getElementById(this.canvasId); |
| 129 |
|
| 130 |
// Make the canvas the requested size. It's always square. |
| 131 |
this.canvas.setAttribute("width",this.displayRadius*2); |
| 132 |
this.canvas.setAttribute("height",this.displayRadius*2); |
| 133 |
this.canvas.style.width = this.displayRadius*2 + "px"; |
| 134 |
//this.canvas.style.height = this.displayRadius*2 + "px"; |
| 135 |
// some responsiveness please: |
| 136 |
this.canvas.style.maxWidth = "100%"; |
| 137 |
this.canvas.style.height = "auto"; |
| 138 |
|
| 139 |
// Determine by what factor to relate skin values to canvas positions. |
| 140 |
// renderRadius is the max skin positional value before leaving the |
| 141 |
// canvas. displayRadius is half the width and height of the canvas in |
| 142 |
// pixels. If they are equal, there is a 1:1 relation of skin position |
| 143 |
// values to canvas pixels. Setting both to 200 allows 100px of space |
| 144 |
// around clock skins to add your own things: this is due to current |
| 145 |
// skins maxing out at a positional value of 100. |
| 146 |
this.scale = this.displayRadius / this.renderRadius; |
| 147 |
|
| 148 |
// Initialise canvas context |
| 149 |
this.ctx = this.canvas.getContext("2d"); |
| 150 |
this.ctx.scale(this.scale,this.scale); |
| 151 |
|
| 152 |
// Keep track of this object |
| 153 |
CoolClock.config.clockTracker[this.canvasId] = this; |
| 154 |
|
| 155 |
// Start the clock going |
| 156 |
this.start(); |
| 157 |
|
| 158 |
return this; |
| 159 |
}, |
| 160 |
|
| 161 |
// Draw a circle at point x,y with params as defined in skin |
| 162 |
fullCircleAt: function(x,y,skin) { |
| 163 |
this.ctx.save(); |
| 164 |
this.ctx.globalAlpha = skin.alpha; |
| 165 |
this.ctx.lineWidth = skin.lineWidth; |
| 166 |
|
| 167 |
if (CoolClock.config.isIE) { |
| 168 |
// excanvas doesn't scale line width so we will do it here |
| 169 |
this.ctx.lineWidth = this.ctx.lineWidth * this.scale; |
| 170 |
} |
| 171 |
|
| 172 |
this.ctx.beginPath(); |
| 173 |
this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false); |
| 174 |
|
| 175 |
if (CoolClock.config.isIE) { |
| 176 |
// excanvas doesn't close the circle so let's fill in the tiny gap |
| 177 |
this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false); |
| 178 |
} |
| 179 |
|
| 180 |
if (skin.fillColor) { |
| 181 |
this.ctx.fillStyle = skin.fillColor |
| 182 |
this.ctx.fill(); |
| 183 |
} |
| 184 |
if (skin.color) { |
| 185 |
this.ctx.strokeStyle = skin.color; |
| 186 |
this.ctx.stroke(); |
| 187 |
} |
| 188 |
this.ctx.restore(); |
| 189 |
}, |
| 190 |
|
| 191 |
// Draw some text centered vertically and horizontally |
| 192 |
drawTextAt: function(theText,x,y,skin) { |
| 193 |
if (!skin) skin = this.getSkin(); |
| 194 |
this.ctx.save(); |
| 195 |
this.ctx.font = skin.font || this.font; |
| 196 |
var tSize = this.ctx.measureText(theText); |
| 197 |
// TextMetrics rarely returns a height property: use baseline instead. |
| 198 |
if (!tSize.height) { |
| 199 |
tSize.height = 0; |
| 200 |
this.ctx.textBaseline = 'middle'; |
| 201 |
} |
| 202 |
this.ctx.fillText(theText, x - tSize.width/2, y - tSize.height/2); |
| 203 |
this.ctx.restore(); |
| 204 |
}, |
| 205 |
|
| 206 |
lpad2: function(num) { |
| 207 |
return (num < 10 ? '0' : '') + num; |
| 208 |
}, |
| 209 |
|
| 210 |
tickAngle: function(second) { |
| 211 |
// Log algorithm by David Bradshaw |
| 212 |
var tweak = 3; // If it's lower the one second mark looks wrong (?) |
| 213 |
if (this.logClock) { |
| 214 |
return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)); |
| 215 |
} |
| 216 |
else if (this.logClockRev) { |
| 217 |
// Flip the seconds then flip the angle (trickiness) |
| 218 |
second = (60 - second) % 60; |
| 219 |
return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak))); |
| 220 |
} |
| 221 |
else { |
| 222 |
return second/60.0; |
| 223 |
} |
| 224 |
}, |
| 225 |
|
| 226 |
timeText: function(hour,min,sec,showAmPm,showSecs) { |
| 227 |
return '' + |
| 228 |
(showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' + |
| 229 |
this.lpad2(min) + |
| 230 |
(showSecs ? ':' + this.lpad2(sec) : '') + |
| 231 |
(showAmPm ? (hour < 12 ? ' am' : ' pm') : '') |
| 232 |
; |
| 233 |
}, |
| 234 |
|
| 235 |
// Draw a radial line by rotating then drawing a straight line |
| 236 |
// Ha ha, I think I've accidentally used Taus, (see http://tauday.com/) |
| 237 |
radialLineAtAngle: function(angleFraction,skin) { |
| 238 |
this.ctx.save(); |
| 239 |
this.ctx.translate(this.renderRadius,this.renderRadius); |
| 240 |
this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5)); |
| 241 |
this.ctx.globalAlpha = skin.alpha; |
| 242 |
this.ctx.strokeStyle = skin.color; |
| 243 |
this.ctx.lineWidth = skin.lineWidth; |
| 244 |
|
| 245 |
if (CoolClock.config.isIE) |
| 246 |
// excanvas doesn't scale line width so we will do it here |
| 247 |
this.ctx.lineWidth = this.ctx.lineWidth * this.scale; |
| 248 |
|
| 249 |
if (skin.radius) { |
| 250 |
this.fullCircleAt(skin.startAt,0,skin) |
| 251 |
} |
| 252 |
else { |
| 253 |
this.ctx.beginPath(); |
| 254 |
this.ctx.moveTo(skin.startAt,0) |
| 255 |
this.ctx.lineTo(skin.endAt,0); |
| 256 |
this.ctx.stroke(); |
| 257 |
} |
| 258 |
this.ctx.restore(); |
| 259 |
}, |
| 260 |
|
| 261 |
render: function(hour,min,sec,date) { |
| 262 |
// Get the skin |
| 263 |
var skin = this.getSkin(); |
| 264 |
|
| 265 |
// Clear |
| 266 |
this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2); |
| 267 |
|
| 268 |
// Draw the outer edge of the clock |
| 269 |
if (skin.outerBorder) |
| 270 |
this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder); |
| 271 |
|
| 272 |
// Draw the tick marks. Every 5th one is a big one |
| 273 |
for (var i=0;i<60;i++) { |
| 274 |
(i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator); |
| 275 |
!(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator); |
| 276 |
} |
| 277 |
|
| 278 |
// Write the time and or date |
| 279 |
if (this.showDigital || this.showDigital24 || this.showDate) { |
| 280 |
var text; |
| 281 |
if (this.showDigital) text = this.timeText(hour,min,sec,true,false); |
| 282 |
else if (this.showDigital24) text = this.timeText(hour,min,sec,false,true); |
| 283 |
else text = date; |
| 284 |
this.drawTextAt( |
| 285 |
text, |
| 286 |
this.renderRadius, |
| 287 |
this.renderRadius+this.renderRadius/2 |
| 288 |
); |
| 289 |
} |
| 290 |
|
| 291 |
var hourA = (hour%12)*5 + min/12.0, |
| 292 |
minA = min + sec/60.0, |
| 293 |
secA = sec; |
| 294 |
|
| 295 |
// Draw the hands |
| 296 |
if (skin.hourHand) |
| 297 |
this.radialLineAtAngle(this.tickAngle(hourA),skin.hourHand); |
| 298 |
|
| 299 |
if (skin.minuteHand) |
| 300 |
this.radialLineAtAngle(this.tickAngle(minA),skin.minuteHand); |
| 301 |
|
| 302 |
if (this.showSecondHand && skin.secondHand) |
| 303 |
this.radialLineAtAngle(this.tickAngle(secA),skin.secondHand); |
| 304 |
|
| 305 |
// Hands decoration - not in IE |
| 306 |
if (!CoolClock.config.isIE) { |
| 307 |
if (skin.hourDecoration) |
| 308 |
this.radialLineAtAngle(this.tickAngle(hourA), skin.hourDecoration); |
| 309 |
|
| 310 |
if (skin.minDecoration) |
| 311 |
this.radialLineAtAngle(this.tickAngle(minA), skin.minDecoration); |
| 312 |
|
| 313 |
if (this.showSecondHand && skin.secondDecoration) |
| 314 |
this.radialLineAtAngle(this.tickAngle(secA),skin.secondDecoration); |
| 315 |
} |
| 316 |
|
| 317 |
if (this.extraRender) { |
| 318 |
this.extraRender(hour,min,sec); |
| 319 |
} |
| 320 |
}, |
| 321 |
|
| 322 |
// Check the time and display the clock |
| 323 |
refreshDisplay: function() { |
| 324 |
var now = new Date(); |
| 325 |
if (this.gmtOffset != null) { |
| 326 |
// Use GMT + gmtOffset |
| 327 |
var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60)); |
| 328 |
this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds(),offsetNow.toLocaleDateString()); |
| 329 |
} |
| 330 |
else { |
| 331 |
// Use local time |
| 332 |
this.render(now.getHours(),now.getMinutes(),now.getSeconds(),now.toLocaleDateString()); |
| 333 |
} |
| 334 |
}, |
| 335 |
|
| 336 |
|
| 337 |
// Check the canvas element hasn't been removed |
| 338 |
stillHere: function() { |
| 339 |
return document.getElementById(this.canvasId) != null; |
| 340 |
}, |
| 341 |
|
| 342 |
// Stop this clock |
| 343 |
stop: function() { |
| 344 |
this.active = false; |
| 345 |
}, |
| 346 |
|
| 347 |
// Start this clock |
| 348 |
start: function() { |
| 349 |
this.active = true; |
| 350 |
}, |
| 351 |
|
| 352 |
getSkin: function() { |
| 353 |
var skin = CoolClock.config.skins[this.skinId]; |
| 354 |
if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin]; |
| 355 |
return skin; |
| 356 |
} |
| 357 |
}; |
| 358 |
|
| 359 |
// Find all canvas elements that have the CoolClock class and turns them into clocks |
| 360 |
CoolClock.findAndCreateClocks = function() { |
| 361 |
// (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery) |
| 362 |
var canvases = document.getElementsByTagName("canvas"); |
| 363 |
for (var i=0;i<canvases.length;i++) { |
| 364 |
// Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000" |
| 365 |
var fields = canvases[i].className.split(" ")[0].split(":"); |
| 366 |
if (fields[0] == "CoolClock") { |
| 367 |
if (!canvases[i].id) { |
| 368 |
// If there's no id on this canvas element then give it one |
| 369 |
canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++; |
| 370 |
} |
| 371 |
// Create a clock object for this element |
| 372 |
new CoolClock({ |
| 373 |
canvasId: canvases[i].id, |
| 374 |
skinId: fields[1], |
| 375 |
displayRadius: fields[2], |
| 376 |
showSecondHand: fields[3]!='noSeconds', |
| 377 |
gmtOffset: fields[4], |
| 378 |
showDigital: fields[5]=='showDigital', |
| 379 |
showDigital24: fields[5]=='showDigital24', |
| 380 |
showDate: fields[5]=='showDate', |
| 381 |
logClock: fields[6]=='logClock', |
| 382 |
logClockRev: fields[6]=='logClockRev' |
| 383 |
}); |
| 384 |
} |
| 385 |
} |
| 386 |
// Start ticking |
| 387 |
CoolClock.tick(); |
| 388 |
}; |
| 389 |
|
| 390 |
// If you don't have jQuery then you need a body onload like this: <body onload="CoolClock.findAndCreateClocks()"> |
| 391 |
// If you do have jQuery and it's loaded already then we can do it right now |
| 392 |
if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks); |
| 393 |
|