Google analytics script

Latest jQuery CDN with code tiggling.

Wednesday 2 February 2011

jQuery animated "scroll into view" plugin (with additional ":scrollable" selector filter)

Sometimes our pages have to deal with long(er) unpaged lists of data. Long enough to fall off the viewable browser window estate. Actually you don't even have to deal with lists at all. Let me re-define the problem: when you have a scrollable element on your page (may be the very body of it) and you need to programmatically scroll to its out-scrolled child element, then this jQuery plugin is just for you. Now let's see when we have to do this and what could go wrong.

This plugin has a new (versioned) home on GitHub. The same code (as below) is provided there with complete version history. And you can also get a minified version over there if you're in a hurry and just need to run it in production right away. But the best thing is you can watch the project over there so you can be notified of new updates.

A real world scenario and usability issues

I have a few Ajax-powered pages in my application that display unpaged lists. Some of them may get long enough to fall off visible browser area. And all of these lists allow adding to as well. There's a nifty button Add at the top that opens a modal dialog window with an empty item editor form in it.

The process of adding a new item in the list is as follows:

  1. user clicks the Add button at the top;
  2. a nice modal dialog with an empty list item form opens;
  3. user fills in data which gets validated along the way;
  4. when all data is valid, an Ajax call is issued to save the item;
  5. item gets saved on the server and all I get back is HTML of the new item that I can directly append to my existing list;
  6. I append the item to the list and highlight it (you know the nice one-time background blink that we use to focus user's attention; usually to a new/changed element);

This seems fine, but what happens when the list falls off the viewable browser area? Well... we append the new item and highlight it. But since the item is not visible, users have all the right to become confused about what just happened. I guess that's a nice example of a bad usability.

What should be changed then? For starters we should display the item to the user before highlighting it. But that's not all. If we just jump to this item users might get confused. Again. How should they know where they jumped? If the content looks familiar, they may assume they've been moved up or down on the same page, but if the new scrolled-into content looks a bit different, they may as well think they were redirected to a different page. Another bad usability example. Not a typical thing developers would think about, but we should.

The correct solution

There are basically two things that need to be done here:

  1. we have to display our element before highlighting it;
  2. we have to smoothly scroll to it, not just jump - by smoothly I mean animate the movement so it becomes obvious whether page is scrolling up or down (or even left or right for what it's worth);
This will ensure that users won't get confused and may quickly move back to where they were before. Less confusion = better usability = more user satisfaction.

Why not reuse an existing jQuery plugin?

There's a great existing jQuery plugin scrollTo that does animated scrolling, but it requires you to provide the scrollable element that will get scrolled. In my particular case I wasn't able to use it without any additional logic because my scrollable elements are purely dynamic and I don't even know whether they exist in the first place (my application is part of a Sharepoint 2010 site which uses dynamic master pages). Hence I would either have to provide functionality to check for a scrollable ancestor element and use this existing plugin, that supports many usage scenarios but I would only use one, or write a less complex plugin that only requires you to execute it against the element you wish to display. I decided for the latter.

In order to make this fully reusable I've written a rather short jQuery plugin that does animated scrolling to any element (when scrolling is at all possible - so it has a scrollable ancestor element). This is the code:

   1:  /*!
   2:   * jQuery scrollintoview() plugin and :scrollable selector filter
   3:   *
   4:   * Version 1.8 (14 Jul 2011)
   5:   * Requires jQuery 1.4 or newer
   6:   *
   7:   * Copyright (c) 2011 Robert Koritnik
   8:   * Licensed under the terms of the MIT license
   9:   * http://www.opensource.org/licenses/mit-license.php
  10:   */
  11:   
  12:  (function ($) {
  13:      var converter = {
  14:          vertical: { x: false, y: true },
  15:          horizontal: { x: true, y: false },
  16:          both: { x: true, y: true },
  17:          x: { x: true, y: false },
  18:          y: { x: false, y: true }
  19:      };
  20:   
  21:      var settings = {
  22:          duration: "fast",
  23:          direction: "both"
  24:      };
  25:   
  26:      var rootrx = /^(?:html)$/i;
  27:   
  28:      // gets border dimensions
  29:      var borders = function (domElement, styles) {
  30:          styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle);
  31:          var px = document.defaultView && document.defaultView.getComputedStyle ? true : false;
  32:          var b = {
  33:              top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0),
  34:              left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0),
  35:              bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0),
  36:              right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0)
  37:          };
  38:          return {
  39:              top: b.top,
  40:              left: b.left,
  41:              bottom: b.bottom,
  42:              right: b.right,
  43:              vertical: b.top + b.bottom,
  44:              horizontal: b.left + b.right
  45:          };
  46:      };
  47:   
  48:      var dimensions = function ($element) {
  49:          var win = $(window);
  50:          var isRoot = rootrx.test($element[0].nodeName);
  51:          return {
  52:              border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0} : borders($element[0]),
  53:              scroll: {
  54:                  top: (isRoot ? win : $element).scrollTop(),
  55:                  left: (isRoot ? win : $element).scrollLeft()
  56:              },
  57:              scrollbar: {
  58:                  right: isRoot ? 0 : $element.innerWidth() - $element[0].clientWidth,
  59:                  bottom: isRoot ? 0 : $element.innerHeight() - $element[0].clientHeight
  60:              },
  61:              rect: (function () {
  62:                  var r = $element[0].getBoundingClientRect();
  63:                  return {
  64:                      top: isRoot ? 0 : r.top,
  65:                      left: isRoot ? 0 : r.left,
  66:                      bottom: isRoot ? $element[0].clientHeight : r.bottom,
  67:                      right: isRoot ? $element[0].clientWidth : r.right
  68:                  };
  69:              })()
  70:          };
  71:      };
  72:   
  73:      $.fn.extend({
  74:          scrollintoview: function (options) {
  75:              /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
  76:              /// <param name="options" type="Object">Additional options that can configure scrolling:
  77:              ///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
  78:              ///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
  79:              ///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
  80:              /// </param>
  81:              /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>
  82:   
  83:              options = $.extend({}, settings, options);
  84:              options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both;
  85:   
  86:              var dirStr = "";
  87:              if (options.direction.x === true) dirStr = "horizontal";
  88:              if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical";
  89:   
  90:              var el = this.eq(0);
  91:              var scroller = el.closest(":scrollable(" + dirStr + ")");
  92:   
  93:              // check if there's anything to scroll in the first place
  94:              if (scroller.length > 0)
  95:              {
  96:                  scroller = scroller.eq(0);
  97:   
  98:                  var dim = {
  99:                      e: dimensions(el),
 100:                      s: dimensions(scroller)
 101:                  };
 102:   
 103:                  var rel = {
 104:                      top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top),
 105:                      bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom,
 106:                      left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left),
 107:                      right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right
 108:                  };
 109:   
 110:                  var animOptions = {};
 111:   
 112:                  // vertical scroll
 113:                  if (options.direction.y === true)
 114:                  {
 115:                      if (rel.top < 0)
 116:                      {
 117:                          animOptions.scrollTop = dim.s.scroll.top + rel.top;
 118:                      }
 119:                      else if (rel.top > 0 && rel.bottom < 0)
 120:                      {
 121:                          animOptions.scrollTop = dim.s.scroll.top + Math.min(rel.top, -rel.bottom);
 122:                      }
 123:                  }
 124:   
 125:                  // horizontal scroll
 126:                  if (options.direction.x === true)
 127:                  {
 128:                      if (rel.left < 0)
 129:                      {
 130:                          animOptions.scrollLeft = dim.s.scroll.left + rel.left;
 131:                      }
 132:                      else if (rel.left > 0 && rel.right < 0)
 133:                      {
 134:                          animOptions.scrollLeft = dim.s.scroll.left + Math.min(rel.left, -rel.right);
 135:                      }
 136:                  }
 137:   
 138:                  // scroll if needed
 139:                  if (!$.isEmptyObject(animOptions))
 140:                  {
 141:                      if (rootrx.test(scroller[0].nodeName))
 142:                      {
 143:                          scroller = $("html,body");
 144:                      }
 145:                      scroller
 146:                          .animate(animOptions, options.duration)
 147:                          .eq(0) // we want function to be called just once (ref. "html,body")
 148:                          .queue(function (next) {
 149:                              $.isFunction(options.complete) && options.complete.call(scroller[0]);
 150:                              next();
 151:                          });
 152:                  }
 153:                  else
 154:                  {
 155:                      // when there's nothing to scroll, just call the "complete" function
 156:                      $.isFunction(options.complete) && options.complete.call(scroller[0]);
 157:                  }
 158:              }
 159:   
 160:              // return set back
 161:              return this;
 162:          }
 163:      });
 164:   
 165:      var scrollValue = {
 166:          auto: true,
 167:          scroll: true,
 168:          visible: false,
 169:          hidden: false
 170:      };
 171:   
 172:      $.extend($.expr[":"], {
 173:          scrollable: function (element, index, meta, stack) {
 174:              var direction = converter[typeof (meta[3]) === "string" && meta[3].toLowerCase()] || converter.both;
 175:              var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle);
 176:              var overflow = {
 177:                  x: scrollValue[styles.overflowX.toLowerCase()] || false,
 178:                  y: scrollValue[styles.overflowY.toLowerCase()] || false,
 179:                  isRoot: rootrx.test(element.nodeName)
 180:              };
 181:   
 182:              // check if completely unscrollable (exclude HTML element because it's special)
 183:              if (!overflow.x && !overflow.y && !overflow.isRoot)
 184:              {
 185:                  return false;
 186:              }
 187:   
 188:              var size = {
 189:                  height: {
 190:                      scroll: element.scrollHeight,
 191:                      client: element.clientHeight
 192:                  },
 193:                  width: {
 194:                      scroll: element.scrollWidth,
 195:                      client: element.clientWidth
 196:                  },
 197:                  // check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars
 198:                  scrollableX: function () {
 199:                      return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client;
 200:                  },
 201:                  scrollableY: function () {
 202:                      return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client;
 203:                  }
 204:              };
 205:              return direction.y && size.scrollableY() || direction.x && size.scrollableX();
 206:          }
 207:      });
 208:  })(jQuery);

