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
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
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";
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;
}
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;
}
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]);
}
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);
}");
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"; }
})();