Orologio analogico realizzato in HTML 5 e JavaScript utilizzando i Canvas per disegnare l'orologio.



Per inserire l'orologio in pagina, crea un tag canvas:
<canvas id="sgart-canvas-clock" style="width:70px;height:70px;"></canvas>
oppure un altro tag DIV, in questo caso verrà creato all'interno un canvas con le stesse dimensioni:
<div id="sgart-canvas-clock" style="width:350px;height:350px;"></div>
per attivarlo usa questo JavaScript passando l'id del controllo:
sgart.clockHtmlCanvas('sgart-canvas-clock');
L'orologio può essere configurato, ma in questo caso va passato un oggetto di configurazione:
sgart.clockHtmlCanvas({
		id:'sgart-canvas-clock',		//id del controllo
		offsetHours:0,		//eventuale scostamento orario per rappresentare altri fusi orari
		backgroundColor:"#fefefe",		//colore dello sfondo
		borderColor:"#000",		//colore del bordo
		borderOn:,true,		//bordo attivo
		textColor:"#888",		//colore del testo
		digitalClockOn:true,	//visualizza l'orologio digitale
		refColor:"#444",		//colore degli indicatore ore/minuti
		refOn:true,		//indicatori visibili
		refMinutesOn:true,		//indicatori minuti attivi
		handHoursColor:"#000",		//colore lancetta ore
		handMinutesColor:"#000",		//colore lancetta minuti
		handSecondsColor:"#BF0000",		//colore lancetta secondo
		handSecondsOn:true,		//secondi visibili
		handSecondsLine:false		//secondi con pallino / lancetta
	});
as esempio per avere un orologio con lancetta dei secondi, avanti di un ora senza visualizzare la parte digitale:
sgart.clockHtmlCanvas({id:'sgart-canvas-clock', offsetHours:1, handSecondsLine:true,digitalClockOn:false });

