Coupling JavaScript with CSS Animations and Transitions

One of the grievances with web developers and designers has been the layers of intricacy associated with animating in CSS. Some go on to believe that the CSS animations are not as effective as they would like them to be. JavaScript meanwhile happens to be the go to platform for most folks aiming for creating powerful animations without a sweat-soaked exercise. We however, can branch out a little and use JavaScript as input to creating animations and transitions through CSS. This combination gives us the wherewithal to shape the animations that are functionally boosted by hardware and end up being more synergistic than the typical JavaScript-created animations.To begin with, it is worth knowing that animations and transitions are not the same thing. Transitions in CSS are time-centric and largely depend on some variable properties in terms of the specific time period over which they change. They are only associated with a single element. Animations on the other hand have little to do with a cyclic period of time. As soon as we apply them, they start running.

Engineering the CSS Transitions

If you have been bothered by doubts over how you can bring about the pause and movement of a specific element's transition, don't be anymore. JavaScript has answers seamlessly executable for you. A class name has to be toggled on an element when its transition has to be initialized. We need to make use of getComputedStyle and getPropertyValue right at the instant we want a pause. Then the CSS values of the properties will be assigned as per the values we have just fetched. Let us inject some more clarity with this example :
HTML
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://code.jquery.com/jquery-2.0.0.js"></script> </head> <body> <h3>Pure Javascript</h3> <div class='box'></div> <button class='toggleButton' value='play'>Play</button> <h3>jQuery</h3> <div class='box'></div> <button class='toggleButton' value='play'>Play</button> </body> </html>
CSS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
.box { margin: 30px; height: 50px; width: 50px; background-color: blue; } .box.horizTranslate { -webkit-transition: 3s; -moz-transition: 3s; -ms-transition: 3s; -o-transition: 3s; transition: 3s; margin-left: 50% !important; }
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
var boxOne = document.getElementsByClassName('box')[0], $boxTwo = $('.box:eq(1)'); document.getElementsByClassName('toggleButton')[0].onclick = function() { if(this.innerHTML === 'Play') { this.innerHTML = 'Pause'; boxOne.classList.add('horizTranslate'); } else { this.innerHTML = 'Play'; var computedStyle = window.getComputedStyle(boxOne), marginLeft = computedStyle.getPropertyValue('margin-left'); boxOne.style.marginLeft = marginLeft; boxOne.classList.remove('horizTranslate'); } }
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
$('.toggleButton:eq(1)').on('click', function() { if($(this).html() === 'Play') { $(this).html('Pause'); $boxTwo.addClass('horizTranslate'); } else { $(this).html('Play'); var computedStyle = $boxTwo.css('margin-left'); $boxTwo.removeClass('horizTranslate'); $boxTwo.css('margin-left', computedStyle); } });

Pure Javascript

jQuery

Now, we will reproduce the above technique even when there are more complex transitions to bring about.

Making use of the Callback Functions

