0

I'm implementing button which will on click vertically scroll down for one team member. So far I have managed that one click scrolls down for one team member, but the code breaks when user manually scrolls back to top.

Here is working JSFiddle

My code

<main class="team container">
    <div v-for='(element, index) in members' :id="element.specialId" class="team__member" v-show="activeIndex === index || widthSmall">
        <div class="team__member__bio">
            <div class="team__member__name">{{element.name}}</div>
        </div>
    </div>
    <a class="scroll-more scroll-team" v-show="widthSmall" @click="move($event)" style="cursor: pointer;">
        <span></span>{{ $t('scroll') }}
    </a>
</main>

export default {
    name: "Team",
    data() {
        return {
            members: [
                {
                    name: "Bojan Dovrtel",
                    specialId: 'bojan-div'
                },
                {
                    name: "Klemen Razinger",
                    specialId: 'kelemen-div'
                },
                {
                    name: "Davor Pečnik",
                    specialId: 'davor-div'
                },
                {
                    name: "Maja Katalinič",
                    specialId: 'maja-div'
                },
                {
                    name: "Petra Vovk",
                    specialId: 'petra-div'
                }
            ],
            secs: document.getElementsByClassName('team__member'),
            currentSection: 0,
        }
    },
    methods: {
        move(e) {
            if (this.currentSection < this.secs.length) {
                if (this.currentSection === 3) {
                    this.widthSmall = false;
                }
                window.scroll({
                    top: this.secs[++this.currentSection].offsetTop,
                    left: 0,
                    behavior: 'smooth'
                });
            } else if (this.currentSection > 0) {
                window.scroll({
                    top: this.secs[--this.currentSection].offsetTop,
                    left: 0,
                    behavior: 'smooth'
                });

            }
        }
    }
};

How can I detect that users have scrolled up and change the value of current section? If you have any additional informations, please let me know and I will provide. Thank you

Valor_
  • 3,461
  • 9
  • 60
  • 109

2 Answers2

1

To detect that a user has scrolled, you can listen for the scroll event on the container that is being scrolled. In this case, that would be the root element, so you can use window to add the event listener.

One way to do achieve that would be to add and remove the scroll listener in the created and destroyed lifecycle hooks, as mentioned in this answer.

Note that the scroll event will also be fired when you trigger a scroll with window.scroll({...}), not just user scrolling. So, you'll need to take care of that.

I'd recommend adding some kind of throttle to the scroll event listener and then responding to all scroll events, post throttle, by changing the currentSection value.

For example, your scroll event handler can be:

...,
onScroll(e) {
  if(this.throttle) {
    clearTimeout(this.throttle);
  }

  this.throttle = setTimeout(() => (this.currentSection = this.findCurrentSection(e)), 300);
},
...

Where throttle is just a data member used to hold the timeout value. The logic to find the value of currentSection will only be triggered 300ms after the last scroll event. You can also use requestAnimationFrame to do this, as mentioned here.

findCurrentSection is just a basic method that iterates over the secs array to find, well, the current section based on the current scroll value.

...,
findCurrentSection(e) {
  const curTop = document.documentElement.scrollTop;
  for(let i=0, len=this.secs.length; i < len; ++i) {
    const secTop = this.secs[i].offsetTop;
    if(curTop === secTop) {
        return i;
    } else if(curTop < secTop) {
        return Math.max(i-1, 0);
    }
  }
},
...

Note that since in this particular case the scrolling container is the root element, I'm using document.documentElement.scrollTop, but based on the context, you can get the required value from the corresponding ScrollEvent (e in this case).

Here's a working fiddle based on your question. Also note that I have modified the move function according to the changes introduced.

Rikonator
  • 1,830
  • 16
  • 27
  • Tried your fiddle, if you *keep on clicking* on the arrow button when it has reached the bottom, you can see that it will start scolling up and down instead of... 1. Staying in there at the bottom or, 2. Moving in reverse order all the way up or, 3. Immediately resetting the offset to the first element's. – Yom T. Feb 19 '19 at 08:32
  • @jom Yes, that's a consequence of leaving in the original `move` code. I actually left that in intentionally. I, and the fiddle, intended to answer the original question of detecting when a scroll occurs. It is up to @Valor_ to do what they wish with it, hopefully improving it to fit their requirements. – Rikonator Feb 19 '19 at 08:38
