/* * Begat : Original code by many, generic HTML5/Canvas clock example. * Date : 12/30/2014 * Who : Sandy Ganz * * Revisions - * * 0.0 - 12/30/2014 sjg - Begat * 0.6 - 01/01/2015 sjg - Added face background inset to make more 3d like * 0.7 - 01/02/2015 sjg - Changed hoursOffset to work agaist date() so calendar updates OK, * added more comment, removed wastefull variable checks in drawPathFast(). * Beginning of multiple clocks on page * 0.8 - 01/04/2015 sjg - Totally broke code in initial attempt to objectify it. * 0.9 - 02/23/2015 sjg - Fixed up timer with some hack code. Added static background draw, fixed * up hand angles and a few other bits. Still Hack, but at least working again. * 0.91 - 08/28/2015 sjg - Added wtfpl LICENSE AGREEMENT * * * Notes - * * This was a hack project that I was messing with. The original had poor optimization * and was drawing a lot more then needed. Worst was the vector drawing that used * a string with the coordinates. These were also changed to regular array for * performance. Exposed most setting via options. The default settings for options * are very noisy to show functionality, turn stuff off to clean up look! * * Todo - * * General code clean and comment. Some possible structural changes to have a 'class' * like structure called Clock that keeps things out of the global name space. * Rename some of the option variables to be more consistent or shorter (I haven't * decided yet). * * The big ones are make the functions that are specific generic, for example * annular ticks, bezels, etc. Right now they are hard coded with positions, * sizes, and so on. Oh yeah learn Javascript... * * Figure out how to create a snap shot of the canvas with all the shading and drawing * done once then animate the image back before drawing hands. I think this would * be faster at some point, but not sure. Good for an experiment or someone that * is a HTML5 badass ;) * * Oh yeah, a demo page maybe operations guide * * For many options you can use rgba() or #123456 style color settings. * * Features (many optional settings too) - * * Very nice looking HTML 5 / Javascript Clock * No incompatible CSS to deal with * User selectable Colors, fonts and sizes for number and most text objects * Supports Time offsets from your local time * Display UTC as an options * Displays user positionable Date in several simple formats * Displays user positionable AM/PM indicator window * Displays PM 'dot' at above 12 * Two user selectable text fields, font and position * Shaded and non shaded bezel options * Two sets of annular tick rings, inner and outer with many options * Pie slice lines from center of clock * Five Minute Numeric display Option (rotated) * Five Minute (offset by 2.5min) display dot * Triangle markers at 3, 6, 9, 12 * 12 O'clock Dot * Display of all Large Hour numbers or only 3, 6, 9, 12 * Sweep second hand or 'Quartz' ticker * * -------------------------------------------------------------------- * Copyright (C) 2015 Sandy Ganz <[email protected]> * * This work is free. You can redistribute it and/or modify it under the * terms of the Do What The Fuck You Want To Public License, Version 2, * as published by Sam Hocevar. See the COPYING file for more details. * (see below, yes this is a real license) * * -------------------------------------------------------------------- * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * Version 2, December 2004 * * Copyright (C) 2004 Sam Hocevar <[email protected]> * * Everyone is permitted to copy and distribute verbatim or modified * copies of this license document, and changing it is allowed as long * as the name is changed. * * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION * * 0. You just DO WHAT THE FUCK YOU WANT TO. * * For more information on the license see www.wtfpl.net/faq/ * -------------------------------------------------------------------- * */ // all paths are CLOSED, and based from the center point of 0,0 // The FIRST element in each array is the contex moveTo for start of path var pie_path = [ [-108, -1], [108, -1], [108, -1], [-108, -1] ]; var tri_path = [ [154, 0], [178, -6], [178, 6] ]; var inner_tick_head_path = [ [110, -3], [120, -3], [120, 3], [110, 3] ]; var inner_tick_path = [ [114, -0.5], [120, -0.5], [120, 0.5], [114, 0.5] ]; var outer_tick_pill_path = [ [156, -2], [180, -2], [180, 2], [156, 2] ]; var outer_tick_head_path = [ [180, -3], [184, -3], [184, 3], [180, 3] ]; var outer_tick_major_path = [ [174, -0.5], [184, -0.5], [184, 0.5], [174, 0.5] ]; var outer_tick_minor_path = [ [180, -0.3], [184, -0.3], [184, 0.3], [180, 0.3] ]; var sec_hand_path = [ [-50, 0], [-45, -5], [-25, -5], [-22, -2], [22, -2], [25, -5], [180, 0], [25, 5], [22, 2], [-22, 2], [-25, 5], [-45, 5] ]; var min_hand_path = [ [0, 0], [1, -2], [20, -2], [22, -5], [122, -5], [124, -2], [146, -2], [168, 0], [146, 2], [124, 2], [122, 5], [22, 5], [20, 2], [1, 2], [0, 0], [24, 0], [24, 2], [120, 2], [122, 0], [120, -2], [24, -2], [24, 0] ]; var hour_hand_path = [ [0, 0], [1, -3], [14, -3], [17, -7], [97, -7], [100, -3], [112, -2], [134, 0], [112, 2], [100, 3], [97, 7], [17, 7], [14, 3], [1, 3], [0, 0], [18, 0], [21, 3], [94, 3], [96, 0], [94, -3], [21, -3], [18, 0] ]; function myclock(id) { var clk = new Clock(id); var clkOpts = clk.getClockOpts(); //cheezeee returns ref to object so now public var textOpts = clk.getTextOpts(); clkOpts.smoothTicks = false; textOpts.text1 = 'Quartz'; textOpts.text1Color = '#ff0000'; textOpts.text1OffsetY= -80; textOpts.text2 = 'Chronometer'; textOpts.text2Font = 'bold italic 20px Georgia'; clk.redraw(); // force update of background on next tick event return clk; } function myclock1(id) { var clk = new Clock(id); return clk; } /*var schedule = { start, end, free, subject }*/ function myclock2(id,schedule, opts) { var ns = []; // schedule with free times var s = schedule[0]; startHr = s.start.substring(0,2); startMn = s.start.substring(3,5); if ((startHr > 7 ) || ((startHr = 7 ) && (startMn > 0 ))) { ns.push({start:"07:00",end:s.start,free:true}); } var prevEndHr, prevEndMn; schedule.forEach(s => { startHr = s.start.substring(0,2); startMn = s.start.substring(3,5); endHr = s.end.substring(0,2); endMn = s.end.substring(3,5); if ((prevEndHr) && ((prevEndHr != startHr) || (prevEndMn != startMn))) { ns.push({start:prevEndHr+":"+prevEndMn,end:startHr+":"+startMn,free:true}); } ns.push({start:startHr+":"+startMn,end:endHr+":"+endMn,subject:s.subject}); if (schedule.indexOf(s) == schedule.length - 1) { ns.push({start:endHr+":"+endMn,end:"07:00",free:true}); } prevEndHr = endHr; prevEndMn = endMn; }); if (opts["nofree"]) { var clk = new Clock(id,schedule); // if schedule is used then free times are not marked } else { var clk = new Clock(id,ns); // if schedule is used then free times are not marked } var bezOpts = clk.getBezelOpts(); var clkOpts = clk.getClockOpts(); var textOpts = clk.getTextOpts(); var tickOpts = clk.getTickOpts(); bezOpts.bezel5Draw = true; textOpts.text2 = ""; textOpts.text1 = ""; tickOpts.innerTick = false; tickOpts.innerTickHead = false; tickOpts.pie = false; clk.redraw(); } function Clock(id,schedule) { var bezelOpts = { // Outer most ring, drawn first, other bezels layer on top // These are all size by the RADIUS 250 is the working size of the // clock. Bezel operations can be as complex as needed as they are // outsize of the animation drawing. Once drawn the do not need // refresh // Simple thickness bezel bezel1Draw: false, bezel1Color: '#111', bezel1Diameter: 250, // Simple thickness bezel bezel2Draw: false, bezel2Color: '#aaa', bezel2Diameter: 248, // shaded bezel bezel3Draw: true, bezel3StartColor: '#555555', bezel3StopColor: '#000000', bezel3Diameter: 245, // shaded bezel bezel4Draw: false, bezel4StartColor: '#ff5555', bezel4StopColor: '#ff0000', bezel4Diameter: 224, // shaded bezel bezel5Draw: false, bezel5FreeColor: '#00ff00', bezel5BusyColor: '#ff0000', bezel5Diameter: 230, }; // general clock face options var faceOpts = { // General face background options, this draws over the bezels faceColor: '#ffffff', // background color of the face faceDiameter: 220, // diameter of the filled face faceInset: true, // simulate depth with shadow }; // text display options, coords from center of clock, - is UP, + down var textOpts = { text1: 'Simplex', text1Color: '#ff5555', text1Font: '30px Georgia', text1OffsetX: 0, text1OffsetY: -75, text2: 'Analog Chronometer', text2Color: '#111111', text2Font: '20px Georgia', text2OffsetX: 0, text2OffsetY: 75, }; // date display options var dateOpts = { dateTextColor: '#111111', dateFont: '22px Georgia', dateOffsetX: 0, dateOffsetY: 40, dateBox: true, // show in inset box dateBoxColor: '#ffffff', dateUTC: false, // use javascript UTC time dateShort: false, // display just date as number }; // Lots of stuff here mainly related to the annular tick rings and // other indicators around the rings really should be split up var tickOpts = { // triangles at 3, 6, 9 and 12 tri: true, triColor: '#555555', // pie slices from the center of the clock pie: true, pieColor: '#dddddd', pieLineWidth: 2, // large hour numbers hourNums: true, hoursComplete: true, // show 1-12 if true, otherwise just 3, 6, 9 and 12 hourColor: '#cc0000', hourFont: '30pt Georgia', hourOffset: 140, // offset from the center of the clock // inner tick ring innerTick: true, innerTickHead: true, // draws thick 5 minute markers innerTickColor: '#aaaaaa', innerTickHeadColor: '#555555', // outer tick ring // draws tick head markers at 5 min marks outerTickHead: true, outerTickHeadColor: '#ff0000', // draws think line UNDER the 5 min tick head outerTickPill: true, outerTickPillColor: '#aaaaaa', // draws the finest tick marks outerTickMinor: true, outerTickMinorColor: '#aaaaaa', // draws the finest tick marks outerTickMajor: true, outerTickMajorColor: '#444000', // draws the dot over the 12 o'clock postion outer12Dot: true, outer12DotColor: '#ff0000', // draws the outer dots at 2.5min marks outerDots: true, outerDotsOffset: 195, // offset from the center of the clock outerDotsSize: 3.0, // radius of the dot, fractional OK outerDotsColor: '#ff0000', // outer rotated minute numbers outerMinNums: true, outerMinNumsOffset: 195, // offset from the center of the clock outerMinNumsFont: '12pt Georgia', outerMinNumsColor: '#555555', }; // general hand options var handOpts = { secPivot: true, // draws dot over center of hands secPivotColor: '#c21', secHandColor: '#c21', minHandColor: '#222222', hourHandColor: '#333333', }; // options related to AM/PM indications var ampmOpts = { ampmDot: true, // show a tranparent dot over the 12 pm position amTextColor: '#000000', // AM/PM indicator text color if AM pmTextColor: '#000000', // AM/PM indicator text color if PM ampmFont: '22px Georgia', ampmBox: true, // Show indicator as inset amBoxColor: '#ffffff', // color of inset if AM pmBoxColor: '#bbbbbb', // color of insert if PM ampmOffsetX: 0, // X offset from center of clock ampmOffsetY: -40, // Y offset from center of clock }; // General clock options var clockOpts = { smoothTicks: true, // smooth sweep second hand hoursOffset: 0, // offset from current time (Local or UTC), may have some glitches useUTC: false, // use UTC as base for time }; // instance vars var d,ds; var scale = 1; var interval_id = null; var ctx, back_ctx; var self = this; var interval = 31; //millisecs default for very smooth action, if not using smooth tics can be much higher var canvas = document.getElementById(id); var refresh = false; if (!canvas.getContext) { alert('Canvas not supported, can\'t draw clock'); return; } ds = new Date(); // just to set it up d = new Date(); // aways pull new date if (clockOpts.hoursOffset != 0) // Add offset to new time, hopefull works OK with date d = new Date(d.getTime() + clockOpts.hoursOffset * 3600000); // set up pre computed options, and one time drawing that // don't get affected by hands or other display changes pie_path[3][1] = tickOpts.pieLineWidth - 1; pie_path[2][1] = tickOpts.pieLineWidth - 1; ctx = canvas.getContext('2d'); canvas.width = 500 * scale; // base size is all drawn at 500x500 canvas.height = 500 * scale; ctx.scale(scale, scale); // hint to get different sized clocks, adj canvas too var back_canvas = document.createElement("canvas"); if (!back_canvas.getContext) { alert('Canvas not supported, can\'t draw clock'); return; } back_canvas.id = id + '-hidden'; back_canvas.style.hidden = true; // not sure if works or needed var back_ctx = back_canvas.getContext('2d'); back_canvas.width = 500; back_canvas.height = 500; // start and stop functions, must call start() to get clock ticking this.start = function() { interval_id = setInterval(function() {self.ticker()}, interval); } this.stop = function() { if(interval_id != null){ clearInterval(interval_id); interval_id = null; } } this.redraw = function() { refresh = true; } this.getClockOpts = function() { return clockOpts; } this.getBezelOpts = function() { return bezelOpts; } this.getTextOpts = function() { return textOpts; } this.getTickOpts = function() { return tickOpts; } // called to update clock time from timeout/interval this.ticker = function () { var h; // adjust time offset if needed d = new Date(); // aways pull new date and time if (clockOpts.hoursOffset != 0) // Add offset to new time, hopefull works OK with date d = new Date(d.getTime() + clockOpts.hoursOffset * 3600000); // get system if (clockOpts.useUTC) h = d.getUTCHours(); else h = d.getHours(); // set up the hands and relationships (they are all dependant) var m = d.getMinutes(); var s = d.getSeconds(); var ms = (clockOpts.smoothTicks) ? d.getMilliseconds() : 0; // if using quarts like ticks 0 ms var secAngle = -90 + s * 6 + ms * 0.006; var minAngle = -90 + m * 6 + s * 0.1 + ms * 0.0001; var hourAngle = -90 + h * 30 + m * 0.5 + s / 120; ctx.clearRect(0, 0, 500, 500); // always clear entire space ctx.drawImage(back_canvas, 0, 0); // draw pre drawn background to top // refresh background on hour change, will catch day change too for calendar if(ds.getHours() != d.getHours() || refresh) { ds = d; this.drawBackground(); // refresh the background to catch changes ctx.drawImage(back_canvas, 0, 0); // draw background again refresh = false; } if(ds.getMinutes() != d.getMinutes() || refresh) { ds = d; this.drawBackground(); // refresh the background to catch changes ctx.drawImage(back_canvas, 0, 0); // draw background again refresh = false; } // hand vectors need translation to center, these all draw to the front ctx/canvas ctx.translate(250, 250); this.drawHourHand(hourAngle, handOpts); this.drawMinHand(minAngle, handOpts); this.drawSecHand(secAngle, handOpts); ctx.translate(-250, -250); } // Draw all background items here. This doesn't get refreshed often, all draw to back ctx this.drawBackground = function() { this.drawBezels(bezelOpts); this.drawFace(faceOpts); this.drawTicks(tickOpts); this.drawText(textOpts); this.drawDate(dateOpts); this.drawAMPM(ampmOpts); } this.drawInsetBox = function (bk_color, shadow_color, offX, offY, width, height) { back_ctx.save(); back_ctx.beginPath(); back_ctx.fillStyle = bk_color; back_ctx.rect(offX, offY, width, height); back_ctx.closePath(); back_ctx.fill(); back_ctx.beginPath(); back_ctx.rect(offX, offY, width, height); back_ctx.clip(); back_ctx.beginPath(); back_ctx.strokeStyle = shadow_color; back_ctx.lineWidth = 4; back_ctx.shadowBlur = 5; back_ctx.shadowColor = shadow_color; back_ctx.shadowOffsetX = 4; back_ctx.shadowOffsetY = 2; width += 4; back_ctx.strokeRect(offX - 2, offY - 2, width, height + 4); back_ctx.stroke(); back_ctx.restore(); }; this.drawBezels = function (opts) { // Outer most Ring, bezel1Ring if (opts.bezel1Draw) { back_ctx.beginPath(); back_ctx.arc(250, 250, opts.bezel1Diameter, 0, 6.2831); //2*pi back_ctx.fillStyle = opts.bezel1Color; back_ctx.fill(); back_ctx.closePath(); } // next thin ring if (opts.bezel2Draw) { back_ctx.beginPath(); back_ctx.arc(250, 250, opts.bezel2Diameter, 0, 6.2831); //2*pi back_ctx.fillStyle = opts.bezel2Color; back_ctx.fill(); back_ctx.closePath(); } var gx1, gy1, gx2, gy2, g; if (opts.bezel3Draw) { gx1 = 127; // 250 + 246 * Math.cos(4 / 3 * Math.PI); gy1 = 35; // 250 + 246 * Math.sin(4 / 3 * Math.PI) gx2 = 373; // 250 + 246 * Math.cos(1 / 3 * Math.PI); gy2 = 463; // 250 + 246 * Math.sin(1 / 3 * Math.PI); g = back_ctx.createLinearGradient(gx1, gy1, gx2, gy2); g.addColorStop(0, opts.bezel3StartColor); // outer ring reflect g.addColorStop(1, opts.bezel3StopColor); // outer ring dark // outer ring back_ctx.beginPath(); back_ctx.arc(250, 250, opts.bezel3Diameter, 0, 6.2831); //2*pi back_ctx.fillStyle = g; back_ctx.fill(); back_ctx.closePath(); } if (opts.bezel4Draw) { gx1 = 138; // 250 + 224 * Math.cos(4 / 3 * Math.PI); gy1 = 56; // 250 + 224 * Math.sin(4 / 3 * Math.PI) gx2 = 362; // 250 + 224 * Math.cos(1 / 3 * Math.PI); gy2 = 326; // 250 + 224 * Math.sin(1 / 3 * Math.PI); g = back_ctx.createLinearGradient(gx1, gy1, gx2, gy2); // inner ring g.addColorStop(0, opts.bezel4StartColor); // outer ring reflect g.addColorStop(1, opts.bezel4StopColor); // outer ring dark back_ctx.beginPath(); back_ctx.arc(250, 250, opts.bezel4Diameter, 0, 6.2831); //2*pi back_ctx.fillStyle = g; back_ctx.fill(); back_ctx.closePath(); } if (opts.bezel5Draw && schedule) { // schedule ring var arcstart, arcend; schedule.forEach(s => { startHr = s.start.substring(0,2); startMn = s.start.substring(3,5); endHr = s.end.substring(0,2); endMn = s.end.substring(3,5); currTime = d.getHours()*60+d.getMinutes(); startTime = startHr*60+parseInt(startMn); endTime = endHr*60+parseInt(endMn) back_ctx.beginPath(); if (startHr < 15 ) { arcstart = 2 * Math.PI - ( 15 - startHr ) * Math.PI / 6 + startMn * Math.PI / 360; } else{ arcstart = ( startHr - 15 ) * Math.PI / 6 + startMn * Math.PI / 360; } if (endHr < 15) { arcend = 2 * Math.PI - ( 15 - endHr ) * Math.PI / 6 + endMn * Math.PI / 360; } else{ arcend = ( endHr - 15 ) * Math.PI / 6 + endMn * Math.PI / 360; } back_ctx.arc(250, 250, bezelOpts.bezel5Diameter, arcstart, arcend); //2*pi back_ctx.strokeStyle = s.free?bezelOpts.bezel5FreeColor:bezelOpts.bezel5BusyColor; back_ctx.lineWidth = 10; back_ctx.stroke(); back_ctx.closePath(); if ((currTime >= startTime) && (typeof(s.free) == "undefined")) { back_ctx.beginPath(); if (startHr < 15 ) { arcstart = 2 * Math.PI - ( 15 - startHr ) * Math.PI / 6 + startMn * Math.PI / 360; } else{ arcstart = ( startHr - 15 ) * Math.PI / 6 + startMn * Math.PI / 360; } if (currTime <= endTime) { if (d.getHours() < 15) { arcend = 2 * Math.PI - ( 15 - d.getHours() ) * Math.PI / 6 + d.getMinutes() * Math.PI / 360; } else{ arcend = ( d.getHours() - 15 ) * Math.PI / 6 + d.getMinutes() * Math.PI / 360; } } else { if (endHr < 15) { arcend = 2 * Math.PI - ( 15 - endHr ) * Math.PI / 6 + endMn * Math.PI / 360; } else{ arcend = ( endHr - 15 ) * Math.PI / 6 + endMn * Math.PI / 360; } } back_ctx.arc(250, 250, bezelOpts.bezel5Diameter, arcstart, arcend); //2*pi back_ctx.strokeStyle = "grey"; back_ctx.lineWidth = 10; back_ctx.stroke(); back_ctx.closePath(); } }); } } this.drawFace = function (opts) { // background face, usally redrawn OVER bezels, and MUST back_ctx.beginPath(); back_ctx.arc(250, 250, opts.faceDiameter, 0, 6.2831); //2*pi back_ctx.fillStyle = opts.faceColor; back_ctx.closePath(); back_ctx.fill(); // create a shaded area to simulate 3D shadow if (opts.faceInset) { back_ctx.save(); back_ctx.beginPath(); back_ctx.arc(250, 250, opts.faceDiameter, 0, 6.2831); //2*pi back_ctx.clip(); back_ctx.beginPath(); back_ctx.strokeStyle = 'black'; back_ctx.lineWidth = 6; back_ctx.shadowBlur = 10; back_ctx.shadowColor = 'black'; back_ctx.shadowOffsetX = 4; back_ctx.shadowOffsetY = 1; back_ctx.arc(250, 250, opts.faceDiameter + 3, 0, 6.2831); //2*pi back_ctx.stroke(); back_ctx.restore(); } } this.drawText = function (opts) { // clock text back_ctx.beginPath(); back_ctx.textAlign = 'center'; back_ctx.textBaseline = 'middle'; back_ctx.font = opts.text1Font; back_ctx.fillStyle = opts.text1Color; back_ctx.fillText(opts.text1, 250 + opts.text1OffsetX, 250 + opts.text1OffsetY); back_ctx.font = opts.text2Font; back_ctx.fillStyle = opts.text2Color; back_ctx.fillText(opts.text2, 250 + opts.text2OffsetX, 250 + opts.text2OffsetY); back_ctx.fill(); back_ctx.closePath(); }; this.drawDate = function (opts) { back_ctx.save(); if (opts.dateUTC) { if (opts.dateShort) datestr = d.getUTCDate(); else datestr = 'UTC : ' + d.getUTCDate() + '-' + d.getUTCMonth() + '-' + d.getUTCFullYear(); } else if (opts.dateShort) datestr = d.getDate(); else datestr = d.toDateString(); back_ctx.font = opts.dateFont; width = back_ctx.measureText(datestr).width + 14; if (false && opts.dateBoxShadow && opts.dateBox) { back_ctx.shadowColor = '#aaaaaa'; back_ctx.shadowBlur = 10; back_ctx.shadowOffsetX = 3; back_ctx.shadowOffsetY = 3; } if (opts.dateBox) { this.drawInsetBox(opts.dateBoxColor, 'black', 250 + opts.dateOffsetX - width / 2, 250 + opts.dateOffsetY - 15, width, 30); } back_ctx.restore(); back_ctx.font = opts.dateFont; back_ctx.beginPath(); back_ctx.textAlign = 'center'; back_ctx.textBaseline = 'middle'; back_ctx.fillStyle = opts.dateTextColor; back_ctx.fillText(datestr, 250 + opts.dateOffsetX, 250 + opts.dateOffsetY); back_ctx.fill(); back_ctx.closePath(); } this.drawAMPM = function (opts) { if (d.getHours() > 11) { ampmstr = 'PM'; pm = true; } else { ampmstr = 'AM'; pm = false; } back_ctx.font = opts.ampmFont; width = back_ctx.measureText(ampmstr).width + 10; back_ctx.save(); if (opts.ampmBox) { background = (pm) ? opts.pmBoxColor : opts.amBoxColor; shadow = 'black'; this.drawInsetBox(background, shadow, opts.ampmOffsetX + 250 - width / 2, 250 + opts.ampmOffsetY - 15, width, 30); } back_ctx.restore(); if (opts.ampmDot && pm) { // 12 o'clock dot PM Dot back_ctx.beginPath(); back_ctx.fillStyle = 'rgba(0,0,0,.50)'; back_ctx.arc(250, 55, 10, 0, 6.2831); //2*pi back_ctx.closePath(); back_ctx.fill(); } back_ctx.font = opts.ampmFont; back_ctx.beginPath(); back_ctx.textAlign = 'center'; back_ctx.textBaseline = 'middle'; back_ctx.fillStyle = (pm) ? opts.pmTextColor : opts.amTextColor; back_ctx.fillText(ampmstr, 250 + opts.ampmOffsetX, 250 + opts.ampmOffsetY); back_ctx.closePath(); back_ctx.fill(); } this.drawTicks = function (opts) { var lbl, x, y, i; back_ctx.translate(250, 250); // inner pie slices if (opts.pie) { this.drawPathBk(pie_path, opts.pieColor, 0); this.drawPathBk(pie_path, opts.pieColor, 30); this.drawPathBk(pie_path, opts.pieColor, 60); this.drawPathBk(pie_path, opts.pieColor, 90); this.drawPathBk(pie_path, opts.pieColor, 120); this.drawPathBk(pie_path, opts.pieColor, 150); } // triangle tics if (opts.tri) { this.drawPathBk(tri_path, opts.triColor, 0); this.drawPathBk(tri_path, opts.triColor, 90); this.drawPathBk(tri_path, opts.triColor, 180); this.drawPathBk(tri_path, opts.triColor, -90); } // outer ticks if (opts.outerTickPill) { this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 30); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 60); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 120); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 150); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, -30); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, -60); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, -120); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, -150); if (!opts.tri) // no triangles, draw ticks in those slots too { this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 0); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 90); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 180); this.drawPathBk(outer_tick_pill_path, opts.outerTickPillColor, 270); } } // Hour Numbers if (opts.hourNums) { for (i = -60; i < 300; i += 30) { // large hour numbers if (!opts.hoursComplete) if (!(i == 270 || i == 180 || i == 90 || i == 0)) continue; lbl = '' + Math.round(i / 30 + 3); x = opts.hourOffset * Math.cos(i * 0.017453); // Math.PI / 180.0); y = opts.hourOffset * Math.sin(i * 0.017453); // Math.PI / 180.0); back_ctx.save(); back_ctx.translate(x, y); back_ctx.font = opts.hourFont; back_ctx.textAlign = 'center'; back_ctx.textBaseline = 'middle'; back_ctx.fillStyle = opts.hourColor; back_ctx.fillText(lbl, 0, 0); back_ctx.restore(); } } if (opts.outer12Dot) { // 12 o'clock dot back_ctx.beginPath(); back_ctx.arc(0, -195, 4, 0, 6.2831); //2*pi back_ctx.fillStyle = opts.outer12DotColor; back_ctx.fill(); back_ctx.closePath(); } for (i = 0; i < 360; i += 30) { // outer tick pills if (opts.outerTickHead) this.drawPathBk(outer_tick_head_path, opts.outerTickHeadColor, i); // inner tick pills if (opts.innerTickHead) this.drawPathBk(inner_tick_head_path, opts.innerTickHeadColor, i); // outer small numbers if (opts.outerMinNums) { lbl = '' + Math.round(i / 6 + 15); if (lbl == '60') // show zero if no dot if (opts.outer12Dot) lbl = ''; else lbl = '0'; if (lbl == '65') lbl = '5'; //hack if (lbl == '70') lbl = '10'; //hack x = opts.outerMinNumsOffset * Math.cos(i * 0.017453); y = opts.outerMinNumsOffset * Math.sin(i * 0.017453); back_ctx.save(); back_ctx.translate(x, y); back_ctx.rotate((i + 90 - (i > 0 && i < 180 ? 180 : 0)) * 0.017453); back_ctx.font = opts.outerMinNumsFont; back_ctx.textAlign = 'center'; back_ctx.textBaseline = 'middle'; back_ctx.fillStyle = opts.outerMinNumsColor; back_ctx.fillText(lbl, 0, 0); back_ctx.restore(); } // small dots between small numbers if (opts.outerDots) { back_ctx.beginPath(); x = opts.outerDotsOffset * Math.cos((i + 15) * 0.017453); y = opts.outerDotsOffset * Math.sin((i + 15) * 0.017453); back_ctx.arc(x, y, opts.outerDotsSize, 0, 6.2831); //2*pi back_ctx.fillStyle = opts.outerDotsColor; back_ctx.fill(); back_ctx.closePath(); } // outiner ring tics, major, minor, inner ring with smart backfill for missing ticks for (var j = 0; j < 25; j += 6) { if (j != 0 || !(opts.outerTickHead || opts.outerTickPill)) // fill in major ticks if head or pills not drawn { // major outer ring if (opts.outerTickMajor) this.drawPathBk(outer_tick_major_path, opts.outerTickMajorColor, i + j); } if (j != 0 || !opts.innerTickHead) // if not drawing tick heads and inner ticks enabled fill them in too { // inner ring major if (opts.innerTick) this.drawPathBk(inner_tick_path, opts.innerTickColor, i + j); } if (opts.outerTickMinor) // fine ticks, back fill if majors are not drawn { var k = (opts.outerTickMajor) ? 1.5 : 0; for (; k < 5; k += 1.5) this.drawPathBk(outer_tick_minor_path, opts.outerTickMinorColor, i + j + k); } } } back_ctx.translate(-250, -250); } this.drawSecHand = function (ang, opts) { this.drawPathFr(sec_hand_path, opts.secHandColor, ang); if (opts.secPivot) { ctx.beginPath(); ctx.arc(0, 0, 8, 0, 6.2831); //2*pi ctx.fillStyle = opts.secPivotColor; ctx.fill(); ctx.closePath(); } } this.drawMinHand = function (ang, opts) { this.drawPathFr(min_hand_path, opts.minHandColor, ang); } this.drawHourHand = function (ang, opts) { this.drawPathFr(hour_hand_path, opts.hourHandColor, ang); } // Draws on the front layer (hands typically) this.drawPathFr = function (path, fill, ang) { ctx.save(); ctx.rotate(ang * 0.017453); // Math.PI / 180.0); ctx.beginPath(); ctx.moveTo(path[0][0], path[0][1]); for (i = 1; i < path.length; i++) ctx.lineTo(path[i][0], path[i][1]); ctx.closePath(); ctx.fillStyle = fill; ctx.fill(); ctx.restore(); }; this.drawPathBk = function (path, fill, ang) { back_ctx.save(); back_ctx.rotate(ang * 0.017453); // Math.PI / 180.0); back_ctx.beginPath(); back_ctx.moveTo(path[0][0], path[0][1]); for (i = 1; i < path.length; i++) back_ctx.lineTo(path[i][0], path[i][1]); back_ctx.closePath(); back_ctx.fillStyle = fill; back_ctx.fill(); back_ctx.restore(); }; /// one time code for the clock this.drawBackground(); // create background that usually is static if (clockOpts.useUTC) dateOpts.useUTC = clockOpts.useUTC; this.ticker(); // force initial this.start(); // start it up by default } // Clock
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>jsAnalogClock</title> <script type='text/javascript' src='analogclock.js'></script> </head> <!-- Working on fixing up the commonly found HTML5 clock --> <!-- Making it soft first, then create a clock object and related part --> <!-- (C) Sandy Ganz 2014-2015 [email protected] --> <body onLoad="runOnLoad();"> <h1>Javascript Analog Clock Hack by Sandy Ganz</h1> <canvas id="analog-clock"></canvas> <canvas id="analog-clock1"></canvas> <p> <a href="http://www.wtfpl.net/"><img src="http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-4.png" width="80" height="15" alt="WTFPL" /></a> <p>We Proudly Support WTFPL for licensing</p> <h2>Here is the link to the project so you can mess with the code</h2> <a href="http://jsfiddle.net/sganz/t20qv4rt/">jsFiddle Link</a><br /> <a href="https://github.com/sganz/analogclock.git">GitHub Javascript Repo (More Current then jsFiddle)</a> </p> <script type="text/javascript"> function runOnLoad() { var a1 = myclock1('analog-clock'); var a2 = myclock2('analog-clock1', [{start:"08:00",end:"09:00",subject:"test1"}, {start:"10:00",end:"12:30",subject:"test2"}, {start:"14:00",end:"16:30",subject:"test3"}],{}); } </script> </body> </html>