The DOM events of JS can prove to be extremely handy when we are playing around with CSS transitions and animations. These may include events of the likes of animationEnd, animationIteration and so on. For transitions, we have transitionEnd, etc. the names make it clear what action they trigger. Let us create an animation and transition effect by creating a heart shape and then interrupt its animation when it is hovered over:
HTML
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="http://code.jquery.com/jquery-2.0.0.js"></script> </head> <body> <h3>Pure CSS</h3> <div class='heart animated css'></div> <h3>With Javascript</h3> <div class='heart animated'></div> </body> </html>
CSS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
.heart { position: relative; width: 90px; height: 85px; margin: 27px; -webkit-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); -moz-transform: scale(1); transform: scale(1); -webkit-transform-origin: center center; -moz-transform-origin: center center; -ms-transform-origin: center center; -o-transform-origin: center center; transition: all 1s; } .heart.css { -webkit-animation-delay:1s; -moz-animation-delay:1s; -ms-animation-delay:1s; -o-animation-delay:1s; animation-dely:1s; } .heart.animated { -webkit-animation: 1500ms pulsate infinite alternate ease-in-out; -moz-animation: 1500ms pulsate infinite alternate ease-in-out; -ms-animation: 1500ms pulsate infinite alternate ease-in-out; -o-animation: 1500ms pulsate infinite alternate ease-in-out; animation: 1500ms pulsate infinite alternate ease-in-out; } .heart:before, .heart:after { position: absolute; content: ""; left: 48px; top: 0; width: 48px; height: 77px; background: red; -moz-border-radius: 48px 48px 0 0; border-radius: 48px 48px 0 0; -webkit-transform: rotate(-43px); -moz-transform: rotate(-43px); -ms-transform: rotate(-43px); -o-transform: rotate(-43px); transform: rotate(-43px); -webkit-transform-origin: 0 100%; -moz-transform-origin: 0 100%; -ms-transform-origin: 0 100%; -o-transform-origin: 0 100%; transform-origin: 0 100%; } .heart:after { left: 0; -webkit-transform: rotate(43deg); -moz-transform: rotate(43deg); -ms-transform: rotate(43deg); -o-transform: rotate(43deg); transform: rotate(43deg); -webkit-transform-origin: 100% 100%; -moz-transform-origin: 100% 100%; -ms-transform-origin: 100% 100%; -o-transform-origin: 100% 100%; transform-origin :100% 100%; } .heart.css:hover { -webkit-transform: scale(2); -moz-transform: scale(2); -ms-transform: scale(2); -o-transform: scale(2); transform: scale(2); -webkit-animation:''; -moz-animation:none; -ms-animation:''; -o-animation:''; animation:''; } @keyframes pulsate { 0% { transform: scale(1); } 48% { transform: scale(1.3); } 95% { transform: scale(1); } } @-webkit-keyframes pulsate { 0% { -webkit-transform: scale(1); } 48% { -webkit-transform: scale(1.3); } 95% { -webkit-transform: scale(1); } } @-moz-keyframes pulsate { 0% { -moz-transform: scale(1); } 48% { -moz-transform: scale(1.3); } 95% { -moz-transform: scale(1); } } @-ms-keyframes pulsate { 0% { -ms-transform: scale(1); } 48% { -ms-transform: scale(1.3); } 95% { -ms-transform: scale(1); } } @-o-keyframes pulsate { 0% { -o-transform: scale(1); } 48% { -o-transform: scale(1.3); } 95% { -o-transform: scale(1); } }
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
var heart = document.getElementsByClassName('heart')[1], pfx = ["webkit", "moz", "MS", "o", ""], hovered = false; function AnimationListener() { if(hovered) { heart.classList.remove('animated'); heart.style.webkitTransform = 'scale(2)'; heart.style.MozTransform = 'scale(2)'; heart.style.msTransform = 'scale(2)'; heart.style.OTransform = 'scale(2)'; heart.style.transform = 'scale(2)'; } } function TransitionListener() { if(!hovered) { heart.classList.add('animated'); } } function PrefixedEvent(element, type, callback) { for (var p = 0; p < pfx.length; p++) { if (!pfx[p]) type = type.toLowerCase(); element.addEventListener(pfx[p]+type, callback, false); } } PrefixedEvent(heart, "AnimationIteration", AnimationListener); heart.onmouseover = function() { hovered = true; } heart.onmouseout = function() { setTimeout(function() { hovered = false; }, 500); PrefixedEvent(heart, "TransitionEnd", TransitionListener); heart.style.webkitTransform = 'scale(1)'; heart.style.MozTransform = 'scale(1)'; heart.style.msTransform = 'scale(1)'; heart.style.OTransform = 'scale(1)'; heart.style.transform = 'scale(1)'; }

Pure CSS

With Javascript

Here, JavaScript makes sure that the final animation is much more stable than how it is in pure CSS.

Playing Around with CSS Animations

Now, we saw some events for animations like animationStart, animationEnd and so on. But you would need to branch out of your comfort zone to alter the animation right in the middle of the animation taking place. For the same, the animation-play-state property proves to be handy and helps you to pause the animation as and when you want. Make the following change in your JS:
JS
  • 1
  • 2
element.style.webkitAnimationPlayState = "paused"; element.style.webkitAnimationPlayState = "running";
It would serve us well if we can fetch the current keyvalue percentage so that we have a better idea of the animation's progress. The setInterval proves to be highly useful for the same. This is how we use it in the JavaScript:
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
var showPercent = window.setInterval(function() { if (currentPercent < 100) { currentPercent += 1; } else { currentPercent = 0; } result.innerHTML = currentPercent; }, 40);

What are the Current Values of CSS Animation

