10

I'm trying to detect with javascript an intentional extra top/bottom scroll. $(window).scrollTop() and window.pageYOffset are not useful since they stop at Top 0 and I would like to reach something like top -X. For bottom let's suppose my document's height is 500, bottom would be like bottom 5XX.

EDIT: An example code could be:

$(window).scroll(function(){

  if(intentionalScrollTop){
    // Do something
  }else if(intentionalScrollDown){
    // Do something
  }

});

Gif example: enter image description here

xWaZzo
  • 748
  • 5
  • 19
  • My first usage idea of this is to detect/prevent an adressbar display on mobile... Is that it? Anyway, this *may* be achieved by setting a content wrapper around the body first child... Maybe. The question: Have you tried anything? I don't see any code here. – Louys Patrice Bessette Oct 20 '16 at 00:54
  • @LouysPatriceBessette I want to use this for desktop, not mobiles. I added a little example of code, but please note that `$(window).scrollTop()` and `window.pageYOffset` were not useful. – xWaZzo Oct 22 '16 at 05:40
  • This seems like a variation on infinite scrolling. – Ouroborus Oct 22 '16 at 09:23

3 Answers3

2

What I understood from your question was not only to "detect an overscroll"...
But also use it to create an animation like you show in your question.

I made a solution using a wrapper div, as I commented 2 days ago.

You can see it in CodePen or the snippet below.

