iscroll icon indicating copy to clipboard operation
iscroll copied to clipboard

Switching focus breaks scroll position

Open Cinamonas opened this issue 11 years ago • 7 comments

Hi,

Switching focus from one input field to another, which is outside viewable area, breaks scroll position.

This fiddle illustrates the issue: focus first field (Foo) and switch focus to last field (Qux) by pressing tab key three times. Observe scrollbar while doing it.

Same happens if you focus programatically. See another fiddle.

Is this known behaviour?

Cinamonas avatar Jan 29 '14 15:01 Cinamonas

Is this iOS specific or general behavior that you are describing?

davidpfahler avatar Feb 11 '14 10:02 davidpfahler

General behaviour. Just checked fiddles in OS X Chrome and Firefox -- both suffer from it.

Cinamonas avatar Feb 11 '14 12:02 Cinamonas

Anyone happen to solve this or made workaround? This stuff really hard to handle without native iScroll solution ...

vkovalskiy avatar Nov 09 '14 09:11 vkovalskiy

Here's the workaround that I found. Use at your own risk:


// Listen to tab events.  If we tab we need to look to see if we "natively" scrolled, in which case we need to
// reset the native scroll and use iscroll to restore our position.
element.on('keyup', function(e) { 
    var keyCode = e.keyCode || e.which; 
    if (keyCode == 9) { 
        setTimeout(function() {
            var oldScrollY = element.scrollTop(), oldScrollX = element.scrollLeft();

            if(oldScrollY || oldScrollX) {
                element.scrollTop(0);
                element.scrollLeft(0);
                scroller.scrollBy(-oldScrollX, -oldScrollY);
            }
        }, 0);
    }
});

physicsrob avatar Jun 25 '15 20:06 physicsrob

Thanks @physicsrob. This works when moving forward with the focus. But when you move backward using the SHIFT key, it doesn't work.

asiby avatar Jan 24 '18 21:01 asiby

I propose using scrollToElement() combined with jQuery focus event ... which worked for me.

Some context relative to my case:

  1. The class .view-id-timeline_2 is on the DIV that wraps around my iScrollable area.
  2. data-center-on-timeline is an attribute that I just happened to have on each item of my content being scrolled. This could have been an id attribute or anything easy to match using a selector.
  3. myScroll is my iScroll instance.
$('.view-id-timeline_2 a').focus(function(e) {
    var $topParent = $(this).parents("[data-center-on-timeline]");
    myScroll.scrollToElement('[data-center-on-timeline="' + $topParent.attr('data-center-on-timeline') + '"]', null, true, true);
});

This solution works beautifully whether you are tabbing forward or backward.

asiby avatar Jan 24 '18 21:01 asiby

After some time searching for the culprit, I believe the reason why this is happening is the interference of browser scrolling method in iscroll's functionality. Switching focus (to an not yet visible element inside iscroll wrapper) triggers the wrapper to scroll to that element without using the way the scrolling is actually performed by iscroll, breaking the iScrollVerticalScrollbar positioning (the scrollbar exists, but is scrolled up about 2 miles). If we manually scroll at the bottom of the wrapper, the scrollbar comes into view and the positioning is repaired.

My workaround below (which I tested extensively and which saved my mental health) is based on modifying the iscroll.js library, adding an event into the _initEvents function that repairs this nasty bug, by forcing the rebuilding of scrollbars' positioning. Not really brain surgery, but quite effective. And yes, it handles any scrolling action performed outside normal iscroll ... scrolling, weather triggered by pressing TAB or Shift+TAB or whatever.

_initEvents: function (remove) {
  var eventType = remove ? utils.removeEvent : utils.addEvent,
      target = this.options.bindToWrapper ? this.wrapper : window;

  eventType(window, 'orientationchange', this);
  eventType(window, 'resize', this);

  if ( this.options.click ) {
    eventType(this.wrapper, 'click', this, true);
  }

  if ( !this.options.disableMouse ) {
    eventType(this.wrapper, 'mousedown', this);
    eventType(target, 'mousemove', this);
    eventType(target, 'mousecancel', this);
    eventType(target, 'mouseup', this);
  }

  if ( utils.hasPointer && !this.options.disablePointer ) {
    eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this);
    eventType(target, utils.prefixPointerEvent('pointermove'), this);
    eventType(target, utils.prefixPointerEvent('pointercancel'), this);
    eventType(target, utils.prefixPointerEvent('pointerup'), this);
  }

  if ( utils.hasTouch && !this.options.disableTouch ) {
    eventType(this.wrapper, 'touchstart', this);
    eventType(target, 'touchmove', this);
    eventType(target, 'touchcancel', this);
    eventType(target, 'touchend', this);
  }

  eventType(this.scroller, 'transitionend', this);
  eventType(this.scroller, 'webkitTransitionEnd', this);
  eventType(this.scroller, 'oTransitionEnd', this);
  eventType(this.scroller, 'MSTransitionEnd', this);

  /* until this point the function is the original */
  /* START WORKAROUND */
  function _isDescendant(parent, child) {
    var node = child.parentNode;
    while (node != null) {
      if (node == parent) return true;
      node = node.parentNode;
    }
    return false;
  }
  var holder = this;
  function _preventScrollBug() {
    var element = document.activeElement;
    if (!_isDescendant(holder.wrapper, element)) return false;
    holder.scrollTo(holder.maxScrollX, holder.maxScrollY);
    setTimeout(function() { 
      holder.scrollTo(0, 0);
    }, 1)
    setTimeout(function() {
      if (element != null && element !== document.body) holder.scrollToElement(element, null, null, true);
    }, 2);
  }
  function _scrollTab(e) {
    var keyCode = e.keyCode || e.which;
    if (keyCode == 9) {
      var element = document.activeElement;
      if (!_isDescendant(holder.wrapper, element)) return false;
      setTimeout(function() {
        if (element != null && element !== document.body) holder.scrollToElement(element, null, null, true);
      }, 2);
    }
  }

  eventType(this.wrapper, 'scroll', _preventScrollBug);
  eventType(this.wrapper, 'keyup', _scrollTab, true);
  /* END WORKAROUND */

},

iwrbr avatar Sep 09 '18 00:09 iwrbr