skip to Main Content

I am facing a challenge where before scroll, you will notice when scrolling down or up, there is a slight pixel shift in the same direction before a smooth scroll animation occurs and snaps to the next or previous section depending on the scroll direction.

Code can be found here: https://stackblitz.com/edit/html-sample-rpcn3b?file=index.html

Preview can be found here: https://html-sample-rpcn3b.stackblitz.io

How can I remove this slight shift before the animated scroll happens, so that I can get a smooth scroll snapping from one section to the next. In the end (for the targeted sections) I want to achieve a smooth scroll closely similar to this example here with swiperjs: https://swiper-master.webflow.io/full-page-vertical

2

Answers


  1. Your code seems to work as expected if you tweak a bit wtd and st in getClosestElement (see my HERE comments in the snippet below).

    (function ($) {
      $.fn.sectionsnap = function (options) {
        var settings = {
          delay: 50, // time dilay (ms)
          selector: '[fs-animated-section]', // selector
          reference: 1, // % of window height from which we start
          animationTime: 300, // animation time (snap scrolling)
          offsetTop: 0, // offset top (no snap before scroll reaches this position)
          offsetBottom: 0, // offset bottom (no snap after bottom - offsetBottom)
        }
    
        var $wrapper = this,
            direction = 'down',
            currentScrollTop = $(window).scrollTop(),
            scrollTimer,
            animating = false;
    
        // check the direction
        var updateDirection = function () {
          if ($(window).scrollTop() >= currentScrollTop) direction = 'down';
          else direction = 'up';
          currentScrollTop = $(window).scrollTop();
        };
    
        // return the closest element (depending on direction)
        var getClosestElement = function () {
          var $list = $wrapper.find(settings.selector),
              wt = $(window).scrollTop(),
              wh = $(window).height(),
              refY = wh * settings.reference,
              wtd = wt + refY - 3, // <--- HERE
              $target;
    
          if (direction == 'down') {
            $list.each(function () {
              var st = $(this).position().top - 1; // <--- HERE
              if (st > wt && st <= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          } else {
            wtd = wt - refY + 3; // <--- HERE
            $list.each(function () {
              var st = $(this).position().top - 1; // <--- HERE
              if (st < wt && st >= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          }
          return $target;
        };
    
        // snap
        var snap = function () {
          var $target = getClosestElement();
          if ($target) {
            animating = true;
            $('html, body').animate(
              {
                scrollTop: $target.offset().top,
              },
              settings.animationTime,
              function () {
                window.clearTimeout(scrollTimer);
                animating = false;
              }
            );
          }
        };
        // on window scroll
        var windowScroll = function () {
          if (animating) return;
          var st = $(window).scrollTop();
          if (st < settings.offsetTop) return;
          if (st > $('html').height() - $(window).height() - settings.offsetBottom)
            return;
          updateDirection();
          window.clearTimeout(scrollTimer);
          scrollTimer = window.setTimeout(snap, settings.delay);
        };
        $(window).scroll(windowScroll);
        return this;
      };
    })(jQuery);
    
    $(document).ready(function () {
      $('[fs-animated-container="container"]').sectionsnap();
    });
    section {
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 10px solid blue;
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous" />
    </head>
    
    <body>
      <div id="app">
        <div class="main-div">
          <div fs-animated-container="container">
            <section fs-animated-section="first" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section ONE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="second" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section TWO ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="third" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section THREE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
          </div>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FOUR NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FIVE NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section SIX NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
        </div>
      </div>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
        integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </body>
    
    </html>

    Moreover, some of your HTML tags are not closed. See <div class="container-large">.


    EDIT 1

    After reading your comment, it seems that I misunderstood the meaning of your question…

    In fact, except if your challenge is to reinvent the wheel, you may want to use a small library like jQuery Easing Plugin. You will find below the same example with a custom easing function and no delay:

    (function ($) {
      $.fn.sectionsnap = function (options) {
        var settings = {
          delay: 0, // <--- HERE
          selector: '[fs-animated-section]', // selector
          reference: 1, // % of window height from which we start
          animationTime: 300, // animation time (snap scrolling)
          offsetTop: 0, // offset top (no snap before scroll reaches this position)
          offsetBottom: 0, // offset bottom (no snap after bottom - offsetBottom)
        }
    
        var $wrapper = this,
            direction = 'down',
            currentScrollTop = $(window).scrollTop(),
            scrollTimer,
            animating = false;
    
        // check the direction
        var updateDirection = function () {
          if ($(window).scrollTop() >= currentScrollTop) direction = 'down';
          else direction = 'up';
          currentScrollTop = $(window).scrollTop();
        };
    
        // return the closest element (depending on direction)
        var getClosestElement = function () {
          var $list = $wrapper.find(settings.selector),
              wt = $(window).scrollTop(),
              wh = $(window).height(),
              refY = wh * settings.reference,
              wtd = wt + refY - 3,
              $target;
    
          if (direction == 'down') {
            $list.each(function () {
              var st = $(this).position().top - 1;
              if (st > wt && st <= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          } else {
            wtd = wt - refY + 3;
            $list.each(function () {
              var st = $(this).position().top - 1;
              if (st < wt && st >= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          }
          return $target;
        };
    
        // snap
        var snap = function () {
          var $target = getClosestElement();
          if ($target) {
            animating = true;
            $('html, body').animate(
              {
                scrollTop: $target.offset().top,
              },
              settings.animationTime,
              'easeOutElastic', // <--- HERE
              function () {
                window.clearTimeout(scrollTimer);
                animating = false;
              }
            );
          }
        };
        // on window scroll
        var windowScroll = function () {
          if (animating) return;
          var st = $(window).scrollTop();
          if (st < settings.offsetTop) return;
          if (st > $('html').height() - $(window).height() - settings.offsetBottom)
            return;
          updateDirection();
          window.clearTimeout(scrollTimer);
          scrollTimer = window.setTimeout(snap, settings.delay);
        };
        $(window).scroll(windowScroll);
        return this;
      };
    })(jQuery);
    
    $(document).ready(function () {
      $('[fs-animated-container="container"]').sectionsnap();
    });
    section {
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 10px solid blue;
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous" />
    </head>
    
    <body>
      <div id="app">
        <div class="main-div">
          <div fs-animated-container="container">
            <section fs-animated-section="first" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section ONE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="second" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section TWO ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="third" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section THREE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
          </div>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FOUR NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FIVE NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section SIX NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
        </div>
      </div>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
        integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js" integrity="sha512-0QbL0ph8Tc8g5bLhfVzSqxe9GERORsKhIn1IrpxDAgUsbBGz/V7iSav2zzW325XGd1OMLdL4UiqRJj702IeqnQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </body>
    
    </html>

    It looks much smoother now!

    For the record, you might also want to use CSS instead of JS:

    div[fs-animated-container] {
      scroll-snap-type: y mandatory;
      overflow-y: scroll;
      height: 100vh;
    }
    
    section[fs-animated-section] {
      scroll-snap-align: center;
    }
    
    section {
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center; 
      border-bottom: 10px solid blue;
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous" />
    </head>
    
    <body>
      <div id="app">
        <div class="main-div">
          <div fs-animated-container="container">
            <section fs-animated-section="first" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section ONE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="second" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section TWO ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="third" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section THREE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
          </div>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FOUR NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FIVE NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section SIX NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
        </div>
      </div>
    </body>
    
    </html>

    It does not work very well on IE, though. This browser is just too old.


    EDIT 2

    If you want something even smoother, I am not sure you need .animate(). I removed it from the code. Of course, the jQuery Easing Plugin is not necessary anymore.

    Besides, even though it is not compulsory, I recommend you to implement throttling to increase performance (the scroll event fires way too often for what we need to do). The easiest strategy here is to use _.throttle() from Lodash.

    (function ($) {
      $.fn.sectionsnap = function (options) {
        var settings = {
          selector: '[fs-animated-section]', // selector
          reference: 1, // % of window height from which we start
          offsetTop: 0, // offset top (no snap before scroll reaches this position)
          offsetBottom: 0 // offset bottom (no snap after bottom - offsetBottom)
        }
    
        var $wrapper = this,
            direction = 'down',
            currentScrollTop = $(window).scrollTop();
    
        // check the direction
        var updateDirection = function () {
          if ($(window).scrollTop() >= currentScrollTop) direction = 'down';
          else direction = 'up';
          currentScrollTop = $(window).scrollTop();
        };
    
        // return the closest element (depending on direction)
        var getClosestElement = function () {
          var $list = $wrapper.find(settings.selector),
              wt = $(window).scrollTop(),
              wh = $(window).height(),
              refY = wh * settings.reference,
              wtd = wt + refY - 3,
              $target;
    
          if (direction == 'down') {
            $list.each(function () {
              var st = $(this).position().top - 1;
              if (st > wt && st <= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          } else {
            wtd = wt - refY + 3;
            $list.each(function () {
              var st = $(this).position().top - 1;
              if (st < wt && st >= wtd) {
                $target = $(this);
                return false; // just to break the each loop
              }
            });
          }
          return $target;
        };
    
        // snap
        var snap = function () {
          var $target = getClosestElement();
          if ($target) {
            $('html, body').scrollTop($target.offset().top); // <--- HERE
          }
        };
        
        // on window scroll
        var windowScroll = function () {
          var st = $(window).scrollTop();
          if (st < settings.offsetTop) return;
          if (st > $('html').height() - $(window).height() - settings.offsetBottom)
            return;
          updateDirection();
          snap();
        };
        $(window).on('scroll', _.throttle(windowScroll, 50, { trailing: false })); // <--- HERE
        return this;
      };
    })(jQuery);
    
    $(document).ready(function () {
      $('[fs-animated-container="container"]').sectionsnap();
    });
    section {
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
      border-bottom: 10px solid blue;
    }
    <!DOCTYPE html>
    <html>
    
    <head>
      <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous" />
    </head>
    
    <body>
      <div id="app">
        <div class="main-div">
          <div fs-animated-container="container">
            <section fs-animated-section="first" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section ONE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="second" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section TWO ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
            <section fs-animated-section="third" class="whatif_section">
              <div class="container-large">
                <div class="whatif_wrapper">
                  <h1> Section THREE ANIMATED SCROLL
                  </h1>
                </div>
              </div>
            </section>
          </div>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FOUR NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section FIVE NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
          <section class="whatif_section">
            <div class="container-large">
              <div class="whatif_wrapper">
                <h1> Section SIX NORMAL SCROLL
                </h1>
              </div>
            </div>
          </section>
        </div>
      </div>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"
        integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    </body>
    
    </html>
    Login or Signup to reply.
  2. You can achieve a smooth scroll snap experience with just pure CSS by using scroll-snap-type property, without jQuery or js.

    refer:
    https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type

    note: check web browser compatibility

    for your case, if you need to enable smooth scroll snap only at the bottom part, the section tags, you may just do as below: (add this style and remove other js animation)

    <style>
      :root {
        scroll-snap-type: y mandatory;
        overscroll-behavior-y: contain;
        overflow: auto;
      }
    
      .main-div>div:first-child {
        scroll-snap-align: end;
      }
    
      .main-div>section {
        scroll-snap-align: center;
      }
    </style>
    

    *do scroll-snap-type: y mandatory for parent element and scroll-snap-align: center for child element that need scroll snap , no complicated js animation needed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search