$(document).ready(function(){
     
    var at_Top=true;
    var at_Bottom=false;
 var previous_scrolled;
 var triggerTime=0;
    var scroll_dir=false;   // false=>Down true=>up
 var scrolled_bottom = $("body").height() - $(window).height();
 var animationDelay = 300;
 var animationTimeout = 350;  //Max delay between 2 triggers is 1 sec.
         //So keep this value under 400ms
         //Because one complete animation is 300ms+350ms+300ms.
         //To have longer animation delays, add time to the triggerDelay
 
 var triggerDelay=0; // You can add more delay to allow the next trigger (in seconds).
 
    $(window).scroll(function(){
        var scrolled=$(window).scrollTop();

        // Reached the top?
        if(scrolled==0){
            at_Top=true;
        }else{
            at_Top=false;
        }

        // Reached the bottom?
        if(scrolled==scrolled_bottom){
            at_Bottom=true;
        }else{
            at_Bottom=false;
        }
        
        // Scroll direction
  if( $(this).scrollTop() > previous_scrolled ){
   scroll_dir=false;  //scroll down
  }else{
   scroll_dir=true;  //scroll up
  }
  
  // Keep previous scrollTop position in memory
  previous_scrolled = $(this).scrollTop();
  
  animationTrigger();
    });

 function animationTrigger(){
        if(at_Top && scroll_dir){
            console.log("Scrolling when at top.");
   $("#wrapper").stop().animate({"margin-top":"3em"},animationDelay);
   setTimeout(function(){
    $("#wrapper").stop().animate({"margin-top":0},animationDelay);
   },animationTimeout);
   clearTimeout(clearConsole);
   var clearConsole = setTimeout(function(){
    console.clear();
   },3000);
        }
        if(at_Bottom && !scroll_dir){
            console.log("Scrolling when at bottom.")
   $("#header").stop().animate({"height":0},animationDelay);
   $("#footer-spacer").stop().animate({"height":"3em"},animationDelay);
   setTimeout(function(){
    $("#header").stop().animate({"height":"3em"},animationDelay);
    $("#footer-spacer").stop().animate({"height":0},animationDelay);
   },animationTimeout);
   clearTimeout(clearConsole);
   var clearConsole = setTimeout(function(){
    console.clear();
   },3000);
        }
    }
 
    // KEYBOARD ARROWS UP/DOWN AND PAGE UP/DOWN SCROLLING
    $(window).on("keydown",function(e){
        //console.log(e.which);
        if( (e.which==38) || (e.which==33) ){    // Arrow up or Page up
            scroll_dir=true;
        }
        if( (e.which==40) || (e.which==34) ){    // Arrow down or Page down
            scroll_dir=false;
        }
  
  // Limit triggers to 1 per second... Because when holding a key down for long, it triggers too fast...
  var thisSecond = new Date().getSeconds()
        if( (triggerTime != thisSecond) || (triggerTime < (thisSecond - triggerDelay) ) ){
            animationTrigger();
            triggerTime=thisSecond;
        }
    })

    // WHEEL SCROLLING
    // Inspired from this SO answer: http://stackoverflow.com/a/7309786/2159528

    //Firefox
    $(window).bind('DOMMouseScroll', function(e){
        var scrolled2=$(window).scrollTop();
        
        if(e.originalEvent.detail > 0) {
            scroll_dir=false;  //scroll down
            //console.log("down");
        }else {
            scroll_dir=true;   //scroll up
            //console.log("up");
        }
        
  // Limit triggers to 1 per second... Because wheel turns quite fast.
        var thisSecond = new Date().getSeconds()
        if( (triggerTime != thisSecond) || (triggerTime < (thisSecond - triggerDelay) ) ){
            animationTrigger();
            triggerTime=thisSecond;
        }
    });

    //IE, Opera, Safari
    $(window).bind('mousewheel', function(e){
  
        if(e.originalEvent.wheelDelta < 0) {
            scroll_dir=false;  //scroll down
        }else {
            scroll_dir=true;   //scroll up
        }
        
        // Limit triggers to 1 per second... Because wheel turns quite fast.
        var thisSecond = new Date().getSeconds()
        if( (triggerTime != thisSecond) || (triggerTime < (thisSecond - triggerDelay) ) ){
            animationTrigger();
            triggerTime=thisSecond;
        }
    });

}); // End Document.ready
body,wrapper{
    padding:0;
    margin:0;
    height:100;
}
#page{
    height:1500px;
    width:100%;
}
#header,#footer{
    height:3em;
    padding:0.5em;
    background-color:cyan;
}
#content{
    height:calc(100% - 8em);    /* -8em for header and footer... (height: 3em + padding: 2x 0,5em) */
    padding:0 0.5em;
    overflow:hidden;
}
#footer-spacer{
    height:0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wrapper">
    <div id="page">
        <div id="header">
            This is the page's top
        </div>
        <div id="content">
            <h1>Scroll this page to see the overscroll effect at top and bottom</h1>
            <br>
            <ul>
                <li>using the mouse wheel</li>
                <li>keyboard arrows</li>
                <li>keyboard page up/down</li>
            </ul>
            <br>
            <br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
            Content...<br>
        </div>
        
        <div id="footer">
            This is the page's bottom
        </div>
        <div id="footer-spacer"></div>
    </div>
</div>

To detect a scroll attempt when the page is at top or bottom is on thing...
Using it to set an animation like you show is another.

Approache for the top:
The wrapper's margin is animated from 0em to 3em, pushing the header and content down.
So the page "looks" like overscrolled.

Approache for the bottom:
This was a challenge...
Can't do the same as for the top with the wrapper's margin-bottom because it works... But below the viewport, which is not really what we want.

So in this case, I defined the "content" as height:calc(100% - 8em) (header and footer both have height:3em and padding:0.5em) just to make sure that wrapper is 100% filled. The empty div to animate is under the footer... When its height passes from 0em to 3em, it creates the overscroll "illusion" by pushing the footer up.

Note that the header retracts in the same time, in order to free space. At this point, the header isn't visible, so why not?

This script works when dragging the scrollbar, spinning the mousewheel and hitting 1 of the 4 "usual" keys on the keyboard (arrows and page up/down).

I leaved a lot of console.log() that you can use to explore how it works and improve the animation and make it your taste.
;)

Louys Patrice Bessette
  • 33,375
  • 6
  • 36
  • 64
  • This is a good approach. Thanks @Louys Patrice Bessette, but I want to detect an EXTRA scroll top/bottom, not the first scroll at top/bottom, however, you gave me some clues to figure out how to find my answer. Thanks! :) – xWaZzo Oct 24 '16 at 05:10
  • +50 because you gave me a live example and I found the main clues to figure out my solution. – xWaZzo Oct 25 '16 at 02:38
  • It was a fun an *unusual* question to answer. I'm happy that you've found what you need through my contribution ;) – Louys Patrice Bessette Oct 25 '16 at 13:07
