Class-based Javascript analog clock

The div tag is not replaced in this custom element, but instead is used as a pseudo-document to hold the clock itself. The 'hands' are implemented using arrays of generated divs.

I was actually inspired to write this widget because of an article on About.com's Javascript pages written by a guy named Stephen Chapman. His code was a mess, but I only discovered this after delving through two layers of 'unescape()' obfuscation.

The basics: You control the look of the numbers and the 'box' model of the clock using basic CSS. You control the clock's size (it will always be square) using the 'size' attribute.

The 'seconds', 'minutes', and 'hours' attributes are for the color of the 'hand's. 'numstyle' can be 1, i, I, or '.', each refering to a different type of face-numbering for the clock

The 'localzone' attribute tells the script to use the user's local time zone. If it's '0', 'false' or 'no', the clock will use the 'clockzone' attribute to determine what time it is (the clock zone is relative to GMT, so, for example, US Eastern is -5. The clock automagically figures out if we're in DST, so you don't have to.)

The 'city' and 'country' are for if you want to put in the city/country of origin for your clock. To be honest, you can put anything there.

Lastly, I designed my version of the clock to be able to not 'tick', i.e., work like the old-school analog clocks. If you want ticking, add 'tick=1' to the attribute list

As with all of my widgets, the idea here is that you can just include the .js file and start dropping in tags without needing to know any ECMAScript at all.

Additionally, for you ECMAScripters, I was very careful to keep my pollution of the global namespace to one class: analogClock.


Example of use:




var analogClock = function (divElem, index) {
        this.mainElem = divElem;
        this.index = index;

        var numStyles = Array();
        numStyles["1"] = Array("1","2","3","4","5","6","7","8","9","10","11","12");
        numStyles["i"] = Array("i","ii","iii","iv","v","vi","vii","viii","ix","x","xi","xii");
        numStyles["I"] = Array("I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII");
        numStyles["."] = Array("·","·","—","·","·","|","·","·","—","·","·","||");
        
        this.position = analogClock.prototypes.position;
        numstyle = String(this.mainElem.getAttribute('numstyle'));
        if ("1I.".indexOf(numstyle.toUpperCase())==-1) numstyle="1";
        var numstyle=numStyles[numstyle];
        
        var radius = Math.floor(parseInt(this.mainElem.getAttribute('size'))/2);
        if (isNaN(radius)) radius=50;
        this.mainElem.style.width=radius*2;
        this.mainElem.style.height=radius*2;
        this.mainElem.style.position='relative';
        this.mainElem.style.overflow='hidden';
        this.mainElem.style.margin='0px';
        this.mainElem.style.padding='0px';
        //Create Hour labels
        this.hourLabels = Array();
        for (var i=0; i<12; i++) {
                this.hourLabels[i]=document.createElement("div");
                this.hourLabels[i].appendChild(document.createTextNode(numstyle[i]));
                this.hourLabels[i].style.position='absolute';
                this.mainElem.appendChild(this.hourLabels[i]);    
        }
        var numhours = Math.floor(radius*0.5);
        this.hourHand = Array();
        for (i=0; i<numhours; i++) {
                this.hourHand[i]=document.createElement("div");
               this.hourHand[i].style.backgroundColor=this.mainElem.getAttribute("hours");
                this.hourHand[i].style.width=Math.ceil(radius*3/75)+1;
                this.hourHand[i].style.height=Math.ceil(radius*3/75)+1;
                this.hourHand[i].style.position='absolute';
                this.hourHand[i].style.overflow='hidden';
                this.mainElem.appendChild(this.hourHand[i]);
        }
        var numminutes = Math.floor(radius*0.75);
        this.minuteHand = Array();
        for (i=0; i<numminutes; i++) {
                this.minuteHand[i]=document.createElement("div");
               this.minuteHand[i].style.backgroundColor=this.mainElem.getAttribute("minutes");
                this.minuteHand[i].style.width=Math.round(radius*2/75)+1;
                this.minuteHand[i].style.height=Math.round(radius*2/75)+1;
                this.minuteHand[i].style.position='absolute';
                this.minuteHand[i].style.overflow='hidden';
                this.mainElem.appendChild(this.minuteHand[i]);
        }
        var numseconds = Math.floor(radius*0.90);
        this.secondHand = Array();
        for (i=0; i<numseconds; i++) {
                this.secondHand[i]=document.createElement("div");
               this.secondHand[i].style.backgroundColor=this.mainElem.getAttribute("seconds");
                this.secondHand[i].style.width=Math.floor(radius*1/75)+1;
                this.secondHand[i].style.height=Math.floor(radius*1/75)+1;
                this.secondHand[i].style.position='absolute';
                this.secondHand[i].style.overflow='hidden';
                this.mainElem.appendChild(this.secondHand[i]);
        }
        this.AmPm=document.createElement("div");
        this.AmPm.style.position="absolute";
        this.mainElem.appendChild(this.AmPm);
        
        this.DoW=document.createElement("div");
        this.DoW.style.position="absolute";
        this.mainElem.appendChild(this.DoW);
        
        this.City=document.createElement("div");
        this.City.style.position="absolute";
        this.City.font="8pt Times";
        this.mainElem.appendChild(this.City);

        this.Country=document.createElement("div");
        this.Country.style.position="absolute";
        this.Country.font="8pt Times";
        this.mainElem.appendChild(this.Country);
        
  this.position();
}
analogClock.prototypes = Array();
analogClock.prototypes.position = function () {
        var Week = Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday","Saturday");
        var radius = Math.floor(parseInt(this.mainElem.getAttribute('size'))/2);
        if (isNaN(radius)) radius=50;
        var Today = new Date();
        var Mills = Today.getMilliseconds();
        var Second = Today.getSeconds()+Mills/1000;
        var Minute = Today.getMinutes()+Second/60;
        var Hour = Today.getHours()+Minute/60;
        if (this.mainElem.getAttribute('tick')) {
                Hour=Math.floor(Hour);
                Minute=Math.floor(Minute);
                Second=Math.floor(Second);
        }
        if ("1\\yes\\true".indexOf(this.mainElem.getAttribute('localzone').toLowerCase())==-1) {
                Hour-=analogClock.getGMT();
                cz=parseInt(this.mainElem.getAttribute('clockzone'));
                Hour+=cz+analogClock.needDST(cz);              
        }
        
        while (Hour>=24) Hour-=12;
        while (Hour<0) Hour+=12;
        for (var i=0; i<this.hourLabels.length; i++) {
                radpos = (i+1)*2*Math.PI/12-Math.PI/2;
                this.hourLabels[i].style.top=radius+Math.sin(radpos)*radius*0.90-parseInt(this.hourLabels[i].offsetHeight)/2;
                this.hourLabels[i].style.left=radius+Math.cos(radpos)*radius*0.90-parseInt(this.hourLabels[i].offsetWidth)/2;
        }
        radpos=(Hour%12)*Math.PI*2/12-Math.PI/2;
        for (i=0; i<this.hourHand.length; i++) {
                this.hourHand[i].style.top=radius+Math.sin(radpos)*i;
                this.hourHand[i].style.left=radius+Math.cos(radpos)*i;
        }
        radpos=Minute*Math.PI*2/60-Math.PI/2;
        for (i=0; i<this.minuteHand.length; i++) {
                this.minuteHand[i].style.top=radius+Math.sin(radpos)*i;
                this.minuteHand[i].style.left=radius+Math.cos(radpos)*i;
        }
        radpos=Second*Math.PI*2/60-Math.PI/2;
        for (i=0; i<this.secondHand.length; i++) {
                this.secondHand[i].style.top=radius+Math.sin(radpos)*i;
                this.secondHand[i].style.left=radius+Math.cos(radpos)*i;
        }
        this.AmPm.innerHTML=(Hour>=12)?"PM":"AM";
        this.AmPm.style.bottom=1.333*radius;
        this.AmPm.style.left=radius-parseInt(this.AmPm.offsetWidth)/2;
        this.AmPm.style.zIndex=-1;
        
        this.DoW.innerHTML=Week[Today.getDay()];
        this.DoW.style.top=0.666*radius;
        this.DoW.style.left=radius-parseInt(this.DoW.offsetWidth)/2;
        this.DoW.style.zIndex=-1;
        
        
        this.City.innerHTML=this.mainElem.getAttribute('city');
        this.City.style.bottom=0.666*radius;
        this.City.style.left=radius-parseInt(this.City.offsetWidth)/2;
        this.City.style.zIndex=-1;
        
        this.Country.innerHTML=this.mainElem.getAttribute('country');
        this.Country.style.top=1.333*radius;
        this.Country.style.left=radius-parseInt(this.Country.offsetWidth)/2;
        this.Country.style.zIndex=-1;
}