How it works

First of all this plugin was written to support all three major browsers: Internet Explorer, Firefox and Chrome (or even Safari for that matter, because they are both Webkit browsers). We call this plugin on an element that we'd like to scroll to (or in other words, that we'd like our user to see). As you can see from the Visual Studio Javascript intellisense XML documentation this plugin can be used with an optional options parameter that can define:

  • animation duration/speed (understands same values as jQuery animate function);
  • scrolling direction defined as a string of either vertical, horizontal or both; the first two have a shorthand version with x and y values as well;
  • complete function that will be called when scrolling finishes; this function will be called in the context of the scrolled DOM element (meaning that this will refer to scrolled DOM element);

These two calls represent possible plugin usage: $("tr:eq(10)").scrollintoview(); or $("h4:last").scrollintoview({ duration: "slow", direction: "y", complete: function(){ alert("Done"); } });

When jQuery selector returns a set of multiple elements, this plugin will display the first element in this set. What it actually does is that it tries to find element's nearest scrollable (see below for the additional gem) ancestor element and scrolls it so the element gets displayed within viewable area. Complex scenarios with nested scrollable elements aren't supported because you're probably already hitting usability issues with your user interface.

Additional gem

If you looked closely there's one more thing included in this plugin. And that's the :scrollable jQuery selector that makes it possible to find all scrollable elements. It has two usage scenarios:

  • parameterless as in $(":scrollable") which would return all elements on the page that are scrollable (that can scroll more content than they display) in either direction;
  • parametrised as in $(":scrollable(vertical)") which would do a similar thing except that it will only look for vertically scrollable elements (can be horizontally scrollable as well but selector will ignore this fact);

