Scrolling to next section and back to top without IDs in Vanilla JS - NO JQUERY

I have used a modified jQuery script to be able to scroll through sections without IDs and back to top when reaching the end of the document.

Since i am new to JS and try to improve and learn i wanted to get rid of the jQuery dependency.

I have managed to get it partly working but fail and got confused finding an alternative to jQueries Offset method.

This is the modified jQuery script:

// scoll to next section and back to top

  var $scrollSection = $('section');
  var $scrollTrigger = $('.section_trigger');
  var nextSection = 1;

  $scrollTrigger.on('click', function() {
    $(this).removeClass('go-to-top');

    // If reached the last section, scroll back to the top on the next click:
    if (nextSection >= $scrollSection.length) {
      $('html, body').animate({
        scrollTop: 0
      }, 1000);
      nextSection = 0;
      return;
    }

    // if we scroll down increment section counter
    while ($('body').scrollTop() > $($scrollSection[nextSection]).offset().top) {
      nextSection++;
    }

    // If next section is the last, add class to rotate arrow:
    if (nextSection === ($scrollSection.length - 1)) {
      $(this).addClass('go-to-top');
    }

    // Move to next section and increment counter
    $('html, body').animate({
      scrollTop: $($scrollSection[nextSection]).offset().top
    }, 1000);
    nextSection++;
  });

  // turn arrow when scrolled to footer
  $(window).scroll(function() {
    if ($(window).scrollTop() + $(window).height() == $(document).height()) {
      $(".section_trigger--side").addClass('go-to-top');
    } else {
      $(".section_trigger--side").removeClass('go-to-top');
    }
  });
.box_0 {
  background-color: grey;
  height: 100vh;
  width: 100vw;
}

.box_1 {
  background-color: green;
  height: 50vh;
  width: 100vw;
}
.box_2 {
  background-color: blue;
  height: 50vh;
  width: 100vw;
}
.box_3 {
  background-color: yellow;
  height: 20vh;
  width: 100vw;
}


footer {
  background-color: pink;
  height: 40vh;
  width: 100vw;
}


/* styling for the section trigger */

.section_trigger svg {
  height: 60%;
  left: 20%;
  position: absolute;
  top: 20%;
  width: 60%;
}

/** @define c-scroll-nav
  */

.section_trigger {
  background: hsla(0, 0%, 0%, 0.45);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  height: 50px;
  width: 50px;
}

.section_trigger:hover {
  background: black;
}

.section_trigger--center {
  position: absolute;
  left: 50%;
  transform: translate(0, -50%);
  bottom: 12px;
  transform: rotate(270deg);
}

.section_trigger--side {
  bottom: 12px;
  position: fixed;
  right: 12px;
  transform: rotate(270deg);
}

