Turn off javascript/jquery function after certain scrolling distance

Hello everybody,

I’m working on a one-page web site, where each page is as tall as your viewport, and you can scroll down to go to the next pages. I found a nice jQuery plugin called windows.js that automatically snaps your page in place after you’ve scrolled down, so it always exactly fits your browser window. Works great, but I want this plugin to work on every page except for the last page. I’m not an expert with javascript, so I hope there’s an easy solution for this. This is the plugin part in my code:

$(document).ready(function() {

	var $windows = $('.windows');

	$windows.windows({
		snapping: true,
		snapSpeed: 400,
		snapInterval: 1200,
		onScroll: function(s){},
		onSnapComplete: function($el){},
		onWindowEnter: function($el){}
	});
});

What I’m trying to achieve is that this code should run, until you’ve scrolled past a certain point where the last page hits the top of the window, and from there on you should be able to scroll without the ‘snapping’. When you scroll back up to pages above this last one, it should run again.

Any ideas? Thanks in advance,

Hugo

Hi,

As I understand it, you give elements that should snap into place a specific class name (for example), then initialize the plugin for these elements.

E.g.

<section class="window"></section>
<section class="window"></section>
<section class="window"></section>

$('.window').windows({
        snapping: true,
        snapSpeed: 500,
        snapInterval: 1100,
        onScroll: function(scrollPos){
            // scrollPos:Number
        },
        onSnapComplete: function($el){
            // after window ($el) snaps into place
        },
        onWindowEnter: function($el){
            // when new window ($el) enters viewport
        }
})

So, for those elements that shouldn’t snap into place, just don’t give them that class name.

<section class="window"></section>
<section class="window"></section>
<section class="window"></section>
<section></section> 
<section></section>

Or did I miss the point?

Hi Pullo,

Thanks, that is the point indeed and that’s what I too expected would happen. But it doesn’t, when I take away the ‘windows’ class from the last section and scroll that page into view, it scrolls all the way back to the very top, to the home page again. Here’s a link to the site (in progress), that might help and explain it a bit better: http://www.11amtoday.nl/klanten/brandweerboot/v3/

Oh yeah, that’s a bit weird.

Is their any reason you don’t want to apply the same behaviour to the blog section of your site (i.e. give it a class of “window”, too)?

Yeah, each page on the site gets the exact height of your viewport, except for that blog page; that page gets more and more height as more items are being added over time, so that’s why I placed that page all the way on the bottom.

Maybe I can calculate the height of all pages above the blog, and then do ‘something’ with javascript when you scroll past that height (so when the top of the blog page hits the top of the window), but I’m not too familiar with javascript yet on how to ‘disable the windows.js plugin’ or something like that when you scroll past that certain point?

looks like a job for the return statement.

However I’m not familiar with the plugin, and so I don’t know if it actually bothers to measure the distance between currentPos and document top. Or if you’d have to give the blog part another class and write an addition to the plugin, where when .blog top position === window/document top, DoOtherThings. But that would be the detection, based on where the top of Blog is in relation to the document or main window.

