Home > Software design >  Differentiate user scroll from anchor scroll
Differentiate user scroll from anchor scroll

Time:04-07

In my Javascript code I'm listening for the scroll event and doing some work whenever it fires:

document.addEventListener(
    'scroll',
    (event) => {
        // do something
    },
    { passive: true }
);

I have a nav bar which contains anchors to different sections on my page, which when clicked causes the page to scroll down to the section (and fires the scroll event):

<nav >
    <ul >
        <li >
            <a  href="#anchor-1">Popular Items</a>
        </li>
    </ul>
</nav>

...

<section id="anchor-1">...</section>

However, I do not want the scroll event to fire when the page scrolls as a result of clicking on the anchor link. I only want it to fire when the user actually scrolls (i.e. with mouse wheel or by swiping).

I couldn't find any field in the 'event' object that would differentiate the two scroll sources but perhaps I missed something.

CodePudding user response:

let navElements = document.getElementsByClassName("nav-link")[0]
let scroll = true

navElements.addEventListener("click", function () {
  scroll = false
});



document.addEventListener(
    'scroll',
    (event) => {
        if ( scroll === true ) {
            // do something
        } 
        scroll = true
       
        
    },
    { passive: true }
);
<nav >
    <ul >
        <li >
            <a  href="#anchor-1">Popular Items</a>
        </li>
    </ul>
</nav>

...

<section id="anchor-1">...</section>

My approach

  1. I initialize a element scroll, set to true.
  2. I set another eventListener for the navbar link. Whenever it is clicked, scroll's value changes to false.
  3. Then in your scroll eventListener, only do something if scroll === true, meaning it's not the nav-link that was clicked.
  4. Lastly, I reset scroll's value to it's original state true.

CodePudding user response:

The scroll event takes the most generic Event interface so there are no properties for differentiating the scroll source. I think it is basically unfeasible without completely overriding the default scroll anchor behaviour with your own JS. Having tried to do similar things in the past and ended up with horrible results, I would not recommend it.

You might want to think about doing this in reverse: disable anything you don't want to happen when a user clicks on the link, then renable it the next time a wheel or touchmove event is triggered. These events are particular to user scroll behaviour and won't be triggered by an anchor link.

const link = document.querySelector("a");
const counter = document.querySelector("p");
const target = document.querySelector("#target");

let userScrolling = true;

link.addEventListener("click", () => userScrolling = false);

window.addEventListener("wheel", () => userScrolling = true);
window.addEventListener("touchmove", () => userScrolling = true);
window.addEventListener("mousedown", () => userScrolling = true);

window.addEventListener("scroll", () => {
  if (!userScrolling) return;
  counter.textContent = parseInt(counter.textContent)   1;
});
html {
  scroll-behavior: smooth;
}

p {
  position: fixed;
  bottom: 0;
  right: 0;
  padding: 16px;
}

.separator, #target {
  height: 100vh;
}
<a href="#target">Link</a>
<p>0</p>
<div ></div>
<div id="target">Target<div>

You'll notice that there's a mousedown handler in there too. That's so we can enable user scrolling again by manually clicking and dragging the scrollbar, and also by clicking the middle-wheel compass. A mousedown event will stop the anchor scrolling behaviour, at least where I'm testing this on FF. Unfortunately one scroll event will still slip through if you use mousedown to reset the behaviour this way - it should be undetectable in the vast majority of user cases.

You could also go a step further to alleviate this issue and make sure the mouse event is from a middle wheel button event.button = 1, or is triggered over a scrollbar. This second factor is potentially complicated and beyond the scope of the question so I leave you with this answer which is the most promising I could find.

Honestly though, I think the takeaway here should be don't try to do this. The reason you can't distinguish user/anchor in the event is because they are functionally identical - a user clicking a button to scroll for them is user scroll behaviour and it should not realistically cause a problem. If you have some scroll-based animation you need to skip over, it might be better to just remove smooth-scrolling behaviour instead and reset the animation values in the background.

  • Related