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