Per funzionare richede il file seguente:
File: sgart.clockHtmlCanvas.js
var sgart=sgart||{};
sgart.clockHtmlCanvas=function(settings){
	//Sgart.it - copyright 2015 - Orologio analogio con Canvas HTML 5 / Analog clock with Canvas HTML 5 
	if(typeof settings==="string") settings={id:settings};
	var cfg={
		id:settings.id,	//id del controllo
		offsetHours:merge(settings.offsetHours,0),	//eventuale scostamento orario per rappresentare altri fusi orari
		backgroundColor:merge(settings.backgroundColor,"#fefefe"),	//colore dello sfondo
		borderColor:merge(settings.borderColor,"#000"),	//colore del bordo
		borderOn:merge(settings.borderOn,true),	//bordo attivo
		textColor:merge(settings.textColor,"#888"),	//colore del testo
		digitalClockOn:merge(settings.digitalClockOn,true),	//visualizza l'orologio digitale
		refColor:merge(settings.refColor,"#444"),	//colore degli indicatore ore/minuti
		refOn:merge(settings.refOn,true),	//indicatori visibili
		refMinutesOn:merge(settings.refMinutesOn,true),	//indicatori minuti attivi
		handHoursColor:merge(settings.handHoursColor,"#000"),	//colore lancetta ore
		handMinutesColor:merge(settings.handMinutesColor,"#000"),	//colore lancetta minuti
		handSecondsColor:merge(settings.handSecondsColor,"#BF0000"),	//colore lancetta secondo
		handSecondsOn:merge(settings.handSecondsOn,true),	//secondi visibili
		handSecondsLine:merge(settings.handSecondsLine,false)	//secondi con pallino / lancetta
	};
	var canvas,ctx,imageBkg,h,w,prevSeconds=-1,wh,cx,cy,r,textY,textY1,textSize;
	var lenHandHours,lenHandMinutes,lenHandSeconds,lenCirceSeconds,widthCirceSeconds,lenRefHours,lenRefMinutes,widthHours,widthMinutes,widthSeconds,borderSizeSmall,borderSize;
	var degree360=2*Math.PI;
	var degree1=degree360/60;
	var degree5=degree1*5;
	var degreeStart=-Math.PI/2;
	
	var obj = document.getElementById(cfg.id);
	w=parseInt(obj.style.width);
	h=parseInt(obj.style.height);
	
	if(obj.nodeName!=="CANVAS"){
		//creo il canvas, lo dimensiono uguale al div e lo aggiungo
		canvas=document.createElement("canvas");
		obj.appendChild(canvas);
	}else{
		canvas=obj;
	}
	//imposto le dimensioni del canvas per essere sicuro di avere le dimensioni/rapporto corrette
	canvas.width=w;
	canvas.height=h;
	//ricavo il contesto per disegnare sul canvas
	ctx = canvas.getContext("2d");
	//calcolo le dimensioni del cerchio che rappresenta l'orologio
	wh= w>h?h:w;	//trovo la dimensione minore tra altezza e larghezza ovvero il diametro
	cx=w/2;	//centro x
	cy=h/2;	//centro y
	r= wh/2;	//raggio
	//calcolo in automatico la dimensione del bordo
	borderSize=r/35; if(borderSize<1)borderSize=1;
	textSize=r/5,textY=cy+r/2,textY1=cy-r/2;
	borderSizeSmall=r/15; if(borderSizeSmall<1)borderSizeSmall=1;
	//dimensione tacche ore/minuti e lancette
	var r1=r-borderSize;
	lenRefHours=r1/7; //lunghezza tacca ora
	lenRefMinutes=lenRefHours*.6; //lunghezza tacca minuti
	lenHandHours=r1-lenRefHours*2;	//lunghezza lancetta ore
	lenHandMinutes=r1-lenRefHours;	//lunghezza lancetta minuti
	lenHandSeconds=r1-lenRefMinutes;	//lunghezza lancetta secondi
	widthHours=r1/18; if(widthHours<1) widthHours=1;	//larghezza lancetta ore
	widthMinutes=r1/30; if(widthMinutes<1) widthMinutes=1; //larghezza lancetta minuti
	widthSeconds=r1/60; if(widthSeconds<1) widthSeconds=1; //larghezza lancetta secondi
	widthCirceSeconds=lenRefHours/2*.8; if(widthCirceSeconds<1) widthCirceSeconds=1;	// diametro del cerchio dei secondi
	lenCirceSeconds=r1-lenRefHours-lenRefHours/2;	// distanza del cerchio dei secondi
	//creo il background ad avvio il timer
	createBackground(canvas);

	//il bordo e le tacche che non cambiano mai le disegno una sola volta su un canvas a parte
	function createBackground(canvas){
		//creo il canvas per il background fisso
		var canvasBkg=document.createElement("canvas");
		canvasBkg.width=w;
		canvasBkg.height=h;
		var ctxBkg = canvasBkg.getContext("2d");
		//pulisco il canvas
		ctxBkg.clearRect(0, 0, canvasBkg.width, canvasBkg.height);	
		//disegno un cerchio pieno
		var r1=r;
		if(cfg.borderOn){
			//per evitare "artefatti" sul bordo, se ho il bordo disegno un cerchio dello stesso colore
			ctxBkg.beginPath();	//inizio a disegnare
			ctxBkg.arc(cx,cy,r,0,degree360);	//disegno un cerchio
			ctxBkg.fillStyle = cfg.borderColor;
			ctxBkg.fill();	//lo riempio
			r1=r-1;
		}
		ctxBkg.beginPath();
		ctxBkg.arc(cx,cy,r1,0,degree360);
		ctxBkg.fillStyle = cfg.backgroundColor;
		ctxBkg.fill();

		//disegno le tacche dei minuti e delle ore
		if(cfg.refOn){
			ctxBkg.lineWidth=widthMinutes;
			ctxBkg.strokeStyle=cfg.refColor;
			var minutes5=5;
			var lenHours=r-lenRefHours;
			var lenMinutes=r-lenRefMinutes;
			for(var i=0;i<60;i++){
				if(cfg.refMinutesOn || minutes5===5){
					ctxBkg.save();	//salvo il contesto corrente
					ctxBkg.beginPath();
					ctxBkg.translate(cx,cy);	//modifico l'origine del contesto
					ctxBkg.rotate(degreeStart+degree1*i);	//ruoto il contesto
					ctxBkg.moveTo(r,0);	//mi sposto senza disegnare
					if(minutes5===5){
						minutes5=0;
						ctxBkg.lineWidth=widthHours;
						ctxBkg.lineTo(lenHours,0);	//disegno una linea dal punto precedente a quello indicato
					}else{
						ctxBkg.lineTo(lenMinutes,0);
					}
					ctxBkg.stroke();	//disegno effettivamente il path generato (la line ain questo caso)
					ctxBkg.restore(); //ripristino il contesto (translate e rotate precedenti)	
				}
				minutes5++;
			}	
		}
		//disegno il bordo dell'orologio
		if(cfg.borderOn){
			ctxBkg.beginPath();
			var borderSize2=borderSize/2; if(borderSize2<1)borderSize2=1;
			ctxBkg.arc(cx,cy,r-borderSize2,0,degree360);
			ctxBkg.lineWidth = borderSize;
			ctxBkg.strokeStyle = cfg.borderColor;
			ctxBkg.stroke();
		}
		//testo sgart.it - NON RIMUOVERE
		ctxBkg.beginPath();
		ctxBkg.font = "bold "+textSize+"px Arial";
		ctxBkg.fillStyle=cfg.textColor;
		ctxBkg.textAlign="center";
		ctxBkg.textBaseline="middle";
		ctxBkg.fillText("sgart.it", cx, textY1);

		
		//setto, in base al browser, la funzione di redraw
		requestAnimFrame = (function(callback) {
			return window.requestAnimationFrame 
						|| window.webkitRequestAnimationFrame 
						|| window.msRequestAnimationFrame 
						|| window.mozRequestAnimationFrame 
						|| window.oRequestAnimationFrame 
						|| function(callback) { window.setTimeout(callback, 1000 / 60); };
		})();
	
		//genero l'immagine del background
		imageBkg = new Image();	//creo unoggetto immagine
		imageBkg.src = canvasBkg.toDataURL('image/png');	//converto il contenuto del canvas in una immagine rappresentata in base64 e lo carico nell'immagine
		imageBkg.onload = function(){
			//aspetto che l'immagine venga caricata
			//richiamo l'aggiornamento
			requestAnimFrame(draw);
		};
	};

	//disegna le lancette
	function draw(){
		//ricavo l'ora attuale
		var dt= new Date();
		var seconds=dt.getSeconds();
		if(seconds===prevSeconds){ //se i secondi non sono cambiati esco
			requestAnimFrame(draw);
			return
		}
		prevSeconds=seconds;
		if(cfg.offsetHours!==0){
			//se ho un offset orario lo imposto
			dt.setHours(dt.getHours()+cfg.offsetHours);
			seconds=dt.getSeconds();
		}
		var minutes=dt.getMinutes();
		var hours=dt.getHours();
		if(hours>=12)  hours=hours-12;	//normalizzo sulle 12 ore

		//calcoli per le lancette
		var hourOffset=minutes/60*degree5; //le ore si muovono nei 60 minuti
		var degreeHours=degreeStart+hours*degree5+hourOffset;
		var degreeMinutes=degreeStart+minutes*degree1;
		var degreeSeconds=degreeStart+seconds*degree1;		
		
		//disegno lo sfondo
		ctx.drawImage(imageBkg, 0, 0);

		//scrivo il time
		if(cfg.digitalClockOn){
			var dtString=(hours>9?"":"0")+hours+"."+(minutes>9?"":"0")+minutes+"."+(seconds>9?"":"0")+seconds;
			ctx.beginPath();	
			ctx.font = textSize+"px Arial";
			ctx.fillStyle=cfg.textColor;
			ctx.textAlign="center";
			ctx.textBaseline="middle";
			ctx.fillText(dtString, cx, textY);
		}
		//disegno le lancette
		//hours
		ctx.save(); // saves the coordinate system
		ctx.strokeStyle = cfg.handHoursColor;
		ctx.beginPath();
		ctx.translate(cx,cy); 
		ctx.rotate(degreeHours);
		ctx.moveTo(0,0)
		ctx.lineTo(lenHandHours,0)
		ctx.lineWidth=widthHours;
		ctx.stroke();
		ctx.restore(); 	
		//minutes
		ctx.save();
		ctx.beginPath();
		ctx.strokeStyle = cfg.handMinutesColor;
		ctx.translate(cx,cy); 
		ctx.rotate(degreeMinutes);
		ctx.moveTo(0,0)
		ctx.lineTo(lenHandMinutes,0)
		ctx.lineWidth=widthMinutes;
		ctx.stroke();
		ctx.restore(); 	
		//second
		if(cfg.handSecondsOn){
			ctx.save(); 
			ctx.beginPath();
			ctx.strokeStyle = cfg.handSecondsColor;
			ctx.translate(cx,cy); 
			ctx.rotate(degreeSeconds);
			if(cfg.handSecondsLine){
				ctx.moveTo(0,0)
				ctx.lineTo(lenHandSeconds,0)
				ctx.lineWidth=widthSeconds;
				ctx.stroke();
			}else {
				ctx.arc(lenCirceSeconds,0,widthCirceSeconds,0,degree360);
				ctx.fillStyle = cfg.handSecondsColor;
				ctx.fill();
			}
			ctx.restore(); 	
		}
		//disegno un cerchio piccolo al centro
		ctx.beginPath();
		ctx.arc(cx,cy,borderSizeSmall,0,degree360);
		ctx.fillStyle = cfg.borderColor;
		ctx.fill();
		requestAnimFrame(draw);
	};
	function merge(v, vDefault){
		return typeof v==="undefined"?vDefault:v;
	}
};
10/05/2015: ho aggiornato la funzione di redraw sostituendo setInterval con window.requestAnimationFrame di HTML 5. Rimane il fallback a setInterval per i browser che non la supportano:
requestAnimFrame = (function(callback) {
  return window.requestAnimationFrame 
	|| window.webkitRequestAnimationFrame 
	|| window.msRequestAnimationFrame 
	|| window.mozRequestAnimationFrame 
	|| window.oRequestAnimationFrame 
	|| function(callback) { window.setTimeout(callback, 1000 / 60); };
})();
Il vantaggio della funzione requestAnimationFrame è che viene gestita automaticamente dal browser secondo la frequenza di refresh supportata.
Per usarla chiamare requestAnimFrame passando la funzione di redraw del canvas. Dalla funzione di redraw richiamare ancora requestAnimFrame(cllaback) che verrà eseguita del browser al prossimo refresh.