Ed Dogan
Ed Dogan

Reputation: 275

$(window).scroll combining similar functions

I have several number counters and want them to start animating when the user scroll downs to them. Right now, I achieved this by writing a function for each one of them but I'm sure that's an inefficient way of doing this.

I have a working example here: https://codepen.io/adogandesign/pen/PWqVov

HTML:

<div id="states" class="animated">
    <div class="anim_num">
        <svg>
            <pattern>...</pattern>
            <text id="count_num1"></text>
        </svg>
    </div>
</div>
<div id="concerts" class="animated">
    <div class="anim_num">
        <svg>
            <pattern>...</pattern>
            <text id="count_num2"></text>
        </svg>
    </div>
</div>

Javascript:

$(window).scroll(startCounter1);
    function startCounter1() {
        if ($(window).scrollTop() > $("#states").offset().top - $(window).height() + 0) {
            $(window).off("scroll", startCounter1);
        $("#count_num1").each(function () {
                var $this = $(this);
                jQuery({ Counter: 0 }).animate({ Counter: $this.text() }, {
                    duration: 4000,
                    easing: 'swing',
                    step: function (now) {
                        $this.text(now.toFixed(0));
                    }
                });
            });
        }
    }
$(window).scroll(startCounter2);
    function startCounter2() {
        if ($(window).scrollTop() > $("#concerts").offset().top - $(window).height() + 0) {
            $(window).off("scroll", startCounter2);
        $("#count_num2").each(function () {
                var $this = $(this);
                jQuery({ Counter: 0 }).animate({ Counter: $this.text() }, {
                    duration: 4000,
                    easing: 'swing',
                    step: function (now) {
                        $this.text(now.toFixed(0));
                }
                });
            });
        }
    }

My question is how can I combine that javascript code into one function?

Upvotes: 0

Views: 86

Answers (3)

Kavish Mehta
Kavish Mehta

Reputation: 53

Simply give each text tag same class like "animation" and remove id (count_num1, count_num2) from there and at $("#count_num1") use $(".animation") I had check this method in your codepen link and it work perfectly.

$(window).scroll(startCounter1);
  function startCounter1() {
    if ($(window).scrollTop() > $("#states").offset().top - $(window).height() + 0) {
      $(window).off("scroll", startCounter1);
      $(".animation").each(function () {
	  var $this = $(this);
	  jQuery({ Counter: 0 }).animate({ Counter: $this.text() }, 
      {
	    duration: 4000,
		easing: 'swing',
		step: function (now) {
		$this.text(now.toFixed(0));
      }
    });
  });
  }
}
body{
  width: 100%;
  overflow-x: hidden;
  font-family: Open Sans, sans-serif;
}

.scrolldown {
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 50px;
}

.animated_percentage {
  max-width: 100%;
  margin: 0 auto;
  position: relative;
}

.anim_num {
  display: block;
  position: relative;
  max-height: 100vh;
  padding: 5% 0;
  font-size: 350px;
  font-weight: 900;
}
#states .anim_num {
  padding-left: 20%;
}
#concerts .anim_num {
  padding-right: 30%;
}
#fans .anim_num {
  padding-left: 15%;
}

svg {
  max-width: 100%;
  max-height: 100%;
}

#count_num1{
  fill: url(#img1);
}
#count_num2{
  fill: url(#img2);
}
#count_num3{
  fill: url(#img3);
}

.exp {
  position: absolute;
  top: 50%;
  font-size: 48px;
  font-weight: 700;
  color: #aabbae;
}
#states .exp {
  left: 10%;
}
#concerts .exp {
  right: 20%;
}
#fans .exp {
  left: 5%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="scrolldown">
  Scroll down to see the animation
</div>  
<div id="states" class="animated_percentage">
<div class="anim_num">
<svg viewBox="0 0 960 540">
    <pattern id="img1" patternUnits="userSpaceOnUse" width="100%" height="100%" x="-10%" y="-25%">
    <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://adogandesign.com/wp-content/uploads/2017/01/AdobeStock_69678727.jpg" width="960" height="540"></image>
    </pattern>
  <text text-anchor="middle" x="50%" y="50%" class="animation" id="count_num1">46</text>
   </svg>
</div><!--#anim_num-->
  <div class="exp">
     States travelled
  </div>  
</div>

<div id="concerts" class="animated_percentage">
<div class="anim_num">
<svg viewBox="0 0 960 540">
    <pattern id="img2" patternUnits="userSpaceOnUse" width="100%" height="100%" x="0" y="-20%">
    <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://adogandesign.com/wp-content/uploads/2017/01/AdobeStock_63664078.jpg" width="960" height="540"></image>
    </pattern>
  <text text-anchor="middle" x="50%" y="50%" class="animation" id="count_num2">97</text>
   </svg>
</div><!--#anim_num-->
  <div class="exp">
     Concerts Given
  </div>  
</div>

<div id="fans" class="animated_percentage">
<div class="anim_num">
<svg viewBox="0 0 960 540">
    <pattern id="img3" patternUnits="userSpaceOnUse" width="100%" height="100%" x="0" y="-22%">
    <image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://adogandesign.com/wp-content/uploads/2017/01/AdobeStock_93833225.jpg" width="960" height="540"></image>
    </pattern>
  <text text-anchor="middle" x="50%" y="50%" class="animation" id="count_num3">436</text>
   </svg>
</div><!--#anim_num-->
  <div class="exp">
     Fans Gone Wild
  </div>  
</div>

Upvotes: 0

charlietfl
charlietfl

Reputation: 171679

Use an each to loop over a common class instead of focusing on individual ID's and handle each instance inside that loop

Something like:

   $('.animated').each(function() { // #states & #concerts
      // current instance of the animated class
      var $this = $(this),
        // find associated text element, 
        // will be  #count_num1 or #count_num2 depending on $this instance  
        $textEl = $this.find('text'); 
      // check instance offset
      if ($(window).scrollTop() > $this.offset().top - $(window).height() + 0) {
        jQuery({
          Counter: 0
        }).animate({
          Counter: $textEl.text()
        }, {
          duration: 4000,
          easing: 'swing',
          step: function(now) {
            $textEl.text(now.toFixed(0));
          }
        });
      }

    });

Note that you probably want to check if the animation is already taking place before initializing it again for every pixel move of scroll

Upvotes: 0

Jacob
Jacob

Reputation: 78850

The general algorithm you can use for refactoring of this sort is:

  1. Identify the parts that are different.
  2. Replace those parts with variable names.
  3. Create a function wrapper, replacing those variables with function parameters.
  4. Replace the code with calls to that function.

So in this case, the first variance is "#states" vs "#concerts"; let's call that section. The second is #count_num1 vs #count_num2, which we can call counter. Now we can do this:

function createScrollCounter(section, counter) {
  $(window).scroll(scrollCounter);

  function scrollCounter() {
    if ($(window).scrollTop() > $(section).offset().top - $(window).height() + 0) {
      $(window).off("scroll", scrollCounter);
      $(counter).each(function () {
        var $this = $(this);
        jQuery({ Counter: 0 }).animate({ Counter: $this.text() }, {
          duration: 4000,
          easing: 'swing',
          step: function (now) {
            $this.text(now.toFixed(0));
          }
        });
      });
    }
  }
}

createScrollCounter('#states', '#count_num1');
createScrollCounter('#concerts', '#count_num2');

Upvotes: 1

Related Questions