3
\$\begingroup\$

I spent the last two days getting a ribbon animation to work with divs so that I can later add li link elements to them to act as a menu. I used the progess bar animation code as inspiration from w3schools. The animation works great, only issue is that the code is very un-DRY. I tried to simplify the code by not repeating myself in the CSS and especially not in the javascript but I kept getting closure issues even after using self invoked functions. I would be greatful if anyone can give me some advice or even a solution that will greatly shorten my code to have the same animation effect. I know that I will have several ribbons on the page, each with different number of ribbon layers, so the code as it is can get very complicated very fast. N.B the code below is the working version.

function move1() {
  var width = 0;
  var elem = document.getElementById("ribbon-part-1");
  var id = setInterval(frame, 1);

  function frame() {
    if (width >= 100) {
      clearInterval(id);
    } else {
      width++;
      elem.style.width = width + '%';
    }
  }
}


function move2() {
  var width = 0;
  var elem = document.getElementById("ribbon-part-2");
  var id = setInterval(frame, 1)

  function frame() {
    if (width >= 100) {
      clearInterval(id);
    } else {
      width += 2;
      elem.style.width = width + '%';
    }
  }
}

function move3() {
  var width = 0;
  var elem = document.getElementById("ribbon-part-3");
  var id = setInterval(frame, 1)

  function frame() {
    if (width >= 100) {
      clearInterval(id);
    } else {
      width += 2;
      elem.style.width = width + '%';
    }
  }
}

function move4() {
  var width = 0;
  var elem = document.getElementById("ribbon-part-4");
  var id = setInterval(frame, 1)

  function frame() {
    if (width >= 100) {
      clearInterval(id);
    } else {
      width += 2;
      elem.style.width = width + '%';
    }
  }
}

function move5() {
  var width = 0;
  var elem = document.getElementById("end-ribbon");
  var id = setInterval(frame, 1)

  function frame() {
    if (width >= 100) {
      clearInterval(id);
    } else {
      width += 2;
      elem.style.width = width + '%';
    }
  }
}

function move() {
  setTimeout(move1, 300);
  setTimeout(move2, 600);
  setTimeout(move3, 900);
  setTimeout(move4, 1200);
  setTimeout(move5, 1500);
}
* {
  box-sizing: border-box;
}

.front-ribbon {
  position: relative;
  /*background-color: grey;*/
  height: 23px;
  width: 160px;
  z-index: 1;
  transform: skewY(-11deg);
}

.back-ribbon {
  position: relative;
  /*background-color: grey;*/
  height: 23px;
  width: 160px;
  transform: skewY(4.7deg);
  z-index: -1;
}

#ribbon-part-1 {
  background-color: rgb(70, 125, 76);
  height: 23px;
  width: 0%;
}

#ribbon-part-2 {
  background-color: rgb(89, 166, 101);
  height: 23px;
  width: 0%;
}


/*Including the code below will allow for a reverse progressive bar*/
.front-ribbon #ribbon-part-2 {
  display: block;
  float: right;
}

#ribbon-part-3 {
  background-color: rgb(70, 125, 76);
  height: 23px;
  width: 0%;
}

#ribbon-part-4 {
  background-color: rgb(89, 166, 101);
  height: 23px;
  width: 0%;
}


/*Including the code below will allow for a reverse progressive bar*/
.front-ribbon #ribbon-part-4 {
  display: block;
  float: right;
}

#end-ribbon {
  fill: rgb(70, 125, 76);
  width: 0%;
}
<h1>JavaScript ribbon animation</h1>

<div class="back-ribbon">
  <div id="ribbon-part-1"></div>
</div>
<div class="front-ribbon">
  <div id="ribbon-part-2"></div>
</div>
<div class="back-ribbon">
  <div id="ribbon-part-3"></div>
</div>
<div class="front-ribbon">
  <div id="ribbon-part-4"></div>
</div>

<svg id="end-ribbon">
  <path d="M-6 17 L35 24 L24 10 Z" />
</svg>

<br>
<br>
<br>
<button onclick="move()">Click Me</button>

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

A DRYer version of your code could be:

function prepare(elem, rate, width) {
    var width = 0;
    return function frame() {
        if (width >= 100) return;
        width += rate;
        elem.style.width = width + '%';
        setTimeout(frame, 5);
    }
}

function move() {
    ['ribbon-part-1', 'ribbon-part-2', 'ribbon-part-3', 'ribbon-part-4', 'end-ribbon'].forEach(function(id, i) {
            var el = document.getElementById(id);
            setTimeout(prepare(el, i == 0 ? 1 : 2), i * 300);
    });
}

jsFiddle: https://jsfiddle.net/wLwo0vs6/

I use the ids of the elements to determine how many parts/things that has to move and like that I don't have to hard code stuff. You could also get them from the DOM knowing the starting button that was pressed. This way you could use the exact same code for all the places you have.

But I don't like the animation not being synced properly, the correct for me would be that when one section reached 100% the next section would start the animation. So I suggest a modification that could be done like this:

function prepare(elems, width) {
    var width = 0;
    var elem = elems.shift();
    return function frame() {
        if (width >= 100) {
            elem = elems.shift();
            if (!elem) return;
            width = 0;
        } else {
            width++;
            elem.style.width = width + '%';
        };
        setTimeout(frame, 5);
    }
}

function move() {
    var elements = ['ribbon-part-1', 'ribbon-part-2', 'ribbon-part-3', 'ribbon-part-4', 'end-ribbon'].map(function(id) {
        return document.getElementById(id);
    });
    setTimeout(prepare(elements), 300);
}

jsFiddle: https://jsfiddle.net/db8hbpry/

The missing part is the logic for accelerating. If you know what you want there I can adjust.

More than this, small things could be more pressed, but I think it would reduce readability.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Great post, your second solution was what I really want, I just didn't know how I could code a sequential animation. I'm not too worried about the acceleration logic, I could just tweak between the rate of incrementation and setTimeout values. Thank you! \$\endgroup\$ Commented Jul 27, 2016 at 23:23

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.