JS that scrolls an element in a view, taking into account a possible scrollable and positioned parent

I was looking for a function that will scroll a given element in a view using clever behavior:

  • if the element is a descendant of a scrollable element, the scroll is the ancestor, not the body.
  • If the element is a descendant of a positioned element, the body will not scroll.

I didn’t find a suitable function, so I made it and wanted an expert opinion. Check out plunkr http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview . There are problems with animated scrolling in FF, so please use Chrome to test the logic.

To illustrate what I'm looking for - here is the first update that came to mind - if we reach an element that can scroll, let's call it SC (Scroll Parent), we must not only scroll SC to make the target visible inside it, but also recursively scroll the SC by itself, as it can be outside of the currently visible pages. Here is the plunkr update http://plnkr.co/edit/DNGWLh5cH1Cr1coZbwpa?p=preview (fix for FF scrolling issue also applies).

And here is the function code

  function scrollTo(target){
  //Position delta is used for scrollable elements other than BODY
  var combinedPositionDelta = 0;  
  var previousParent = $(target);
  var parent = $(target).parent();

  while(parent){

    combinedPositionDelta += previousParent.position().top - parent.position().top;

    //If we reached body
    if(parent.prop("tagName").toUpperCase() == "BODY"){
      scrollBody(target.offset().top);
      break;
    }

    //if we reached an element that can scroll
    if(parent[0].scrollHeight > parent.outerHeight()){
      scrollElementByDelta(parent,combinedPositionDelta);
      //Recursively scroll parent into view, since it itself might not be visible
      scrollTo(parent);
      break;
    }

    //if we reached a apositioned element - break
    if(parent.css('position').toUpperCase() != 'STATIC'){
      console.log("Stopping due to positioned parent " + parent[0].outerHTML);
      break;
    }

    previousParent = parent;
    parent = parent.parent();

  }

}

var offsetSkin = 20;
function scrollElementByDelta(element,offsetDelta){
   $(element).animate({
        scrollTop: element.scrollTop() + (offsetDelta - offsetSkin) 
    }, 1000);
}

function scrollBody(offset){
     $('body,html').animate({
        scrollTop: offset - offsetSkin
    }, 1000);
}

      

+3


source to share


2 answers


Well, I use this one, which works very well for me:

function scrollIntoView (element, alignTop) {
    var document = element.ownerDocument;
    var origin = element, originRect = origin.getBoundingClientRect();
    var hasScroll = false;
    var documentScroll = this.getDocumentScrollElement(document);

    while (element) {

        if (element == document.body) {
            element = documentScroll;
        } else {
            element = element.parentNode;
        }

        if (element) {
            var hasScrollbar = (!element.clientHeight) ? false : element.scrollHeight > element.clientHeight;


            if (!hasScrollbar) {
                if (element == documentScroll) {
                    element = null;
                }
                continue;
            }

            var rects;
            if (element == documentScroll) {
                rects = {
                    left : 0,
                    top : 0
                };
            } else {
                rects = element.getBoundingClientRect();
            }

            // check that elementRect is in rects
            var deltaLeft = originRect.left - (rects.left + (parseInt(element.style.borderLeftWidth, 10) | 0));
            var deltaRight = originRect.right
                            - (rects.left + element.clientWidth + (parseInt(element.style.borderLeftWidth, 10) | 0));
            var deltaTop = originRect.top - (rects.top + (parseInt(element.style.borderTopWidth, 10) | 0));
            var deltaBottom = originRect.bottom
                            - (rects.top + element.clientHeight + (parseInt(element.style.borderTopWidth, 10) | 0));

            // adjust display depending on deltas
            if (deltaLeft < 0) {
                element.scrollLeft += deltaLeft;
            } else if (deltaRight > 0) {
                element.scrollLeft += deltaRight;
            }

            if (alignTop === true && !hasScroll) {
                element.scrollTop += deltaTop;
            } else if (alignTop === false && !hasScroll) {
                element.scrollTop += deltaBottom;
            } else {
                if (deltaTop < 0) {
                    element.scrollTop += deltaTop;
                } else if (deltaBottom > 0) {
                    element.scrollTop += deltaBottom;
                }
            }

            if (element == documentScroll) {
                element = null;
            } else {
                // readjust element position after scrolls, and check if vertical scroll has changed.
                // this is required to perform only one alignment
                var nextRect = origin.getBoundingClientRect();
                if (nextRect.top != originRect.top) {
                    hasScroll = true;
                }
                originRect = nextRect;
            }
        }
    }
}

      



Hope this helps.

+4


source


If you don't mind getting involved with jQuery, scrollTo is your best bet . It handles most needs and gives a very sophisticated sleek shape. Hope it helps.



0


source







All Articles