2

This is my 'very first' foray into keyframe animation. I was put off before by the alleged inability to dynamically change hard coded CSS values on the fly via JavaScript.

I Googled around a bit to find it 'can' be done but with a lot of fiddly work finding, removing and replacing parts of or whole stylesheets.

Anyway, here's my effort. The eventual goal of this particular script is to be the bounce of clock hand. I can already do this with transition and cubic bezier but can only get one bounce in. I've seen this effect using various js libraries etc but you know how it is - you like to roll your own.

My question is - Is there anything glaringly wrong with my method below. Ignore the trivia, it's just a test. What I want looking at is my GenerateKeyFrames() function and the two methods of calling it.

Thanks for any info.

    var d = document;
    var incr = 1;
    var step = -6; //Initial start pos.

    var coreCss = 'display: block;position: absolute;'
               +'left: 100px;top: 10px;height: 95px;'
               +'width: 4px;background-color: #000;'
               +'transform-origin: center bottom;';

    var initialAniCss = 'animation: reGen'+incr+' 1s 1 forwards;';
    coreCss += initialAniCss;

    var elementToAnimate = d.createElement('div');
    elementToAnimate.setAttribute('style', coreCss);
    d.body.appendChild(elementToAnimate);

    function GenerateKeyFrames() {

    /* Remove previous 'tmpSheet' stylesheet */
    var currSheet = (d.getElementById('tmpSheet'));
    if (currSheet) {
        //currSheet.parentNode.removeChild(currSheet);// Doesn't seem as smooth as other 2 below!
        //currSheet.remove(); - Not IE, shame.
        currSheet.outerHTML = '';
    }

    /* Movement in degrees */
    var p1 = step;
    var p2 = step+6;
    var p3 = step+4;
    var p4 = step+6;
    var p5 = step+5; 
    var p6 = step+6;

    /* Create new keyframe. Must have new name! Adding an incrementing var to name does ok */
    var frames = '@keyframes reGen'+incr+' { '
    +'0% { transform: rotate('+p1+'deg) translateZ(0); animation-timing-function: ease-in;}'
    +'25% { transform: rotate('+p2+'deg) translateZ(0); animation-timing-function: ease-out;}'
    +'45% { transform: rotate('+p3+'deg) translateZ(0); animation-timing-function: ease-in;}'
    +'65% { transform: rotate('+p4+'deg) translateZ(0); animation-timing-function: ease-out;}' 
    +'75% { transform: rotate('+p5+'deg) translateZ(0); animation-timing-function: ease-in;}'
    +'85%,100% { transform: rotate('+p6+'deg) translateZ(0);animation-timing-function: ease-out;}}';

    /* Create new tmpSheet style sheet to head */
    var s = document.createElement( 'style' );
    s.setAttribute("id", "tmpSheet");
    s.innerHTML = frames;
    document.getElementsByTagName('head')[0].appendChild(s);

    /* Put it all together and it's ready to go */ 
    var newAni = 'animation: reGen'+incr+' 1s 1 forwards;';
    coreCss += newAni;
    elementToAnimate.setAttribute('style', coreCss);
    d.body.appendChild(elementToAnimate);
}
GenerateKeyFrames();

/* Can be called on animation end - or timed function rep().
elementToAnimate.addEventListener("animationend",function(e) {
    incr++;
    step += 6;
    elementToAnimate.removeAttribute("style");
    GenerateKeyFrames();
    },false);
*/

function rep() {
    incr++;
    step += 6;
    elementToAnimate.removeAttribute("style");
    GenerateKeyFrames();
setTimeout(rep, 2000);
}
rep();

5
  • 1
    To me this just doesn't make sense--writing all this javascript to generate CSS3 animations. Just use jQuery.animate() and be done with it. Commented Aug 31, 2015 at 17:47
  • @dave and what is wrong with learning how to code stuff yourself. Rolling your own, as the OP says. Commented Aug 31, 2015 at 18:37
  • @Griggy I'm not sure if you mean it doesn't work as expected, or that it does but you're afraid you're not doing it right. Commented Aug 31, 2015 at 18:39
  • @ Mr Lister - Yes, the second one - doing it right. It works perfectly but vals a few posts down has pointed out the coreCSS var is not clearing. Commented Aug 31, 2015 at 22:35
  • @Daemedeor - yes I know but like I said it's just a test. Cheers anyway. Commented Aug 31, 2015 at 22:38

5 Answers 5

2

I don't really like the idea of inserting all those keyframes dynamically with unique names. If I were doing this and trying to use CSS keyframe animations, I'd create two animations, one that provides your "double-bounce" effect, and one that rotates a wrapper around the center.

