I am so used to working with jQuery that I can't seem to figure out how I can do this in plain Javascript. I don't want to use jQuery because this is the only snippet that I use on the site and the library is too large for only that purpose.
This is the jQuery script (working): http://jsfiddle.net/1jt6dhzu/2/
$(document).ready(function () {
var whatAmI = ["Fruit", "Not a vegetable", "A phone", "Yummie in tummy"];
var j, i = 4,
n = whatAmI.length;
function changeSubTitle() {
setTimeout(function () {
j = Math.floor(Math.random() * n - 1);
if (j >= i) j += 1;
$(".page-header > h2").animate({
"opacity": 0
}, 700, function () {
$(this).children("span").text(whatAmI[j]);
$(this).animate({
"opacity": 1
}, 700, changeSubTitle);
});
}, 1000);
i = j;
}
changeSubTitle();
});
And I want to swap it for vanilla JS. A large part of the loop can stay, however, the timeout and callbacks have to be replaced. I figured because I don't need IE9 support I could do this with css3 transitions and add classes. This is what I have so far:
h2 {
opacity: 1;
transition: opacity 700ms;
}
h2.fade-out {
opacity: 0;
}
document.addEventListener("DOMContentLoaded", function () {
var whatAmI = ["Fruit", "Not a vegetable", "A phone", "Yummie in tummy"];
var j, i = 4,
n = whatAmI.length,
heading = document.querySelector(".page-header > h2");
function changeSubTitle() {
setTimeout(function () {
j = Math.floor(Math.random() * n - 1);
if (j >= i) j += 1;
heading.classList.add("fade-out");
setTimeout(function () {
heading.children("span")[0].innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
setTimeout(changeSubTitle, 700);
}, 700);
}, 1000);
i = j;
}
changeSubTitle();
});
Unfortunately this doesn't work. It would probably be better to swap out the timeOuts (except the most outer one) for events on transitionend. But I'm not sure how to implement this crossbrowser (IE 10 and higher, and other major browsers).
You had done almost everything right bar removing the children
method which is implemented in a way specific to jQuery. Contrary to what I had posted earlier, JS does have a children
function and it returns a HTMLCollection but it cannot filter based on element type by providing the type as param. It returns all child nodes and we must pick the correct one either by using the child element's index or by checking the type of element.
For this example (and for simplicity sake), replace
heading.children("span")[0].innerHTML = whatAmI[j];
with
heading.children[0].innerHTML = whatAmI[j]; // since span is the first and only child
or
heading.querySelector("span").innerHTML = whatAmI[j];
and it should work as expected.
document.addEventListener("DOMContentLoaded", function() {
var whatAmI = ["Fruit", "Not a vegetable", "A phone", "Yummie in tummy"];
var j, i = 2,
n = whatAmI.length,
heading = document.querySelector(".page-header > h2");
function changeSubTitle() {
setTimeout(function() {
j = Math.floor(Math.random() * (n - 1));
if (j >= i) j += 1;
heading.classList.add("fade-out");
setTimeout(function() {
heading.querySelector("span").innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
setTimeout(changeSubTitle, 700);
}, 700);
i = j;
}, 1000);
}
changeSubTitle();
});
h2 {
opacity: 1;
transition: opacity 700ms;
}
h2.fade-out {
opacity: 0;
}
<header class="page-header">
<h1>Bananas</h1>
<h2><span>A phone</span></h2>
</header>
Using transitionend
:
transitionend
is a single event listener which is attached and fired whenever the transition on an element's properties end. It will be fired both after the addition and removal of fade-out clas and so, we have to manually check what is the state of the element when the event is fired and then act based on it.
Here I have used the getComputedStyle
property to check the opacity
state of the element and if it is in faded-out state then change text and remove fade-out class (or) else, call the changeSubTitle
function again.
if (window.getComputedStyle(heading).opacity == 0) {
heading.querySelector("span").innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
} else
changeSubTitle();
document.addEventListener("DOMContentLoaded", function() {
var whatAmI = ["Fruit", "Not a vegetable", "A phone", "Yummie in tummy"];
var j, i = 2,
n = whatAmI.length,
heading = document.querySelector(".page-header > h2");
function changeSubTitle() {
setTimeout(function() {
j = Math.floor(Math.random() * (n - 1));
if (j >= i) j += 1;
heading.classList.add("fade-out");
i = j;
}, 1000);
}
heading.addEventListener('transitionend', function() {
if (window.getComputedStyle(heading).opacity == 0) {
heading.querySelector("span").innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
} else
changeSubTitle();
});
changeSubTitle();
});
h2 {
opacity: 1;
transition: opacity 700ms;
}
h2.fade-out {
opacity: 0;
}
<header class="page-header">
<h1>Bananas</h1>
<h2><span>A phone</span></h2>
</header>
Alternately (like you commented), you could check the class present on the element also and then decide accordingly. This seems a simpler method compared to my earlier one.
if (heading.classList.contains("fade-out")) {
heading.querySelector("span").innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
} else
changeSubTitle();
document.addEventListener("DOMContentLoaded", function() {
var whatAmI = ["Fruit", "Not a vegetable", "A phone", "Yummie in tummy"];
var j, i = 2,
n = whatAmI.length,
heading = document.querySelector(".page-header > h2");
function changeSubTitle() {
setTimeout(function() {
j = Math.floor(Math.random() * (n - 1));
if (j >= i) j += 1;
heading.classList.add("fade-out");
i = j;
}, 1000);
}
heading.addEventListener('transitionend', function() {
if (heading.classList.contains("fade-out")) {
heading.querySelector("span").innerHTML = whatAmI[j];
heading.classList.remove("fade-out");
} else
changeSubTitle();
});
changeSubTitle();
});
h2 {
opacity: 1;
transition: opacity 700ms;
}
h2.fade-out {
opacity: 0;
}
<header class="page-header">
<h1>Bananas</h1>
<h2><span>A phone</span></h2>
</header>
Both the above snippets have been tested in latest versions of Firefox, Chrome, Opera, IE11 and IE10 (using Emulation). It should work in latest version of Safari on Mac also. For Windows, I think Safari versions stopped with 5.1.x and still fire only webkitTransitionEnd
event. To cater for these browsers, the method mentioned in this SO thread can be used.