.go-to-top {
  transform: rotate(90deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="section_trigger section_trigger--center">
  <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
</span>
<span class="section_trigger section_trigger--side">
  <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
</span>
<section>
  <div class="box_0"></div>
</section>
<section>
  <div class="box_2"></div>
</section>
<section>
  <div class="box_3"></div>
</section>
<section>
  <div class="box_1"></div>
</section>
<section>
  <div class="box_2"></div>
</section>
<section>
  <div class="box_3"></div>
</section>
<footer></footer>

This is my Vanilla JS approach:

// Debuging
window.onscroll = function() {
  //console.log('top: '  + (window.pageYOffset || document.documentElement.scrollTop));
  // console.log('section: '  + (window.pageYOffset || document.scrollSection[nextSection].scrollTop));
  // console.log(nextSection);
  // console.log(scrollSection[0]);
}

/*
 * Helper Function: Vanilla version of jQuery scrollTop
 */

function scrollTo(element, to, duration) {
  if (duration <= 0) return;
  let difference = to - element.scrollTop;
  let perTick = difference / duration * 50;

  setTimeout(() => {
    element.scrollTop = element.scrollTop + perTick;
    if (element.scrollTop == to) return;
    scrollTo(element, to, duration - 10);
  }, 10);
}

/*
 * Main Function: Scroll to section elements and back to top
 */

const scrollSection = document.getElementsByTagName('section');
const scrollTrigger = document.getElementsByClassName('section_trigger')[1];
let nextSection = 0;

// turn arrow back arround after clicked to return to top
scrollTrigger.addEventListener('click', () => {
  scrollTrigger.classList.remove('go-to-top');
  console.log('removed class `go-to-top`');

  // If reached the last section, scroll back to the top on the next click:
  if (nextSection >= scrollSection.length) {
    function runScroll() {
      scrollTo(document.body, 0, 1000);
      nextSection = 0;
      console.log('Scrolled back to Top');
    }
  }

  // if we scroll down increment section counter
  while (document.body.scrollTop > document.scrollSection[nextSection].getBoundingClientRect().scrollTop) { //doesnt work , cant next section scrollSection undefined
    nextSection++;
    console.log('incremented counter to' + nextSection);
  }

  // If next section is the last, add go-to-top class to rotate arrow:
  if (nextSection === (scrollSection.length - 1)) {
    scrollTrigger.classList.add('go-to-top');
    console.log('added class `go-to-top`');
  }

  // Move to next section and increment counter
  function runScroll() {
    scrollTo(document.scrollSection[nextSection].getBoundingClientRect().scrollTop, 0, 1000); //doesnt work , cant next section scrollSection undefined
    nextSection++;
    console.log('Scrolled one section down');
  }

});

/*
 * Helper Function: vanilla JS get document height and window width and height
 */

const body = document.body;
const html = document.documentElement;

// document height
const documentHeight = Math.max(body.scrollHeight, body.offsetHeight,
  html.clientHeight, html.scrollHeight, html.offsetHeight);

// window width and height
const windowWidth = window.innerWidth || document.documentElement.clientWidth || body.clientWidth;
const windowHeigth = window.innerHeight || document.documentElement.clientHeight || body.clientHeight;

// turn arrow when scrolled to footer
window.onscroll = function() {
  if (window.getBoundingClientRect() + windowHeigth == documentHeight) { // not sure if getBoundingClientRect is the right choice since its relative to the viewport
    scrollTrigger.classList.add('go-to-top');
  } else {
    scrollTrigger.classList.remove('go-to-top');
  }
};
.box_0 {
  background-color: lightgreen;
  height: 100vh;
  width: 100vw;
}

.box_1 {
  background-color: magenta;
  height: 50vh;
  width: 100vw;
}

.box_2 {
  background-color: blue;
  height: 50vh;
  width: 100vw;
}

.box_3 {
  background-color: yellow;
  height: 20vh;
  width: 100vw;
}

footer {
  background-color: pink;
  height: 40vh;
  width: 100vw;
}


/* styling for the section trigger */

.section_trigger svg {
  height: 60%;
  left: 20%;
  position: absolute;
  top: 20%;
  width: 60%;
}


/** @define c-scroll-nav
  */

.section_trigger {
  background: hsla(0, 0%, 0%, 0.45);
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  height: 50px;
  width: 50px;
}

.section_trigger:hover {
  background: black;
}

.section_trigger--center {
  position: absolute;
  left: 50%;
  transform: translate(0, -50%);
  bottom: 12px;
  transform: rotate(270deg);
}

.section_trigger--side {
  bottom: 12px;
  position: fixed;
  right: 12px;
  transform: rotate(270deg);
}

.go-to-top {
  transform: rotate(90deg);
}
<span class="section_trigger section_trigger--center">
  <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
</span>
<span class="section_trigger section_trigger--side go-to-top">
  <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
</span>
<section>
  <div class="box_0"></div>
</section>
<section>
  <div class="box_2"></div>
</section>
<section>
  <div class="box_3"></div>
</section>
<section>
  <div class="box_1"></div>
</section>
<section>
  <div class="box_2"></div>
</section>
<section>
  <div class="box_3"></div>
</section>
<footer></footer>

I think i am not using the getBoundingClientRect() method correctly, i am also unsure if i should use it relative to the viewport or not.

Beside that i don't know if its effective and the right approach i have in general, i also read that its possible to increase performance using a throttle ? method .... thanks a lot, really appreciate it guys.

Answers 1

  • Sorry I don't have time to finish but I've made some updates. The code doesn't trigger an error when clicking on the center scroll trigger, but it also doesn't yet work. I'm posting to save my answer for now, and will update as needed.

    My advice when doing this sort of thing is to implement in pieces. For example, use the working jQuery code, and then replace one function or small piece at a time in pure JS. It will be easier to debug.

    Some changes:

    1. if (window.pageYOffset + windowHeight == documentHeight) {
    2. const scrollTrigger = document.getElementsByClassName('section_trigger')[0];

    Code:

    // Debuging
    window.onscroll = function() {
      // console.log('top: '  + (window.pageYOffset || document.documentElement.scrollTop));
      // console.log('section: '  + (window.pageYOffset || document.scrollSection[nextSection].scrollTop));
      // console.log(nextSection);
      // console.log(scrollSection[0]);
    }
    
    /*
     * Helper Function: Vanilla version of jQuery scrollTop
     */
    
    function scrollTo(element, to, duration) {
      if (duration <= 0) return;
      let difference = to - element.scrollTop;
      let perTick = difference / duration * 50;
    
      setTimeout(() => {
        element.scrollTop = element.scrollTop + perTick;
        if (element.scrollTop == to) return;
        scrollTo(element, to, duration - 10);
      }, 10);
    }
    
    /*
     * Main Function: Scroll to section elements and back to top
     */
    
    const scrollSection = document.getElementsByTagName('section');
    const scrollTrigger = document.getElementsByClassName('section_trigger')[0];
    let nextSection = 0;
    
    // turn arrow back arround after clicked to return to top
    scrollTrigger.addEventListener('click', () => {
      scrollTrigger.classList.remove('go-to-top');
      console.log('removed class `go-to-top`');
    
      // If reached the last section, scroll back to the top on the next click:
      if (nextSection >= scrollSection.length) {
        function runScroll() {
          scrollTo(document.body, 0, 1000);
          nextSection = 0;
          console.log('Scrolled back to Top');
        }
      }
    
      scrollTo(document.body, 0, 1000);
    
      // if we scroll down increment section counter
      while (document.body.scrollTop > scrollSection[nextSection].getBoundingClientRect().scrollTop) { //doesnt work , cant next section scrollSection undefined
        nextSection++;
        console.log('incremented counter to' + nextSection);
      }
    
      // If next section is the last, add go-to-top class to rotate arrow:
      if (nextSection === (scrollSection.length - 1)) {
        scrollTrigger.classList.add('go-to-top');
        console.log('added class `go-to-top`');
      }
    
      // Move to next section and increment counter
      function runScroll() {
        scrollTo(document.scrollSection[nextSection].getBoundingClientRect().scrollTop, 0, 1000); //doesnt work , cant next section scrollSection undefined
        nextSection++;
        console.log('Scrolled one section down');
      }
    
    });
    
    /*
     * Helper Function: vanilla JS get document height and window width and height
     */
    
    const body = document.body;
    const html = document.documentElement;
    
    // document height
    const documentHeight = Math.max(body.scrollHeight, body.offsetHeight,
      html.clientHeight, html.scrollHeight, html.offsetHeight);
    
    // window width and height
    const windowWidth = window.innerWidth || document.documentElement.clientWidth || body.clientWidth;
    const windowHeight = window.innerHeight || document.documentElement.clientHeight || body.clientHeight;
    
    // turn arrow when scrolled to footer
    window.onscroll = function() {
      if (window.pageYOffset + windowHeight == documentHeight) { // not sure if getBoundingClientRect is the right choice since its relative to the viewport
        scrollTrigger.classList.add('go-to-top');
      } else {
        scrollTrigger.classList.remove('go-to-top');
      }
    };
    .box_0 {
      background-color: lightgreen;
      height: 100vh;
      width: 100vw;
    }
    
    .box_1 {
      background-color: magenta;
      height: 50vh;
      width: 100vw;
    }
    
    .box_2 {
      background-color: blue;
      height: 50vh;
      width: 100vw;
    }
    
    .box_3 {
      background-color: yellow;
      height: 20vh;
      width: 100vw;
    }
    
    footer {
      background-color: pink;
      height: 40vh;
      width: 100vw;
    }
    
    
    /* styling for the section trigger */
    
    .section_trigger svg {
      height: 60%;
      left: 20%;
      position: absolute;
      top: 20%;
      width: 60%;
    }
    
    
    /** @define c-scroll-nav
      */
    
    .section_trigger {
      background: hsla(0, 0%, 0%, 0.45);
      border: 0;
      border-radius: 50%;
      cursor: pointer;
      height: 50px;
      width: 50px;
    }
    
    .section_trigger:hover {
      background: black;
    }
    
    .section_trigger--center {
      position: absolute;
      left: 50%;
      transform: translate(0, -50%);
      bottom: 12px;
      transform: rotate(270deg);
    }
    
    .section_trigger--side {
      bottom: 12px;
      position: fixed;
      right: 12px;
      transform: rotate(270deg);
    }
    
    .go-to-top {
      transform: rotate(90deg);
    }
    <span class="section_trigger section_trigger--center">
      <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
    </span>
    <span class="section_trigger section_trigger--side go-to-top">
      <svg viewBox="0 0 100 100"><path d="M 10,50 L 60,100 L 70,90 L 30,50  L 70,10 L 60,0 Z" fill="white" class="arrow"></path></svg>
    </span>
    <section>
      <div class="box_0"></div>
    </section>
    <section>
      <div class="box_2"></div>
    </section>
    <section>
      <div class="box_3"></div>
    </section>
    <section>
      <div class="box_1"></div>
    </section>
    <section>
      <div class="box_2"></div>
    </section>
    <section>
      <div class="box_3"></div>
    </section>
    <footer></footer>


Related Articles