Selector parameter as seen in the second example can be any of the following self explanatory values (similar to plugin's except they're not specified as strings):

  • horizontal or x
  • vertical or y
  • both

Issues/questions/suggestions/ideas?

If you need any further assistance regarding this plugin or selector, let me know. Write a comment. If you intend to use this plugin you're also welcome to provide some information how it turned out for you. I'd be happy if it helps you with your projects. Happy coding fellows.

75 comments:

  1. What is the license of this plugin? :)

    ReplyDelete
    Replies
    1. Lines 8-9 of plugin:
      "Licensed under the terms of the MIT license http://www.opensource.org/licenses/mit-license.php"

      Delete
  2. @joril: As you may see I've updated plugin source that includes license reference in the top comment section. Refer to MIT license link for further information of this plugin usage.

    ReplyDelete
  3. Looks really good, but where is the download link and why cant I copy+paste from the code display? Because the linenumbers are copied as well! So basically I cannot use even if I really want to.

    ReplyDelete
  4. @Anonymous: You're right. There's no link to copy the code in one go. I mean code only.

    I don't know if you're aware of this, but multi-line editing is available in many text editors (including Visual Studio and Notepad++). If you hold ALT key and drag your mouse vertically along the same column (so not selecting any text), you will en up with a multi-line cursor and you can edit all these lines at once.

    So. Steps:
    1. Copy the code from my blog.
    2. Paste it in either Visual Studio or Notepad++ or similar
    3. Hold ALT and drag a cursor at the beginning of all lines.
    4. press DELETE key as many times as needed to remove all line numbers.

    That's it. It's a matter of seconds really. If you'd like you can also remove every second line, because it copies it as empty line.

    Would this help?

    ReplyDelete
  5. @Anonymous: I've added a link at the top of each code block that toggles line numbers so it should be easier to copy code from my blog posts.

    Hope this helps.

    ReplyDelete
  6. Yes, that definitely helped, I didn't know the alt+drag trick. Im sure Im not the only one though, so I believe you have now taken all steps necessary. So now. Right now. Im trying the plugin.

    ReplyDelete
  7. @Anonymous: Glad I could help. You're welcome to tell me how it worked. I'm using it on my Sharepoint 2010 website and it works as expected. But it's true that Sharepoint 2010 default master doesn't use scrollable document/window but rather scrollable container that keeps title bar always on top.

    ReplyDelete
  8. As I anticipated, it worked like a charm! I was using the native jscript scrollintoview() but it made my whole page jump in IE and FF. Now everything is melted chocolate on icecream.

    ReplyDelete
  9. I'm a newbie with JQuery Plugins, please could you instruct me how can i use this code? I already copied it as a new file and referenced it in my Master Page as Javascript file (as did with jquery itself), but when I invocate scrollintoview it says method invalid. Please Help me.

    ReplyDelete
  10. @Anonymous: Example usage is described in short in How it works section of this blog post. If you tried executing it like:

    $(/* some selector expression */).scrollintoview();

    and it didn't work, then I suggest you check your code with Firebug and see how files get loaded and of course when do you execute your code. Is it on page ready or while loading or something similar. Try to analyse your code and what executes when. Firebug will be very helpful.

    Can you use Jquery at all (if you've loaded it in the same way)?

    The most I can help you is to click this Google search query and read about jQuery integration with asp.net WebForms (if that's what you're using)

    ReplyDelete
  11. Any reason why this wouldn't work?


    $('.contact').click(function() {
    $('#contact').slideToggle('slow', function() {
    $('#contact').scrollintoview();
    });
    return false;
    });

    ReplyDelete
  12. @Anonymous: "...wouldn't work" as what? Can you elaborate a bit more on this?

    What seems to not work as expected?
    1. You don't see sliding effect that you run first?
    2. It doesn't scroll into view afterwards?
    3. You get an error?
    4. Not working in a particular browser?
    5. Which element is scrollable in your case? Document itself or some arbitrary sub element? Maybe an IFRAME or element in its particular document?

    So many questions that need answers. :)

    ReplyDelete
  13. @Anonymous: If you were trying to scroll your whole window/document I understand you had problems. I've changed the code so it should work even in this case as expected and in all major browsers (IE, FF and CH).

    Just make sure you're using plugin version 1.5 (11th March 2011).

    But if you had any other issues you'll have to provide more info.

    ReplyDelete
  14. This is great!

    How do I have it scroll to the selected tr with a class "selectedTR" in a div with overflow and have it put the selected item at the top of the div with the overflow set. It scrolls to it but puts it at the bottom of the div with the overflow.

    Hope that makes sense. :)

    Thanks,

    Aaron

    ReplyDelete
  15. @Aaron: The purpose of this plugin is to scroll a certain element into view (as close as possible) and not to scroll it to the top of the scrollable element (it would be called scrolltotop then). Hence below the view elements will be scrolled to the bottom of scrollable ancestor, and elements above the view will be scrolled to the top of the scrollable ancestor. And those anywhere in the view won't scroll at all.

    Maybe you should consider highlighting the element as well. In this case it doesn't really matter where scrolling positions it because you will visually highlight it so it will be apparent which element you're trying your user to focus on.

    The other problem with scrolling to top is that it's not consistent. When scrollable ancestor can't be scrolled that much your element will still be displayed at the bottom (if it's the last visual child of the scrollable ancestor for instance). Highlighting is therefore still important.

    If none of these arguments holds water for your case I suppose it's probably better that you use a different jQuery plugin that allows scrolling as per your requirements. scrollTo() plugin.

    http://flesler.blogspot.com/2007/10/jqueryscrollto.html

    and his demos:

    http://demos.flesler.com/jquery/scrollTo/

    In my case I wasn't able to use this particular plugin because my scrollable element is not known during development hence my plugin automatically finds the closest scrollable ancestor. Just to clear things a bit.

    Hope this helps...

    ReplyDelete
  16. Hey Robert thanks for the reply and makes perfect sense!

    Thanks so much!

    ReplyDelete
  17. Works great. However, I did have to hack the code a bit so that the complete function is always called. I display a toolbar after I scroll the element into view and I have to wait for the scrolling to complete before positioning the toolbar so I show the toolbar in the complete function, but the complete function wasn't being called when there was nothing to scroll.

    ReplyDelete
  18. @Anonymous: You're completely correct about the complete() function. It should be called regardless of scrolling. And since it's called in context of the scrollable element it won't be called when things can't be scrolled in the first place.

    Call to complete() will therefore execute only when there's a scrollable ancestor (otherwise we won't have context of the call) and after the element is in view (whether after scrolling to it or immediately).

    Thanks for pointing out this glitch.

    ReplyDelete
  19. @Luke: Can you elaborate on your offset?

    ReplyDelete
  20. I'm guessing Luke means a situation where you toggle a DIV, then you want it to scroll past the bottom of that DIV by X number of pixels. I could use such a function, but was able to fake it by adding some extra padding in the CSS.

    ReplyDelete
  21. @Eric: If that's what he was after (I though he was asking about providing offset settings in any direction, but wasn't really sure) then your hack is probably a nice simple and more predictable solution because you can always assure that certain element will be able to be scrolled a bit higher. The problem would be with the very last element that can in that situation hit the scroll limit. That by itself is a bad user experience to me because users can't always expect the element at the very same position. And that's confusing and non-uniform.

    Even though it would still be possible to implement but would also be a bit tricky what to do when element is visible but offset from visible view port is a bit smaller than the offset provided along with the call?

    Basically this is not so much a problem of code but rather experience and predictability.

    ReplyDelete
  22. Hey Robert,

    Thanks for posting this, it might just be what I am looking for!

    I have used the script to create a .js file and called it 'jqueryscroll.js', which gets called into the head (under the jQuery 1.4+ call).

    I then have this action in the footer:

    jQuery(document).ready(function() {

    jQuery('#').scrollintoview();

    });


    I must be missing something though as when I try to get the script to fire using a conventional anchor within the body of the doc, nothing happens. Have I missed something entirely?

    Cheers and thanks in advance.

    Gerard

    ReplyDelete
  23. @Gerard:: The problem is that you haven't used a valid jQuery selector to select an element on your page. $("#") barely throws a syntax error because jQuery doesn't recognise this selector.

    You should therefore use a valid selector that jQuery can return an element (or a set of them):

    $("some_valid_selector").scrollintoview();

    and everything should work as expected.

    ReplyDelete
  24. I can't thank you enough.

    You turned a productive workday into an day with an enjoyable progress. It worked, but it had rough edges. You made it smooth.

    Lots of kudos from Hamburg/Germany!

    ReplyDelete
  25. Erik Eckhardt14 July, 2011 03:21

    Works for me in IE 7 but not in FF 5.0. Any thoughts?

    ReplyDelete
  26. @Erik Eckhardt: I'm also using the latest FF5 (says there're no newer updates) and it works fine. It's true I'm using jQuery 1.4.4 but functionality that's used within plugin hasn't changed in later jQuery versions. Without providing any insight into your code It's hard to tell why things aren't working for you...

    Maybe you're using selectors that don't return elements anymore or something. Hard to tell.

    ReplyDelete
  27. Great plugin, I'm using it for my Chrome extension for Google+: http://huyz.us/google-plus-me/

    ReplyDelete
  28. @huyz: Thanks for letting me know. This is really great news. I'm glad you're making such a good use of it. And this way it will gain some more users as well. :)

    ReplyDelete
  29. How about using any easing? Would love to smooth out the transitions.

    ReplyDelete
  30. @Anonymous: Good suggestion. Will include that.

    ReplyDelete
  31. Simple comment system with form submission via ajax. Comment html is built and displayed at the bottom of existing comments in a div #mainResponse.

    success: function(responseText){
    if (responseText.indexOf("Notice:") !== 0) {
    $.unblockUI();
    $('#mainForm').toggle();
    $("#mainResponse").html(responseText);
    $("#mainResponse").scrollintoview({ duration: 2500, direction: "vertical" });
    } else {
    $("div.blockMsg").text(responseText);
    setTimeout($.unblockUI, 1500);
    }
    }

    Am I doing this wrong? Seems pretty self explanatory but I'm not getting any scroll at all. I'm using Jquery 1.4.2, FireFox 6.0.

    ReplyDelete
  32. @Anonymous: I'm sorry for such a long response delay that's due to my past vacation.

    Apart from your code not being optimised it seems you're doing it all right. If your #mainResponse div isn't visible it should be scrolled to when ancestor is scrollable (with scrollbars).

    Could you provide a JSFiddle of your problematic code so I could take a look?

    ReplyDelete
  33. Hi Robert, I found your plugin most useful, especially as it handles both scrolling at the document level and inside an arbitrary element. But what if you want to scroll multiple elements into view? I found out your plugin could easily be modified to handle this case, by just modifying the dimensions() function and adding one small extra function. See new code here: http://regularwebmaster.blogspot.com/2011/11/scrollintoview-revised.html and please feel free to do what you want with it.

    ReplyDelete
  34. @Martin Boström: I'm glad you found my plugin useful and extended it even further to your needs. That's great, but I think your use scenario is rather seldom situation hence I decided to keep my plugin as is.

    Let me explain a bit why. If my plugin would work with several elements it would be totally unreliable. Your code completely relies on you to provide elements that are within a common ancestor. A general plugin should be more robust that that.

    Anyway. Thanks again and I hope you keep your initial satisfaction.

    ReplyDelete
  35. Awesome plugin!
    Tried a ton of snippets from Stackoverflow but none would work correctly in my case.

    Just dropped yours in and it works out of the box like a champ.

    Thanks a lot, Robert!

    ReplyDelete
  36. @Anonymous: Thank you so very much! I'm always glad to get such response from other fellow developers.
    Keep following my blog and maybe there'll be other useful things as well...

    ReplyDelete
  37. Absolutely. :)

    ReplyDelete
  38. Hi, can you help me make the alignment on the top not bottom? Like the native version of scrollIntoView.

    ReplyDelete
  39. @Luke: my plugin's functionality doesn't support scrolling elements to top. It rather scrolls either to top or bottom depending where the element is before you want it to be visible.

    For always scrolling elements to top (which is unreliable thus not being part of my plugin) I suggest you rather use Ariel Fleisler's scrollTo plugin or use jQery's scrollTop function (along with animate).

    ReplyDelete
  40. Great plugin, just what I was looking for ;)

    Except for one thing - it would be indeed nice to have an offset/margin setting. I read the earlier comment about padding "hack", but unfortunately that's not a solution for me. In my situation I have a user bar position: fixed at the top of the browser window (just like facebook does, or a lot of other websites these days). And I also have a vertical list of items on which you can drop other elements. When you drop something on that list element its position may change (they're always sorted) and go out of view. I want to scroll to that new position, but the list element ends up hidden behind the top user bar. A few pixels of top offset would fix all my problems :P

    ReplyDelete
  41. thank you for sharing such an informative blog.. it is very valuable blog..it will be helpful in my business.. Air Conditioning Baltimore .. we offer excellent air conditioning services in Baltimore..

    ReplyDelete
  42. Hi Robert nice article - a colleague of mine implemented this a few months back (now converted to coffee script) for a photo gallery. I now need to change the image gallery slightly so that the scroll links only appear when they are needed i.e. if there are only a few items in the gallery no scroll links appear as they are not needed. I thought this was a standard option with the scrollintoview() plugin but I can't find any documentation on how to do this - do you know of any resources that explain how this can be done? Many thanks.

    ReplyDelete
    Replies
    1. Hi. Along with my plugin there's also a selector filter :scrollable that you can use in your case.

      When you page loads (or when you load the gallery images) you should check if your gallery container is scrollable and display those links only when it is:

      $("#yourCcntainerId:scrollable").length && $(".galleryLinks").show();

      This should do the trick for you.

      Delete
  43. Your plugin is really great, it saves my life!

    ReplyDelete
  44. It appears to be broken with jQuery 1.8. :(

    Uncaught TypeError: Cannot read property '3' of undefined jquery.scrollintoview.min.js:174

    ReplyDelete
    Replies
    1. I created a ticket: https://github.com/litera/jquery-scrollintoview/issues/10
      More info: http://bugs.jquery.com/ticket/12201

      Delete
    2. Thanks for this Yann. I'll look into it and see what may be wrong.

      Delete
  45. Nice plugin.
    Worked when the jQuery scrollIntoView plugin did not.
    Please fix for use with jQuery 1.8.x

    ReplyDelete
    Replies
    1. This has been on ma priority list now for 2 weeks. Code will be updated to support jQuery latest version.
      Thank you.

      Delete
  46. Hi.

    More specifically, the problem comes from the fact that your :scrollable pseudo-class filter has the signature "function (element, index, meta, stack)" and jQuery (1.8) wants a "function (element, context)" (from inside the matcherFromGroupMatchers function).

    It's easy to patch your sources for specific needs, bypassing the use of :scrollable, so many thanks anyway!

    Regards,

    Claude

    ReplyDelete
  47. Hello Robert,
    Thanks that useful plugin..
    My problem: i want for object's closest first scrollable parent. but page(iframe) scrollbar move :(

    ReplyDelete
    Replies
    1. Plugin is designed to find closest scrollable ancestor that also has excess content (so it's actually phisically scrollable). Some element may have scrollbars but not enough content to actually be scrollable. Such elements are skipped and plugin progresses further up DOM tree.

      Delete
  48. I modified your code to run the "complete" function even if there was no scrollable element found. Thanks for this useful jQuery plugin.

    I also like only one var declaration per function, so I changed that, too. And one more tweak you may or may not like:

    dirStr = [
    "",
    "horizontal",
    "vertical",
    "both"
    ][options.direction.x * 1 + options.direction.y * 2];

    I sure wish I could style that code a bit...

    ReplyDelete
  49. Hi Robert,

    I have implemented this scrollintoview function on my application. It works in FF 13, Chrome 25, but not in IE9. In IE9, it simply wouldn't scroll into the element at all. I'm using JQuery 1.7.1. My code looks like this:

    jQuery(qaNode).scrollintoview({
    duration: 600,
    direction: "both",
    complete: function() {
    dojox.fx.highlight({
    color: "#E8F6FB",
    node: targetNode1,
    duration: 600
    }).play(250);

    }
    });

    Many thanks.

    ReplyDelete
  50. Works great!

    One problem: With no explicit DOCTYPE it does nothing at all.
    No error reported, but no scroll happens.

    If I add the DOCTYPE from the demo.html, it works fine.
    If I add a DOCTYPE of simply <!DOCTYPE html>, it works fine.

    I have a large app mostly finished and had never specified a DOCTYPE,
    so I'm leery of adding one now, unless I take the time to re-test
    all functionality on all device/OS/browser combinations.

    Any way to get it to work w/o adding a DOCTYPE?

    Thanks!
    --Fred

    ReplyDelete
  51. This is great!

    Any word on incorporating easing?

    Cheers

    ReplyDelete
  52. Hi again how i can disable the animation.??

    ReplyDelete
  53. Is there an easy way to update this plugin to bring the scrolled-to element to the center of the viewport / parent? Instead of simply bringing it "within view"? That would be great from a focusing perspective when using for both x and y.

    ReplyDelete
    Replies
    1. There're problems related to that. Let me point out two perfectly viable extremes: elements that are rendered at the top of the page that fill up to top half of the screen and elements that are rendered at the bottom of the page that fill the bottom half of the screen when page is completely scrolled down. All of these elements would fail to scroll into the middle of the screen. This would make this plugin inconsistent and such flaw would be regarded as a bug.

      The best thing you can do is to keep elements get scrolled into view and when scrolling finishes, you visually highlight them in whatever way seems appropriate.

      Delete
  54. Hi Robert....

    This looks exactly what I want but I cannot get it to work...

    Simple HTML is

    DIV
    TABLE
    TR
    TD
    TD
    TD
    TD
    TD ID="xx"

    The "xx" TD is hidden by "overflow-x: hidden" on the DIV and want to nicely scroll in... stepping your code gets to "if (scroller.length > 0) {" but .length = 0 so not executing

    Thanks

    ReplyDelete
    Replies
    1. Can you plese create a jsfilddle (or similar) so it will be easier to analyse?

      Delete
  55. Great solution! Works like a charm. I'm using jQuery 1.11.1 on latest Chrome, FF and IE. I tested scrolling table row into view.

    Only issue I have right now is the speed, I would like to speed it up somehow. Right now is user holds keyup/down there is some serious lagging after scrolling a couple of rows. I tried:
    $("#" + rowid).scrollintoview({ duration: "fast" });
    but that seems to be default.
    I'll take a look at you code to see if I can speed it up by adding an option or animating faster?
    If you know how, let me know, else I'll give it a go.
    This is very cool indeed.
    Hans

    ReplyDelete
  56. $("#" + rowid).scrollintoview({ duration: 10 });
    will scroll very fast. :-)
    I should have checked the jQuery documentation before posting.
    Thanks again.

    ReplyDelete
    Replies
    1. For the same purpose I added a direct link to it. And as I see you've found it yourself already.

      Delete
  57. Sorry I'm very new to web dev, here is the site I'm working on: http://nwcdnetwork.businesscatalyst.com/ .I wanted to add a stop() or delay() method to scrollintoview so when the user scrolls over and out of states it doesn't cause the list to erratically move up and down till the user clicks on a state or stops moving long enough for the animation que to catch up. Some of the things I've tried are commented out in the main.js file around line 80. Any help is appreciated.

    ReplyDelete
    Replies
    1. I'm sorry for seeing this so late, but your website isn't available any more. If you point me to a different URL I'll take a look at it and see what you're trying to do. That is if you're still having issues of course.

      Delete
  58. Any chance to get this plugin posted on Bower repo? Would be very nice!

    ReplyDelete
  59. Works like a charm, and has saved me some precious coding time. :) Thanks for publishing and writing the blog post.

    ReplyDelete
  60. Thanks for sharing this!
    Unfortunately, the scroll of a root element (html/body/document) does not work any more, once the page has been scrolled down a bit. See https://codepen.io/leanix/pen/XqXeOd. For some reason, your plugin cancels the scroll animation assuming that user has initiated a scroll himself.

    ReplyDelete

This is a fully moderated comments section. All spam comments are marked as spam without exception. Don't even try.

Note: only a member of this blog may post a comment.