1

You could iterate through the elements, finding the closest one whose offsetTop (+ offsetHeight) matches (or be in range of) the current window.scrollY offset as it scrolls, and then decide whether to scroll to the next element or "readjust" the offset:

new Vue({
  el: '#app',

  data() {
    return {
      members: [
        {
          name: "Bojan",
          specialId: 'bojan-div'
        }, 
        {
          name: "Klemen",
          specialId: 'kelemen-div'
        }, 
        {
          name: "Davor",
          specialId: 'davor-div'
        }, 
        {
          name: "Maja",
          specialId: 'maja-div'
        }, 
        {
          name: "Petra",
          specialId: 'petra-div'
        }
      ],
      secs: document.getElementsByClassName('height'),
      currentSection: 0
    }
  },

  mounted() {
    this.move();
  },

  methods: {
    move() {
      let y = window.scrollY;
      let totalSection = this.secs.length;
      
      for (let index = 0; index < totalSection; index++) {
        let sec = this.secs[index];

        if (sec.offsetTop === y) {
          // currentSection matches current window.scrollY, so we want to move to the next section/element
          // Math.min() to ensure it won't go out of range, capping at the length of the total elements.
          this.currentSection = Math.min(index + 1, totalSection - 1);

          // Or reset the index once it has scrolled all the way down
          // this.currentSection = (index + 1) % totalSection;
          break;
        }
        else if (sec.offsetTop >= y && y <= (sec.offsetTop + sec.offsetHeight)) {
          // window.scrollY is currently between the matched element's offsetTop and offsetHeight.
          // This is user-initiated scrolling, so let's just "readjust" the offset rather than scrolling to the next element.
          this.currentSection = index;
          break;
        }
      }

      window.scroll({
        top: this.secs[this.currentSection].offsetTop,
        left: 0,
        behavior: 'smooth'
      });
    }
  },
})
.height {
  background-color: grey;
  height: 300px;
  border: solid 2px black;
}

.scroll-team {
  position: fixed;
  top: calc(100vh - 6rem);
  left: 50%;
  z-index: 2;
  display: inline-block;
  -webkit-transform: translate(0, -50%);
  transform: translate(0, -50%);
  color: #fff;
  letter-spacing: 0.1em;
  text-decoration: none;
  transition: opacity 0.3s;
}

.scroll-team a:hover {
  opacity: 0.5;
}

.scroll-more {
  padding-top: 60px;
  font-size: 1.35rem;
}

.scroll-more span {
  position: absolute;
  top: 0;
  left: 50%;
  width: 46px;
  height: 46px;
  margin-left: -23px;
  border: 1px solid #fff;
  border-radius: 100%;
  box-sizing: border-box;
  background: rgba(255, 255, 255, 0.2);
}

.scroll-more span::after {
  position: absolute;
  top: 50%;
  left: 50%;
  content: "";
  width: 16px;
  height: 16px;
  margin: -12px 0 0 -8px;
  border-left: 1px solid #fff;
  border-bottom: 1px solid #fff;
  -webkit-transform: rotate(-45deg);
  transform: rotate(-45deg);
  box-sizing: border-box;
}

.scroll-more span::before {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  content: "";
  width: 44px;
  height: 44px;
  box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.1);
  border-radius: 100%;
  opacity: 0;
  -webkit-animation: sdb03 3s infinite;
  animation: sdb03 3s infinite;
  box-sizing: border-box;
}
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <div class="height" v-for="(element, index) in members" :key="index">
    {{ element.name }}
  </div>
  <a class="scroll-more scroll-team" @click="move" style="cursor: pointer;">
    <span></span>
  </a>
</div>
Yom T.
  • 8,760
  • 2
  • 32
  • 49