Finding out the same is highly consequential on the overall exercise. Here are some code snippets that can be made use of when obtaining and altering CSS animation midway is on the agenda:
HTML
  • 1
  • 2
  • 3
<div id="circle"></div> <div id='button'>ChangeAnimation</div> <div id='result'></div>
CSS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
@-webkit-keyframes rotate { 0% { -webkit-transform:translate(98px, 98px) rotate(0deg) translate(-98px, -98px) rotate(0deg); background-color:red; } 13% { -webkit-transform:translate(98px, 98px) rotate(39deg) translate(-98px, -98px) rotate(-39deg); } 25% { -webkit-transform:translate(98px, 98px) rotate(85deg) translate(-98px, -98px) rotate(-85deg); } 38% { -webkit-transform:translate(98px, 98px) rotate(129deg) translate(-98px, -98px) rotate(0deg); } 50% { -webkit-transform:translate(98px, 98px) rotate(180deg) translate(-98px, -98px) rotate(-180deg); } 63% { -webkit-transform:translate(98px, 98px) rotate(225deg) translate(-98px, -98px) rotate(225deg); } 75% { -webkit-transform:translate(98px, 98px) rotate(270deg) translate(-98px, -98px) rotate(-270deg); } 88% { -webkit-transform:translate(98px, 98px) rotate(315deg) translate(-98px, -98px) rotate(315deg); } 100% { -webkit-transform:translate(98px, 98px) rotate(360deg) translate(-98px, -98px) rotate(-360deg); } } #circle { height: 50px; width: 50px; border-radius:25px; background-color: teal; -webkit-animation-duration: 4s; -webkit-animation-timing-function: linear; -webkit-animation-name:"rotate"; -webkit-animation-iteration-count: infinite; position:absolute; left:30%; top:20%; } #button { width:130px; background:teal; }
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
// NOTE: The change to red signifies the start of // the animation // Allows elements to be accessed in a clean way var circle = document.getElementById('circle'), button = document.getElementById('button'); // Gets element to show current percentage var result = document.getElementById('result'), // Current position of circle around its path // in percent in reference to the original totalCurrentPercent = 0, // Percent of circle around its path in // percent in reference to the latest origin currentPercent = 0; // Updates the percent change from the latest origin var showPercent = window.setInterval(function() { if(currentPercent < 100) { currentPercent += 1; } else { currentPercent = 0; } result.innerHTML = currentPercent; }, 39); // Runs at a rate based on the animation's // duration (milliseconds / 100) // Checks to see if the specified rule is within // any of the stylesheets found in the document; // returns the animation object if so function findKeyframesRule(rule) { var ss = document.styleSheets; for (var i = 0; i < ss.length; ++i) { for (var j = 0; j < ss[i].cssRules.length; ++j) { if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE && ss[i].cssRules[j].name == rule) { return ss[i].cssRules[j]; } } } return null; } // Replaces the animation based on the percent // when activated and other hard coded // specifications function change(anim) { // Obtains the animation object of the specified // animation var keyframes = findKeyframesRule(anim), length = keyframes.cssRules.length; // Makes an array of the current percent values // in the animation var keyframeString = []; for(var i = 0; i < length; i ++) { keyframeString.push(keyframes[i].keyText); } // Removes all the % values from the array so // the getClosest function can perform calculations var keys = keyframeString.map(function(str) { return str.replace('%', ''); }); // Updates the current position of the circle to // be used in the calculations totalCurrentPercent += currentPercent; if(totalCurrentPercent > 100) { totalCurrentPercent -= 100; } // Self explanatory variables if you read the // description of getClosest var closest = getClosest(keys); var position = keys.indexOf(closest), firstPercent = keys[position]; // Removes the current rules of the specified // animation for(var i = 0, j = keyframeString.length; i < j; i ++) { keyframes.deleteRule(keyframeString[i]); } // Turns the percent when activated into the // corresponding degree of a circle var multiplier = firstPercent * 3.6; // Essentially this creates the rules to set a new // origin for the path based on the approximated // percent of the animation when activated and // increases the diameter of the new circular path keyframes.insertRule("0% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 0) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 0) + "deg); background-color:red; }"); keyframes.insertRule("13% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 45) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 45) + "deg); }"); keyframes.insertRule("25% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 90) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 90) + "deg); }"); keyframes.insertRule("38% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 135) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 135) + "deg); }"); keyframes.insertRule("50% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 180) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 180) + "deg); }"); keyframes.insertRule("63% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 225) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 225) + "deg); }"); keyframes.insertRule("75% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 270) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 270) + "deg); }"); keyframes.insertRule("88% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 315) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 315) + "deg); }"); keyframes.insertRule("100% { -webkit-transform: translate(98px,98px) rotate(" + (multiplier + 360) + "deg) translate(-98px,-98px) rotate(" + (multiplier + 360) + "deg); }"); // Shows the circle again circle.style.display = "inherit"; // Sets the animation to the newly specified rules circle.style.webkitAnimationName = anim; // Resets the approximate animation percent counter window.clearInterval(showPercent); currentPercent = 0; showPercent = self.setInterval(function() { if(currentPercent < 100) { currentPercent += 1; } else { currentPercent = 0; } result.innerHTML = currentPercent; }, 39); } // Attatches the change function to the button's // onclick function button.onclick = function() { // Removes the animation so a new one can be set circle.style.webkitAnimationName = "none"; // Temporarily hides the circle circle.style.display = "none"; // Initializes change function setTimeout(function () { change("rotate"); }, 0); } // Gets the animation's closest % value based on // the approximated % found below function getClosest(keyframe) { var curr = keyframe[0]; var diff = Math.abs (totalCurrentPercent - curr); for (var val = 0, j = keyframe.length; val < j; val++) { var newdiff = Math.abs(totalCurrentPercent - keyframe[val]); if (newdiff < diff) { diff = newdiff; curr = keyframe[val]; } } return curr; }
ChangeAnimation