analogClock.arClocks = new Array();

analogClock.replace = function () {
        var Divs = document.getElementsByTagName('div');
        for (var i=0; i<Divs.length; i++) 
                if (Divs[i].className.toLowerCase().indexOf("analogclock")!=-1)
                        analogClock.arClocks[analogClock.arClocks.length]=newanalogClock(Divs[i],analogClock.arClocks.length);
        setTimeout('analogClock.update();', 100);
}

analogClock.update = function () {
        for (var i=0; i<analogClock.arClocks.length; i++) 
                analogClock.arClocks[i].position();
        var interval = Math.floor(50*Math.pow(analogClock.arClocks.length,1.5));
        setTimeout('analogClock.update();', (analogClock.active)?(interval):(1000))
}

analogClock.getGMT = function () {
        var day;
        var Today = new Date();
        var gmtAdjust=-Today.getTimezoneOffset()/60;
        var lsm = new Date;
        var lso = new Date;
        lsm.setMonth(2); lsm.setDate(31); day = lsm.getDay(); lsm.setDate(31-day); 
        lso.setMonth(9); lso.setDate(31); day = lso.getDay(); lso.setDate(31-day);
        if (Today < lsm || Today >= lso) gmtAdjust -= 1;
        return gmtAdjust;
}

analogClock.needDST = function (a) {
        if ((a<-8)||(a>-5)) return 0;
        var day;
        var Today = new Date();
        var dst=0;
        var lsm = new Date;
        var lso = new Date;
        lsm.setMonth(2); lsm.setDate(31); day = lsm.getDay(); lsm.setDate(31-day); 
        lso.setMonth(9); lso.setDate(31); day = lso.getDay(); lso.setDate(31-day);
        if (Today >= lsm || Today < lso) dst = 1;
        return dst;
}

analogClock.suspend = function () {
        analogClock.active=false;
}

analogClock.resume = function () {
        analogClock.active=true;
}

analogClock.attach = function () {
        if (window.attachEvent) {
                window.attachEvent('onload',analogClock.replace);
                window.attachEvent('onblur',analogClock.suspend);
                window.attachEvent('onfocus',analogClock.resume);
        }
        if (window.addEventListener) {
                window.addEventListener('load',analogClock.replace,false);
                window.addEventListener('blur',analogClock.suspend,false);
                window.addEventListener('focus',analogClock.resume,false);
        }
}

analogClock.active=true;
analogClock.attach();

Comments