Hey I did get it to work now, so that’s good, however I had to put the whole thing inside the $(window).scroll(function() which is inside the document ready function. I’m not sure if that makes it way too heavy now, as it continuously reads that function while you are scrolling? Or should that be all right? It sort of looks like this now:

$(document).ready(function() {

  $(window).scroll(function() {

    if (scroll > blogTop) {
      var $windows = '';
    } else if (scroll <= blogTop) {
      var $windows = $('.windows');
    }
		
    $windows.windows({
      snapping: true,
      snapSpeed: 400,
      snapInterval: 1200,
      onScroll: function(s){},
      onSnapComplete: function($el){},
      onWindowEnter: function($el){}
    });

  });

});

Here’s the live link: http://www.11amtoday.nl/klanten/brandweerboot/v3/

In your position, I would just hack the plugin a little.

If you look at the plugin source code, you should see this:

var _snapWindow = function(){
  // clear timeout if exists
  if(t){clearTimeout(t);}
  // check for when user has stopped scrolling, & do stuff
  if(options.snapping){
    t = setTimeout(function(){
      var $visibleWindow = _getCurrentWindow(), // visible window
          scrollTo = $visibleWindow.offset().top, // top of visible window
          completeCalled = false;

      // animate to top of visible window
      $('html:not(:animated),body:not(:animated)').animate({scrollTop: scrollTo }, options.snapSpeed, function(){
        if(!completeCalled){
          if(t){clearTimeout(t);}
          t = null;
          completeCalled = true;
          options.onSnapComplete($visibleWindow);
        }
      });
    }, options.snapInterval);
  }
};

Add this line to the top of it:

if($(".noSnap").visible( true )){ return false; }

The .visible() method checks to see if any elements with the class of .noSnap are visible on the screen.
The plugin does expose its own .isOnScreen() method, but I found this to be unreliable when testing.

Then we need to add the code for the .visible() method:

(function($){
  /**
  * Copyright 2012, Digital Fusion
  * Licensed under the MIT license.
  * http://teamdf.com/jquery-plugins/license/
  *
  * @author Sam Sehnert
  * @desc A small plugin that checks whether elements are within
  *     the user visible viewport of a web browser.
  *     only accounts for vertical position, not horizontal.
  */
  $.fn.visible = function(partial){
    var $t = $(this),
    $w = $(window),
    viewTop = $w.scrollTop(),
    viewBottom = viewTop + $w.height(),
    _top = $t.offset().top,
    _bottom = _top + $t.height(),
    compareTop  = partial === true ? _bottom : _top,
    compareBottom  = partial === true ? _top : _bottom;

    return ((compareBottom <= viewBottom) && (compareTop >= viewTop));
  };
})(jQuery);

After that, give the containing section of your blog a class of “.noSnap” and you should be good.

Here’s a demo.
The final section will not snap into place, whereas the others will.

Hi! Super thanks for all your input, that seems like a much better solution than anything I could think of. There’s one thing though; it works good if you slowly scroll down, but when you scroll down real fast (either manually or by pressing the blog button in my menu, which auto scrolls down), it automatically scrolls back up again. Same in your demo. Any ideas on that? Besides that it works perfect, thanks very much for helping out.

Yeah, that seems to be the timeout event firing.

Change the beginning of the function to this:

var _snapWindow = function(){
  // clear timeout if exists
  if(t){clearTimeout(t);}
						
  if($(".noSnap").visible( true )){ return false; }

I updated my demo to reflect this.

(Be sure to clear your cache before trying it).

Sweet! That’s it, thank you very much for that. One last small thing I would like to ask you: Right now the function ‘visible’ returns true as soon as the element with class ‘noSnap’ enters the screen on the very bottom. Can I easily change that to when the top of that element hits the top of the window instead of enters the bottom?

Thanks

Sure! You can use the offset function for that. It gives you the element’s position relative to the (left,top) of the document.

Let me know if you get stuck.

Yeah I’m pretty familiar with the offset function, I’m already using it in my navigation, but I just don’t know how or where to put it inside your ‘visible’ function, to fire it once the top of the blog hits the top of the viewport instead of becoming visible on the bottom of the viewport:

(function($){
	/**
	* Copyright 2012, Digital Fusion
	* Licensed under the MIT license.
	* http://teamdf.com/jquery-plugins/license/
	*
	* @author Sam Sehnert
	* @desc A small plugin that checks whether elements are within
	*		 the user visible viewport of a web browser.
	*		 only accounts for vertical position, not horizontal.
	*/
	$.fn.visible = function(partial){
		var $t			= $(this),
		$w				= $(window),
		viewTop			= $w.scrollTop(),
		viewBottom		= viewTop + $w.height(),
		_top			= $t.offset().top,
		_bottom			= _top + $t.height(),
		compareTop		= partial === true ? _bottom : _top,
		compareBottom	= partial === true ? _top : _bottom;
		
		return ((compareBottom <= viewBottom) && (compareTop >= viewTop));
	};
})(jQuery);

Change this:

if($(".noSnap").visible( true )){ return false; }

To this:

if($(".noSnap").visible(true) && ($(document).scrollTop() - $(".noSnap").offset().top) > 0){
  return false;
}

Does that work?

Pullo, that works great. Thank you very much for all your help on this, I really appreciate it! The result so far is online for viewing at http://www.11amtoday.nl/klanten/brandweerboot/v3/

Thanks!

No problem :slight_smile:

However, the code is starting to get a bit messy now and we could really do with caching our selectors, so that we are not querying the DOM every time something happens.

Leave this with me and I’ll post an optimized version back here soon.

Wow… that’s really nice of you. Thanks! Looking forward to see what that’s gonna look like. Cheers!

Hi,

I modified the plugin for you a little.
Now, you should give all of the sections a class of “window” and remove the class of “noSnap”.

<section class="window" id="one">Section A</section>
<section class="window" id="two">Section B</section>
<section class="window" id="three">Section C</section>
<section class="window" id="four">Section D</section>
<section class="window" id="five">Section E</section>        
<section class="window" id="six">Section F</section>

When you initialize the plugin, you can now pass in an option of ignoreLast:

var $win = $('.window');
$win.windows({
  snapping: true,
  snapSpeed: 500,
  snapInterval: 1100,
  ignoreLast: true,
  onScroll: function(s){},
  onSnapComplete: function($el){},
  onWindowEnter: function($el){}
});

If this is set to true, the final element in the collection that you call the windows() method on, will be allowed to scroll as far as it likes past the top of the viewport, without snapping back into position.

I made a new demo to reflect this.

Here’s the code:

<!doctype html>
<html>
  <head>
    <title>windows</title>
    <style>
      html, body{
        height: 100%;
      }
      .window{
        height:100%;
      }
      #one{
        background-color: red;
      }
      #two{
        background-color: blue;
      }
      #three{
        background-color: yellow;
      }
      #four{
        background-color: green;
      }
      #five{
        background-color: pink;
      }
      #six{
        background-color: #639;
        height:3000px;
      }
    </style>
  </head>
  
  <body>
    <section class="window" id="one">Section A</section>
    <section class="window" id="two">Section B</section>
    <section class="window" id="three">Section C</section>
    <section class="window" id="four">Section D</section>
    <section class="window" id="five">Section E</section>        
    <section class="window" id="six">Section F</section> 
          
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script>
      /*!
       * windows: a handy, loosely-coupled jQuery plugin for full-screen scrolling windows.
       * Version: 0.0.1
       * Original author: @nick-jonas
       * Website: http://www.workofjonas.com
       * Licensed under the MIT license
       */
      
      ;(function ( $, window, document, undefined ) {
      
      var that = this,
          pluginName = 'windows',
          defaults = {
              snapping: true,
              snapSpeed: 500,
              snapInterval: 1100,
              ignoreLast: false,
              onScroll: function(){},
              onSnapComplete: function(){},
              onWindowEnter: function(){}
          },
          options = {},
          $w = $(window),
          s = 0, // scroll amount
          t = null, // timeout
          $windows = [];
          
          /**
           * Constructor
           * @param {jQuery Object} element       main jQuery object
           * @param {Object} customOptions        options to override defaults
           */
          function windows( element, customOptions ) {
              this.element = element;
              options = options = $.extend( {}, defaults, customOptions) ;
              this._defaults = defaults;
              this._name = pluginName;
              $windows.push(element);
              var isOnScreen = $(element).isOnScreen();
              $(element).data('onScreen', isOnScreen);
              if(isOnScreen) options.onWindowEnter($(element));
          }
      
          /**
           * Get ratio of element's visibility on screen
           * @return {Number} ratio 0-1
           */
          $.fn.ratioVisible = function(){
              var s = $w.scrollTop();
              if(!this.isOnScreen()) return 0;
              var curPos = this.offset();
              var curTop = curPos.top - s;
              var screenHeight = $w.height();
              var ratio = (curTop + screenHeight) / screenHeight;
              if(ratio > 1) ratio = 1 - (ratio - 1);
              return ratio;
          };
      
          /**
           * Is section currently on screen?
           * @return {Boolean}
           */
          $.fn.isOnScreen = function(){
              var s = $w.scrollTop(),
                  screenHeight = $w.height(),
                  curPos = this.offset(),
                  curTop = curPos.top - s;
              return (curTop >= screenHeight || curTop <= -screenHeight) ? false : true;
          };
      
          /**
           * Get section that is mostly visible on screen
           * @return {jQuery el}
           */
          var _getCurrentWindow = $.fn.getCurrentWindow = function(){
              var maxPerc = 0,
                  maxElem = $windows[0];
                  
              $.each($windows, function(i){
                  var perc = $(this).ratioVisible();
                  if(Math.abs(perc) > Math.abs(maxPerc)){
                      maxElem = $(this);
                      maxPerc = perc;
                  }
              });
              return $(maxElem);
          };
      
      
          // PRIVATE API ----------------------------------------------------------
      
          /**
           * Window scroll event handler
           * @return null
           */
          var _onScroll = function(){
              s = $w.scrollTop();
      
              _snapWindow();
      
              options.onScroll(s);
      
              // notify on new window entering
              $.each($windows, function(i){
                  var $this = $(this),
                      isOnScreen = $this.isOnScreen();
                  if(isOnScreen){
                      if(!$this.data('onScreen')) options.onWindowEnter($this);
                  }
                  $this.data('onScreen', isOnScreen);
              });
          };
      
          var _onResize = function(){
              _snapWindow();
          };
      
          var _snapWindow = function(){          
            // clear timeout if exists
            if(t){clearTimeout(t);}
            
            if(options.ignoreLast){
              if ($(document).scrollTop() - $($windows[$windows.length-1]).offset().top > 0){ 
                return false;
              }
            }
            
            // check for when user has stopped scrolling, & do stuff
            if(options.snapping){
              t = setTimeout(function(){
                var $visibleWindow = _getCurrentWindow(), // visible window
                    scrollTo = $visibleWindow.offset().top, // top of visible window
                    completeCalled = false;
                    
                // animate to top of visible window
                $('html:not(:animated),body:not(:animated)').animate({scrollTop: scrollTo }, options.snapSpeed, function(){
                  if(!completeCalled){
                    if(t){clearTimeout(t);}
                    t = null;
                    completeCalled = true;
                    options.onSnapComplete($visibleWindow);
                  }
                });
              }, options.snapInterval);
            }
          };
      
          /**
           * A really lightweight plugin wrapper around the constructor,
              preventing against multiple instantiations
           * @param  {Object} options
           * @return {jQuery Object}
           */
          $.fn[pluginName] = function ( options ) {
      
              $w.scroll(_onScroll);
              $w.resize(_onResize);
      
              return this.each(function(i) {
                  if (!$.data(this, 'plugin_' + pluginName)) {
                      $.data(this, 'plugin_' + pluginName,
                      new windows( this, options ));
                  }
              });
          };
      
      })( jQuery, window, document );          
    </script>
  
    <script>
      var $win = $('.window');
      $win.windows({
        snapping: true,
        snapSpeed: 500,
        snapInterval: 1100,
        ignoreLast: true,
        onScroll: function(s){},
        onSnapComplete: function($el){},
        onWindowEnter: function($el){}
      });
    </script>
  </body>
</html>

I removed the .visible() method entirely (as it was superfluous) and I amended most of the stuff that was querying the DOM.
I did keep $(document).scrollTop() however, as that could change if the windows gets resized.

I hope this helps.

Hey Pullo,

Thanks again. I updated it and works perfectly. I can see it’s much cleaner now and less heavy. Thank you very much!

Regards,

Hugo