0

There is actually exists a library named overscroll but unfortunately this only works in Safari because Chrome has removed support for overscroll numbers.

So the only way for me to do it is to listen for mouse wheel event, check if current scroll position is on top or on the bottom of the page and then if scroll destination matches over scroll zone return true.

Keep in mind, this will fire event even if a system doesn't support over the scroll. So if you want to make it work only if true over scroll happens make sure to check if user's OS is macOS(navigator.platform === 'MacIntel').

document.getElementById("content").innerHTML = new Array(60).fill(0).map(function() {
    return '<div>Sample text</div>';
   }).join('');

document.onwheel = function (event) {
  if(intentionalScrollTop(event)) {
    console.log('overscrolled top');
  } else if(intentionalScrollBottom(event)) {
    console.log('overscrolled bottom');
  }
}
  
function intentionalScrollTop (event) {
  return document.body.scrollTop === 0 && event.deltaY < 0;
}
  
function intentionalScrollBottom (event) {
  return (window.innerHeight + window.scrollY) >= document.body.offsetHeight && event.deltaY > 0;
}
<div id="content"></div>
G07cha
  • 4,009
  • 2
  • 22
  • 39
0

Thanks to @Louys and @Konstantin I found some clues to get my answer.

To detect the scroll event with cross browser support I used this reference, which creates an event listener addWheelListener( elem, callback, useCapture ).

Then using scrollPos I can detect whether the scroll is at top/bottom edge or none and with e.deltaY and maxScrollTop or maxScrollBottom I can trigger the Extra scroll message.

$(document).ready(function(){

  var canvasHeight    = ($("body").height() - $(window).height()).toFixed(0),
      elem            = document.getElementsByTagName("BODY")[0],
      maxScrollTop    = -200,
      maxScrollBottom = 200,
      scrollTop       = false,
      scrollBottom    = false;

  addWheelListener( elem, function( e ) {  

    var scrollPos = $(window).scrollTop();

    // Is Scroll at Top or Bottom edge?
    if(scrollPos === 0 || scrollPos === parseInt(canvasHeight)){
      if(e.deltaY < -1){
        if(e.deltaY < maxScrollTop){
          $('#message').text('Extra Scroll Top');
          console.log('Extra Scroll Top');
        }

        // This can be removed if you dont need to detect the first scroll top.
        if(!scrollTop){
          scrollTop = true;
          $('#message').text('Scroll Top');
          console.log('Scroll Top');
        }
      }else if(e.deltaY > 1){
        if(e.deltaY > maxScrollBottom){
          $('#message').text('Extra Scroll Bottom');
          console.log('Extra Scroll Bottom');
        }

        // This can be removed if you dont need to detect the first scroll bottom.
        if(!scrollBottom){
          scrollBottom = true;
          $('#message').text('Scroll Bottom');
          console.log('Scroll Bottom');
        }
      }
    }else{
      // Clean Scroll positions.
      scrollTop     = false;
      scrollBottom  = false;
      $('#message').text('Not at the top/bottom edge');
      console.log('Not at the top/bottom edge');
    }

  }, {passive: true});


}); // End Document.ready

And here is the working example using CodePen.

http://codepen.io/xWaZzo/pen/NRorKj

xWaZzo
  • 748
  • 5
  • 19
  • You are welcome, you have a really interesting question but there is still one thing that bothers me, it detects over scroll even if there is no such functionality presented so you should consider adding OS check or provide animation if it's not supported by platforms. – G07cha Oct 24 '16 at 08:32
  • @KonstantinAzizov for my purpose is enough to detect the extra scroll intention to trigger a callback, the animation was just a way to illustrate what I was looking for. – xWaZzo Oct 25 '16 at 02:35