Using just HTML and CSS, you can almost create a full working clock, although you don't have any way to start it at the right time without JavaScript.

This was a fun exercise though, and I was able to recreate the whole clock effect using just keyframe animations with a tiny bit of JavaScript to determine the starting time of the animation and supply a negative animation-delay to offset the animations to the proper timeframes.

The time can wander a bit, but really not as much as you might think. I left the animations running overnight in Chrome, FF, and IE, and allowed my computer to go to sleep, and 9 hours later the clocks were still keeping perfect time. However, when I left it running in a background tab of my phone overnight, the clock was completely broken in the morning. Though, seeing as how the whole purpose is to display a clock on a webpage, it's unlikely that the page would stick around long enough for the clock to be noticeably off.

Anyway, here's the snippet:

window.addEventListener("load", function() {
  var now = new Date();
  var secondsDelay = now.getSeconds();
  var minutesDelay = now.getMinutes() * 60 + secondsDelay;
  var hoursDelay = (now.getHours() % 12) * 3600 + minutesDelay;
  var minuteHand = document.querySelector(".minute .hand");
  var minuteWrapper = document.querySelector(".minute");
  var secondWrapper = document.querySelector(".second");
  var hourWrapper = document.querySelector(".hour");

  //set animation offsets with negative delay
  minuteHand.style.animation = "minuteTick 60s -" + secondsDelay + "s infinite";
  secondWrapper.style.animation = "rotateHolder steps(60) 60s -" + secondsDelay + "s infinite";
  minuteWrapper.style.animation = "rotateHolder steps(60) 3600s -" + minutesDelay + "s infinite";
  hourWrapper.style.animation = "rotateHolder steps(43200) 43200s -" + hoursDelay + "s infinite";

  //start running
  document.querySelector(".clock").classList.add("running");
});
.clock {
  width: 200px;
  height: 200px;
  background-color: gray;
  border-radius: 50%;
}
.sectionWrapper {
  width: 200px;
  height: 200px;
  position: absolute;
  transform: rotate(0deg);
}
.clock.running .second {
  animation: rotateHolder 60s steps(60) infinite;
}
.clock.running .second .hand {
  animation: secondTick 1s 0s infinite;
}
.clock.running .minute {
  animation: rotateHolder 3600s steps(60) infinite;
}
.clock.running .minute .hand {
  animation: minuteTick 60s 0s infinite;
}
.clock.running .hour {
  animation: rotateHolder 43200s steps(43200) infinite;
}
.hand {
  display: block;
  position: absolute;
  background-color: #000;
  transform-origin: center bottom;
}
.second .hand {
  left: 99px;
  top: 5px;
  height: 95px;
  width: 2px;
  background-color: red;
}
.minute .hand {
  left: 98px;
  top: 15px;
  height: 85px;
  width: 4px;
}
.hour .hand {
  left: 97px;
  top: 45px;
  height: 55px;
  width: 6px;
}
@keyframes secondTick {
  0% { transform: rotate(0deg); animation-timing-function: ease-in; }
  25% { transform: rotate(6deg); animation-timing-function: ease-out; }
  45% { transform: rotate(4deg); animation-timing-function: ease-in; }
  65% { transform: rotate(6deg); animation-timing-function: ease-out; } 
  75% { transform: rotate(5deg); animation-timing-function: ease-in; }
  85%,100% { transform: rotate(6deg);animation-timing-function: ease-out; }				
}
@keyframes minuteTick {
  0%,98.3% { transform: rotate(0deg); animation-timing-function: ease-in; }
  98.8% { transform: rotate(6deg); animation-timing-function: ease-out; }
  99.1% { transform: rotate(4deg); animation-timing-function: ease-in; }
  99.4% { transform: rotate(6deg); animation-timing-function: ease-out; } 
  99.6% { transform: rotate(5deg); animation-timing-function: ease-in; }
  99.8%,100% { transform: rotate(6deg);animation-timing-function: ease-out; }				
}
@keyframes rotateHolder {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
<div class="clock">
  <div class="hour sectionWrapper">
    <div class="hand"></div>
  </div>
  <div class="minute sectionWrapper">
    <div class="hand"></div>
  </div>
  <div class="second sectionWrapper">
    <div class="hand"></div>
  </div>
</div>

Sign up to request clarification or add additional context in comments.

5 Comments

Excellent, nice bit of sideways thinking. Now if I could get it to update via getSeconds() that would be ideal. I'm not keen on other timing methods for clocks as they always drift. I've already updated mine, posted below, but I'll certainly play around with your wrapper method. Thank again.
@Griggy this idea really intrigued me so I implemented the whole clock and posted an updated answer. After letting it run overnight the time did not wander at all in my desktop browsers. Only when I ran it in my phone overnight did the animation get completely out-of-sync with the actual time.
I'm impressed with your idea and 'lack of code'. I've completed my clock in basic form - code will quadruple with fancy css styling. If I can marry your minimalism to my timing then it would be a tasty little core clock mechanism. I hear what you say about no one staying around to notice drift - but I'd know lol. My finished effort is posted below somewhere.
You may be able setup a timer and re-sync the animation every 5-10 minutes (or whatever), so it can't get too far out of sync. I'll try to look into this later when I have some more time.
All done. Had a spare hour and reused the css from one of my old clocks. It's a bit lengthy but it can be just stuck into a js file and placed where ever in a webpage. As far as I can see it isn't collecting any garbage and it runs about 0.8% to 2% cpu on my laptop. Unless I've missed anything I'm quite happy with my first crack at keyframes.
2

Finished clock.

(function () {

    /* Station Style Clock - [email protected] */

    /* ^^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^^ */

    var clockSize = 400;
    var clockColour = 'rgb(255,255,255)';
    var secHandColour = 'rgb(255,255,255)';

    /* ^^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^^^ */

    var d = document;
    var mcon = [];
    var mrkrs = [];
    var e = 360/60;
    var degr = -6;
    var mls = 100;
    var prev = performance.now();
    var radi = Math.PI / 180;
    var offs = 90 * radi;
    var rndId = 'id'+Math.random() * 1;
    var secSpan = '.8s';
    var minSpan = '1s';
    var houSpan = '1s';
    var secIncr = 0;
    var minIncr = 0;
    var houIncr = 0;
    var secDeg, minDeg, houDeg, preSec, preMin, preHou;
    var idx = d.getElementsByTagName('div').length;
    var shdcol = 'rgba(0,0,0,0.6)';
    var eiatf = ' translateZ(0); animation-timing-function: ease-in';
    var eoatf = ' translateZ(0); animation-timing-function: ease-out';

    d.write('<div id = "'+rndId+'" style="display:inline-block;line-height:0px;"></div>');

    function xy (a) {
        return (a * (clockSize * 2) / 100);
    }

    function shdw(s, h) {
        var depth = xy(h);
        var angle = s * radi;
        var vsa = depth * Math.cos(angle);
        var hsa = depth * Math.sin(angle);
        return {vsa:vsa, hsa:hsa}
    }

    var dial = d.createElement('div');
    dial.setAttribute('style', 'display: inline-block;'
        +'position: relative;'
        +'height: '+xy(100)+'px;'
        +'width: '+xy(100)+'px;'
        +'background-color:transparent;'
        +'border-radius: 50%;'
        +'margin: -'+xy(24)+'px -'+xy(24)+'px -'+xy(24)+'px -'+xy(24)+'px;');

    d.getElementById(rndId).style.transform = 'scale3d(.5,.5,1)';
    d.getElementById(rndId).appendChild(dial); 

    for (var i = 0; i < 60; i++) {

        var mky = shdw(i * 6, 0.5).vsa;
        var mkx = shdw(i * 6, 0.5).hsa;

        var len = (i % 5) ? 4 : 8;
        var wid = (i % 5) ? .8 : 4;

        mcon[i] = d.createElement('div');
        mcon[i].setAttribute('style', 'display: block;'
            +'position: absolute;'
            +'width: '+xy(4)+'px;'
            +'height: '+xy(8)+'px;'
            +'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
            +'font-size: 0px;line-height: 0px;padding: 0;'
            +'color: '+clockColour+';');

        mrkrs[i] = d.createElement('div');
        mrkrs[i].setAttribute('style', 'display: block;'
            +'position: absolute;'
            +'width: '+xy(wid)+'px;'
            +'height: '+xy(len)+'px;'
            +'margin: auto; top: 0;left: 0;right: 0;'
            +'font-size: 0px;line-height: 0px;padding: 0;'
            +'box-shadow:'+mkx+'px '+mky+'px 0px 0px rgba(0,0,0,0.7);'
            +'background-color:'+clockColour+';');
        mcon[i].appendChild(mrkrs[i]);
        dial.appendChild(mcon[i]);
        degr += 6;
        mcon[i].style.top = xy(0) + xy(86) * Math.sin(-offs + e * i * radi) + 'px';
        mcon[i].style.left= xy(0) + xy(86) * Math.cos(-offs + e * i * radi) + 'px';
        mcon[i].style.transform = 'rotate(' + (degr) + 'deg)';
        mcon[i].style.transformOrigin = 'center center';
    }

    /* Generic container div for all hands */

    var handContainers = 'display: block;'
        +'position: absolute;'
        +'height: '+xy(100)+'px;'
        +'width: '+xy(16)+'px;'
        +'font-size: 0px; line-height: 0px; padding: 0;'
        +'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
        +'transform-origin: center center;';
    

    /* Hour hand CSS */

    var houClone = handContainers;
    var houHand = d.createElement('div');
    houHand.setAttribute('style', houClone);
    
    dial.appendChild(houHand);

    var hh = d.createElement('div');
    hh.setAttribute('style', 'display: block;'
        +'position: absolute;'
        +'height: '+xy(48)+'px;'
        +'width: '+xy(5)+'px;'
        +'top: '+xy(15)+'px;'
        +'margin: auto; left: 0; right: 0;'
        +'outline: 1px solid transparent;'
        +'background-color: '+clockColour+';');
    houHand.appendChild(hh);

    var houShad = houHand.cloneNode(true);
    houShad.childNodes[0].style.backgroundColor = shdcol;

    /* Minute hand CSS */

    var minClone = handContainers;
    var minHand = d.createElement('div');
    minHand.setAttribute('style', minClone);

    dial.appendChild(minHand);

    var mh = d.createElement('div');
    mh.setAttribute('style', 'display:block;'
        +'position: absolute;'
        +'height: '+xy(58)+'px;'
        +'width: '+xy(3)+'px;'
        +'top: '+xy(5)+'px;'
        +'margin: auto; left: 0; right: 0;'
        +'outline: 1px solid transparent;'
        +'background-color: '+clockColour+';');
    minHand.appendChild(mh);

    var minShad = minHand.cloneNode(true);
    minShad.childNodes[0].style.backgroundColor = shdcol;

    /* Seconds hand CSS */

    var secClone = handContainers;
    var secHand = d.createElement('div');
    secHand.setAttribute('style', secClone);
    dial.appendChild(secHand);

    var sh = d.createElement('div');
    var svgsec = '<svg height="'+xy(100)+'" width="'+xy(16)+'">'
    +'<g>'
    +'<rect id="hd" x="'+xy(7.45)+'" y="'+xy(3)+'" width="'+xy(1)+'" height="'+xy(60)+'" stroke-width="0" fill="'+secHandColour+'"/>'
    +'<circle id="cw" cx="50%" cy="'+xy(67)+'" r="'+xy(4)+'" stroke-width="0" stroke="'+secHandColour+'" fill="'+secHandColour+'"/>'
    +'</g>'
    +'</svg>';
    sh.innerHTML = svgsec;
    secHand.appendChild(sh);

    var secShad = secHand.cloneNode(true);
    var newColor = '"'+shdcol+'"';
    var clnshd = svgsec.split(secHandColour).join("");
    clnshd = clnshd.replace(/""/g, newColor);
    secShad.innerHTML = clnshd;

    var nut = d.createElement('div');
    nut.setAttribute('style', 'display: block;'
        +'position: absolute;'
        +'height: '+xy(4)+'px;'
        +'width: '+xy(4)+'px;'
        +'border-radius: 50%;'
        +'margin: auto;top: 0;bottom: 0;left: 0;right: 0;'
        +'background-color: '+secHandColour+';'
        +'box-shadow:'+xy(0)+'px '+xy(0.5)+'px 0px 0px rgba(0,0,0,0.7);'
        +'z-index: 105;');
    dial.appendChild(nut);

    function houKeyFrames() {
        var houSheet = (d.getElementById('tmphouSheet'+idx));
        if (houSheet) {
            houSheet.parentNode.removeChild(houSheet);
        }

        houClone = handContainers;
 
        var p1 = houDeg;
        var p2 = houDeg+1;
        var p3 = houDeg+0.4;
        var p4 = houDeg+1;
        var p5 = houDeg+0.5; 
        var p6 = houDeg+1; 

        var houframes = '@keyframes hou'+idx+'gen'+houIncr+' { '
        +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
        +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
        +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
        +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
        +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
        +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

        var hs = document.createElement( 'style' );
        hs.setAttribute('id', 'tmphouSheet'+idx);
        hs.innerHTML = houframes;
        d.getElementsByTagName('head')[0].appendChild(hs);

        var houAni = 'animation: hou'+idx+'gen'+houIncr+' '+houSpan+' 1 forwards;';
        houClone += houAni;
        houHand.setAttribute('style', houClone);
        houHand.style.zIndex = 100;
        dial.appendChild(houHand);

        houShad.setAttribute('style', houClone);
        houShad.style.top = xy(2.5)+'px';
        houShad.style.zIndex = 99;
        dial.appendChild(houShad);
    }

    function minKeyFrames() {
        var minSheet = (d.getElementById('tmpMinSheet'+idx));
        if (minSheet) {
            minSheet.parentNode.removeChild(minSheet);
        }

        minClone = handContainers;

        var p1 = minDeg;
        var p2 = minDeg+6;
        var p3 = minDeg+4;
        var p4 = minDeg+6;
        var p5 = minDeg+5; 
        var p6 = minDeg+6;

        var minframes = '@keyframes min'+idx+'gen'+minIncr+' { '
        +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
        +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
        +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
        +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
        +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
        +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

        var ms = document.createElement( 'style' );
        ms.setAttribute('id', 'tmpMinSheet'+idx);
        ms.innerHTML = minframes;
        d.getElementsByTagName('head')[0].appendChild(ms);

        var minAni = 'animation: min'+idx+'gen'+minIncr+' '+minSpan+' 1 forwards;';
        minClone += minAni;
        minHand.setAttribute('style', minClone);
        minHand.style.zIndex = 102;
        dial.appendChild(minHand);

        minShad.setAttribute('style', minClone);
        minShad.style.top = xy(3)+'px';
        minShad.style.zIndex = 101;
        dial.appendChild(minShad);
    }

    function secKeyFrames() {
        var secSheet = (d.getElementById('tmpSecSheet'+idx));
        if (secSheet) {
            secSheet.parentNode.removeChild(secSheet);
        }
        
        secClone = handContainers;
 
        var p1 = secDeg;
        var p2 = secDeg+6;
        var p3 = secDeg+4;
        var p4 = secDeg+6;
        var p5 = secDeg+5; 
        var p6 = secDeg+6; 

        var secframes = '@keyframes sec'+idx+'gen'+secIncr+' { '
        +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
        +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
        +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
        +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
        +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
        +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

        var ss = document.createElement( 'style' );
        ss.setAttribute('id', 'tmpSecSheet'+idx);
        ss.innerHTML = secframes;
        document.getElementsByTagName('head')[0].appendChild(ss);

        var secAni = 'animation: sec'+idx+'gen'+secIncr+' '+secSpan+' 1 forwards;';
        secClone += secAni;
        secHand.setAttribute('style', secClone);
        secHand.style.zIndex = 104;
        dial.appendChild(secHand);

        secShad.setAttribute('style', secClone);
        secShad.style.top = xy(3.5)+'px';
        secShad.style.zIndex = 103;
        dial.appendChild(secShad);
    }

    function clock() {
        var x = new Date();
        var seconds = x.getSeconds();
        var minutes = x.getMinutes();
        var hours = (x.getHours() * 30) + (x.getMinutes() / 2);
        
        if (seconds !== preSec) {
            secIncr++;
            secDeg = (seconds-1) * 6;
            secHand.removeAttribute('style');
            secKeyFrames();
            if (secIncr > 59) {
                secIncr = 0;
            }
        }

        if (minutes !== preMin) {
            minIncr++;
            minDeg = (minutes-1) * 6;
            minHand.removeAttribute('style');
            minKeyFrames();
            if (minIncr > 59) {
                minIncr = 0;
            }
        }

        if (hours !== preHou) {
            houIncr++;
            houDeg = (hours-1) * 1;
            houHand.removeAttribute('style');
            houKeyFrames();
            if (houIncr > 59) {
                houIncr = 0;
            }  
        }

        preSec = seconds;
        preMin = minutes;
        preHou = hours;
    }

    function cyc() {
        var pres = performance.now(); 
        if ((pres - prev) > mls) {
            clock();
            prev = performance.now();
        }
        window.requestAnimationFrame(cyc);
    } 

    window.addEventListener('load', cyc, false);
})();
<!doctype html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Clock</title>

    <style type="text/css">
        html {
            height: 100%;
        }
        
        body {
            background-color: #ffff00;
            text-align: center;
        }
    </style>
</head>

<body>
  
  
  
</body>
</html>  

Comments

1

This seems way too complicated. Whenever I'm working with complex animations, I use GSAP. Here's a simplied demo that I made. It uses basic JQuery and the GSAP animation engine, TweenMax.

You can easily adjust the easing with GSAP via their ease visualizer

$(document).ready(function() {
  var clock = $('.clock'); // clock container
  var arm = $('.arm'); // seconds arm
  var log = $('.log'); // log beneath clock
  var position =  90; // starting position at 0 seconds
  var time = 0; 

setInterval(function(){
  position += 6; // 360 / 60 = +6 deg per second
  time ++; 
  TweenLite.to(arm, 0.3, {rotation:position, transformOrigin:"right bottom", ease: Back.easeOut.config(2), y: 0 });
  log.text(time + " seconds and " + position +  " degs");
 }, 1000);

});

2 Comments

The OP already explained that they know there are libraries to do it, but they prefer to roll their own. So I must ask, how is the OP's little standalone piece of JavaScript more complicated than including the entire jQuery library plus another 100K library to do the same?
@Matt Wujek - as per Mr Lister's comment above. It's just a personal thing of mine of not liking a 'middle man'. I've seen work produced by most of the popular libraries and yes it's amazing - but for me there's nothing more satisfying than doing it all yourself. Ps: I still write EVERYTHING by hand in NotePad too!
1

Focusing just in the code, I find it quite good.

my only concern would be

/* Put it all together and it's ready to go */ 
var newAni = 'animation: reGen'+incr+' 1s 1 forwards;';
coreCss += newAni;

Note that coreCss will at the end have lots of animations (verified in dev tools)

You should just clear it.

But given that the animation is setting the position via forwards, may be that can give problems ...

If this is the case, may be playing with just 2 animations ??

In case that you want advice about how I would get that effect, I would set 2 nested elements, one with the elaborate keyframes that you have , and running every second. And the other making the full circle, stepped 60 times ...

2 Comments

@ vals - Thanks. Exactly what I was looking for, in particular the coreCSS var
coreCSS now clears on every cycle. Thanks again for pointing that out.
0

BBC clock example for Dave.

(function () {

/* The BBC Analogue Clock - [email protected] */

/* ^^^^^^^^^^^^^^^^^^^ Config below ^^^^^^^^^^^^^^^^^^^ */

var clockSize = 300;
var dialcol = 'rgba(0,0,255,0.9)';
var handcol = 'rgb(230,230,230)';

/* ^^^^^^^^^^^^^^^^^^^ End config ^^^^^^^^^^^^^^^^^^^ */

var d = document;
var mrkrs = [];
var e = (360 / 12);
var degr = 0;
var mls = 100;
var prev = performance.now();
var radi = Math.PI / 180;
var offs = 60 * radi;
var rndId = 'id'+Math.random() * 1;
var sSpan = '.9s';
var mSpan = '1s';
var hSpan = '1s';
var sIncr = 0;
var mIncr = 0;
var hIncr = 0;
var sDeg, mDeg, hDeg, sPre, mPre, hPre;
var idx = d.getElementsByTagName('div').length;
d.write('<div id = "'+rndId+'" style="display:inline-block;line-height:0px;"></div>');

function xy(a) {
    return (a * clockSize / 100);
}

var dial = d.createElement('div');
dial.setAttribute('style', 'display: inline-block;'
    +'position: relative;'
    +'height: '+clockSize+'px;'
    +'width: '+clockSize+'px;'
    +'margin: 0px;padding: 0px;'
    +'border-radius: 5%;z-index: 1;'
    +'background-color: '+dialcol+';');
d.getElementById(rndId).appendChild(dial);

for (var i = 0; i < 12; i++) {

    var incr = xy(2.0) + (i * xy(0.2));

    mrkrs[i] = d.createElement('div');
    mrkrs[i].setAttribute('style', 'display: block;'
        +'position: absolute;'
        +'width: '+xy(14)+'px;'
        +'height: '+xy(14)+'px;'
        +'margin: auto; top: 0;bottom: 0;left: 0;right: 0;'
        +'font-size: 0px;line-height: 0px;padding: 0;'
        +'text-align: center !important;'
        +'background-color: transparent;');

    mrkrs[i].innerHTML = '<div style = "display: inline-block;'
    +'position: relative;width: '+incr+'px;height: '+xy(14)+'px;'
    +'font-size: 0px;background-color:'+handcol+';'
    +'margin-right: '+xy(0.6)+'px;"></div>'
    +'<div style = "display:inline-block;position: relative;'
    +'width: '+incr+'px;height: '+xy(14)+'px;font-size: 0px;'
    +'margin-left: '+xy(0.6)+'px;'
    +'background-color:'+handcol+';"></div>';

    dial.appendChild(mrkrs[i]);
    degr += 30;

    mrkrs[i].style.top = xy(0) + xy(77) * 
        Math.sin(-offs + e * i * radi) + 'px';
    mrkrs[i].style.left= xy(0) + xy(77) * 
        Math.cos(-offs + e * i * radi) + 'px';
    mrkrs[i].style.transform = 'rotate(' + (degr) + 'deg)';
    mrkrs[i].style.transformOrigin = 'center center';
}

/* Hour CSS */

var hCss = 'display: block;'
    +'position: absolute;'
    +'height: '+xy(56)+'px;'
    +'width: '+xy(6)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
    +'transform-origin: center center 0;'
    +'z-index: 2;';
var hClone = hCss;

var houHand = d.createElement('div');
houHand.setAttribute('style', hClone);
dial.appendChild(houHand);

var hh = d.createElement('div');
hh.setAttribute('style', 'display: block;'
    +'position: absolute;'
    +'height: '+xy(21)+'px;'
    +'width: '+xy(6)+'px;' 
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0; left: 0; right: 0;'
    +'background-color: '+handcol+';');
houHand.appendChild(hh);

/* Minute CSS */

var mCss = 'display: block;'
    +'position: absolute;'
    +'height: '+xy(86)+'px;'
    +'width: '+xy(4)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0; bottom: 0; left: 0; right: 0;'
    +'transform-origin: center center;'
    +'z-index: 3;';
var mClone = mCss;

var minHand = d.createElement('div');
minHand.setAttribute('style', mClone);
dial.appendChild(minHand);

var mh = d.createElement('div');
mh.setAttribute('style', 'display: block;'
    +'position: absolute;'
    +'height: '+xy(36)+'px;'
    +'width: '+xy(4)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0; left: 0; right: 0;'
    +'background-color: '+handcol+';');
minHand.appendChild(mh);

/* Second CSS */

var sCss = 'display: block;'
    +'position: absolute;'
    +'height: '+xy(90)+'px;'
    +'width: '+xy(2)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0;bottom: 0; left: 0; right: 0;'
    +'transform-origin: center center;'
    +'z-index: 4;';
var sClone = sCss;

var secHand = d.createElement('div');
secHand.setAttribute('style', sClone);
dial.appendChild(secHand);

var sh = d.createElement('div');
sh.setAttribute('style', 'display: block;'
    +'position: absolute;'
    +'height: '+xy(39)+'px;'
    +'width: '+xy(2)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'margin: auto; top: 0; left: 0; right: 0;'
    +'background-color: '+handcol+';');
secHand.appendChild(sh);

var sectail = d.createElement('div');
sectail.setAttribute('style', 'display: block;'
    +'position: absolute;'
    +'height: '+xy(12)+'px;'
    +'width: '+xy(2)+'px;'
    +'margin: auto; left: 0; right: 0;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'top: '+xy(52)+'px;'
    +'background-color: '+handcol+';');
secHand.appendChild(sectail);

/* Centre nut & optional glass front CSS */

var nut = d.createElement('div');
nut.setAttribute('style', 'display: inline-block;'
    +'position: absolute;'
    +'height: '+xy(10)+'px;'
    +'width: '+xy(10)+'px;'
    +'font-size: 0px;line-height: 0px;padding: 0;'
    +'border: '+xy(3)+'px solid '+handcol+';'
    +'border-radius: 50%;'
    +'margin: auto;top: 0;bottom: 0;left: 0;right: 0;'
    +'background-color: transparent;'
    +'z-index: 5;');
dial.appendChild(nut);

var glass = d.createElement('div');
glass.setAttribute('style', 'display: block;'
    +'height:'+clockSize+'px;'
    +'width: '+clockSize+'px;'
    +'border-radius:'+xy(5)+'px;'
    +'margin: auto;top: 0;bottom: 0;left: 0;right: 0;'
    +'z-index: 6;box-shadow:'
    +'0 '+xy(1)+'px '+xy(1)+'px rgba(0,0,0,0.5),'
    +'inset 0 '+xy(1)+'px rgba(255,255,255,0.3),'
    +'inset 0 '+xy(10)+'px rgba(255,255,255,0.2),'
    +'inset 0 '+xy(10)+'px '+xy(20)+'px rgba(255,255,255,0.25),'
    +'inset 0 -'+xy(15)+'px '+xy(30)+'px rgba(0,0,0,0.3);');
dial.appendChild(glass);

var eiatf = 'translateZ(0); animation-timing-function: ease-in';
var eoatf = 'translateZ(0); animation-timing-function: ease-out';

function secKeyFrames() {
    var secSheet = (d.getElementById('tmpSecSheet'+idx));
    if (secSheet) {
        secSheet.outerHTML = '';
    }
    
    sClone = sCss;
 
    var p1 = sDeg;
    var p2 = sDeg+6;
    var p3 = sDeg+4;
    var p4 = sDeg+6;
    var p5 = sDeg+5; 
    var p6 = sDeg+6; 

    var secframes = '@keyframes reGen'+sIncr+' { '
    +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
    +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
    +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
    +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
    +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
    +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

    var ss = document.createElement( 'style' );
    ss.setAttribute('id', 'tmpSecSheet'+idx);
    ss.innerHTML = secframes;
    document.getElementsByTagName('head')[0].appendChild(ss);

    var secAni = 'animation: reGen'+sIncr+' '+sSpan+' 1 forwards;';
    sClone += secAni;
    secHand.setAttribute('style', sClone);
    dial.appendChild(secHand);
}

function minKeyFrames() {
    var minSheet = (d.getElementById('tmpMinSheet'+idx));
    if (minSheet) {
        minSheet.outerHTML = '';
    }

    mClone = mCss;
 
    var p1 = mDeg;
    var p2 = mDeg+6;
    var p3 = mDeg+4;
    var p4 = mDeg+6;
    var p5 = mDeg+5; 
    var p6 = mDeg+6;

    var minframes = '@keyframes minGen'+mIncr+' { '
    +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
    +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
    +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
    +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
    +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
    +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

    var ms = document.createElement( 'style' );
    ms.setAttribute('id', 'tmpMinSheet'+idx);
    ms.innerHTML = minframes;
    d.getElementsByTagName('head')[0].appendChild(ms);

    var minAni = 'animation: minGen'+mIncr+' '+mSpan+' 1 forwards;';
    mClone += minAni;
    minHand.setAttribute('style', mClone);
    dial.appendChild(minHand);
}

function houKeyFrames() {
    var houSheet = (d.getElementById('tmphouSheet'+idx));
    if (houSheet) {
        houSheet.outerHTML = '';
    }

    hClone = hCss;
 
    var p1 = hDeg;
    var p2 = hDeg+1;
    var p3 = hDeg+0.4;
    var p4 = hDeg+1;
    var p5 = hDeg+0.5; 
    var p6 = hDeg+1; 

    var houframes = '@keyframes houGen'+hIncr+' { '
    +'0% { transform: rotate('+p1+'deg) '+eiatf+';}'
    +'30% { transform: rotate('+p2+'deg) '+eoatf+';}'
    +'45% { transform: rotate('+p3+'deg) '+eiatf+';}'
    +'60% { transform: rotate('+p4+'deg) '+eoatf+';}' 
    +'70% { transform: rotate('+p5+'deg) '+eiatf+';}'
    +'80%,100% { transform: rotate('+p6+'deg) '+eoatf+';}}';

    var hs = document.createElement( 'style' );
    hs.setAttribute('id', 'tmphouSheet'+idx);
    hs.innerHTML = houframes;
    d.getElementsByTagName('head')[0].appendChild(hs);

    var houAni = 'animation: houGen'+hIncr+' '+hSpan+' 1 forwards;';
    hClone += houAni;
    houHand.setAttribute('style', hClone);
    dial.appendChild(houHand);
}

function animate() {
    var x = new Date();
    var seconds = x.getSeconds();
    var minutes = x.getMinutes();
    var hours = (x.getHours() * 30) + (x.getMinutes() / 2);
    
    if (seconds !== sPre) {
        sIncr++;
        sDeg = (seconds-1) * 6;
        secHand.removeAttribute('style');
        secKeyFrames();
        if (sIncr > 59) {
            sIncr = 0;
        }
    }

    if (minutes !== mPre) {
        mIncr++;
        mDeg = (minutes-1) * 6;
        minHand.removeAttribute('style');
        minKeyFrames();
        if (mIncr > 59) {
            mIncr = 0;
        }
    }

    if (hours !== hPre) {
        hIncr++;
        hDeg = (hours-1) * 1;
        houHand.removeAttribute('style');
        houKeyFrames();
        if (hIncr > 59) {
            hIncr = 0;
        }    
    }

    sPre = seconds;
    mPre = minutes;
    hPre = hours;
}

function cyc() {
    var pres = performance.now(); 
    if ((pres - prev) > mls) {
        animate();
        prev = performance.now();
    }
    window.requestAnimationFrame(cyc);
} 

window.addEventListener('load', cyc, false);
})();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.