1

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).

2
  • transitionend is supported by IE10 and above, so if you're targeting eariler versions you're out of luck. developer.mozilla.org/en-US/docs/Web/Events/transitionend Commented Sep 6, 2015 at 12:48
  • @Jan I suppose I can leave out support for IE9 for this one. It isn't critical. Commented Sep 6, 2015 at 12:54

1 Answer 1

2

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.

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

18 Comments

I deleted it because it contained its own logic error ;) . I got what it was that Harry was trying to do, and to fix it, you just need to add a parentheses around (n-1). It makes the method less random and intuitive than having a loop while (j===i) /* calculate random */ though.
Also, some thing odd is happening now. The point of defining i early on is to tell the loop that this is the item that we start with and that we don't want to show that again immediately after the first one. However, this doesn't work as expected. Even if you set i = 2 (which it should be), this would still occur. Also, if you would look at your running code, you'll find that sometimes the text returned is undefined. So I'm guessing something goes wrong with assigning j. The strange thing is that this doesn't happen in jQuery (see my OP).
Now the NON-working randomizer didn't work, because what I'm guessing is the above behavior was the intended one, but instead it returned a value between 0 and 4 and then subtracted 1. So the returned range was between -1 and 3. And whatAmI[-1] didn't return anything because it wasn't set. And setting it would have created a property rather than an index but anyways, that's why it didn't work
I'll test in a minute Harry (I'm sorry for the late reply, I just went for a run). One thing I note, wouldn't it be more straightforward to test if the class is already present instead of getComputedStyle? if(heading.classList.contains("fade-out"))?
I have tested in Edge, IE 11 and 10, FF and Chrome. Approved! Thanks again for the help!
|

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.