Practically Altering the Animation

To begin with, we need to know how many circles have been completed by the animation from its starting point and also how many circles it has completed from the latest recorded starting point. For both, we need different variables. The second variable can be changed using the setInterval function. As soon as the button is clicked, the following JavaScript code will come into play to update it:
JS
  • 1
  • 2
  • 3
  • 4
totalCurrentPercent += currentPercent; if (totalCurrentPercent > 100) { totalCurrentPercent -= 100; }
Now, we will determine the closest keyframe to the percentage we have at present:
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
function getClosest(keyframe) { // curr stands for current keyframe var curr = keyframe[0]; var diff = Math.abs (totalCurrentPercent - curr); for (var val = 0, j = keyframe.length; val < j; val++) { var newdiff = Math.abs(totalCurrentPercent - keyframe[val]); // If the difference between the current percent and the iterated // keyframe is smaller, take the new difference and keyframe if (newdiff < diff) { diff = newdiff; curr = keyframe[val]; } } return curr; } The .indexOf method will be used the first keyframe value of fresh animation: for (var i = 0, j = keyframeString.length; i < j; i ++) { keyframes.deleteRule(keyframeString[i]); }
And now comes changing the percentage into the circle's degree. Following that, the new rles have to be charted out, based on which variables we have fetched so far.
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
keyframes.insertRule("0% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 0) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 0) + "deg); background-color:red; }"); keyframes.insertRule("13% { -webkit-transform: translate(100px,100px) rotate(" + (multiplier + 45) + "deg) translate(-100px,-100px) rotate(" + (multiplier + 45) + "deg); }");
The current percent setInterval will then be reset. Cross browser compatibility has also be taken into account meanwhile:
JS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
var browserPrefix; navigator.sayswho= (function(){ var N = navigator.appName, ua = navigator.userAgent, tem; var M = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i); if(M && (tem = ua.match(/version\/([\.\d]+)/i))!= null) M[2] = tem[1]; M = M? [M[1], M[2]]: [N, navigator.appVersion,'-?']; M = M[0]; if(M == "Chrome") { browserPrefix = "webkit"; } if(M == "Firefox") { browserPrefix = "moz"; } if(M == "Safari") { browserPrefix = "webkit"; } if(M == "MSIE") { browserPrefix = "ms"; } })();
The CSS animations thus have been simplified using JavaScript. Concludingly, the task was not as hard as you might think, but it would demand attentiveness on your part. CSS can really produce some astounding animations, but you always have the option of using JavaScript in close conjunction with it. The final results are more than gratifying.
  • Article written by Mike Swan. Mike Swan loves blogging and is one of the best HTML to wordpress certified developer by profession. Currently, he is serving at Markupcloud Ltd, a reputed name in the website markup conversion services domain.
New question is currently disabled!