Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ScrollTop position not work on mobile Safari?

I have a feature on my website where the logo scrolls vertically depending on the users position on the site.

You can see it working on Chrome here

However it does not work on Safari, which includes mobile and tablet.

The scroll position does not seem to change at all in the console.

// logo positioning  

let logos, logoHeight, barTopMargin;
let viewportHeight;

window.addEventListener('load', init);
window.addEventListener('resize', setSizes);
document.addEventListener('scroll', update);


function init(lockUpdate) {
    logos = document.querySelectorAll('.scroll-text');
    setSizes(lockUpdate);
}

function update() {
    // ensure initialization and prevent recursive call
    if (!logos) init(true);
    //*************************************************

    /**************************************************
        THIS LINE MUST BE HERE.
    **************************************************/
    let maxScrollDist  = document.documentElement.scrollHeight - viewportHeight;
    //*************************************************

    let currentScrollPos = document.documentElement.scrollTop;
    let newTop;

    let middle = currentScrollPos + viewportHeight/2;
    let middleY = maxScrollDist/2;

    if (middle >= (maxScrollDist+viewportHeight)/2) {
        let p = (middleY - Math.floor(middle - (maxScrollDist+viewportHeight)/2))*100/middleY;

        newTop = viewportHeight/2 - logoHeight/2;
        newTop += (100-p)*(viewportHeight/2)/100;
        newTop -= (100-p)*(barTopMargin +logoHeight/2)/100;
        newTop = Math.max(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
    } else {
        let p = (middleY - Math.floor(-middle + (maxScrollDist+viewportHeight)/2))*100/middleY;
        newTop = barTopMargin*(100-p)/100+(viewportHeight/2 - (logoHeight/2)*p/100 )*p/100;
        newTop = Math.min(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
    }

    logos.forEach(function(el) {
        el.style.top = newTop + "px";
    });
}

function setSizes(lockUpdate) {
    logoHeight     = logos[0].offsetHeight;
    barTopMargin   = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
    viewportHeight = window.innerHeight;
    if (lockUpdate === true) return;
    update();
}
like image 715
Mr Toad Avatar asked Nov 04 '19 12:11

Mr Toad


People also ask

Does safari support smooth scroll?

Unfortunately, the smooth option is not supported by Safari (Element.

Why is scrollTop always 0?

An element's scrollTop is a form of distance measurement regarding an element's top to its topmost visible content. When an element content does not generate a vertical scrollbar, then its scrollTop value defaults to 0." Since the content of inner doesn't generate a scrollbar, scrollTop is 0 .

How does scrollTop work?

scrollTop property gets or sets the number of pixels that an element's content is scrolled vertically. An element's scrollTop value is a measurement of the distance from the element's top to its topmost visible content. When an element's content does not generate a vertical scrollbar, then its scrollTop value is 0 .

How do you control the scroll position?

To get or set the scroll position of an element, you follow these steps: First, select the element using the selecting methods such as querySelector() . Second, access the scroll position of the element via the scrollLeft and scrollTop properties.


1 Answers

The reason behind this behavior is laid inside the difference in scrolling implementation between the browsers. For example Chrome calculates page scrolling based on the <html>, while Safari does the same on the <body>.

Chrome:

chrome scrollingElement

Safari:

safari scrollingElement

Considering this info it would reasonable to assume that in Safari document.documentElement is completely unaware of page global scrolling value.

And to fix this issue you could define a helper func that uses document.scrollingElement with fallback to getBoundingClientRect on document.documentElement:

function getScrollingElement() {
  if (document.scrollingElement) {
    return document.scrollingElement;
  }

  const docElement = document.documentElement;
  const docElementRect = docElement.getBoundingClientRect();

  return {
    scrollHeight: Math.ceil(docElementRect.height),
    scrollTop: Math.abs(docElementRect.top)
  }
}

and use it in your update func:

function update() {
  // ensure initialization and prevent recursive call
  if (!logos) init(true);
  //*************************************************

  /**************************************************
      THIS LINE MUST BE HERE.
  **************************************************/
  let maxScrollDist = getScrollingElement().scrollHeight - viewportHeight;
  //*************************************************

  let currentScrollPos = getScrollingElement().scrollTop;

  // ...
}

function getScrollingElement() {
  // ...
}

Full code:

// logo positioning  

let logos, logoHeight, barTopMargin;
let viewportHeight;

window.addEventListener('load', init);
window.addEventListener('resize', setSizes);
document.addEventListener('scroll', update);


function init(lockUpdate) {
    logos = document.querySelectorAll('.scroll-text');
    setSizes(lockUpdate);
}

function update() {
    // ensure initialization and prevent recursive call
    if (!logos) init(true);
    //*************************************************

    /**************************************************
        THIS LINE MUST BE HERE.
    **************************************************/
    let maxScrollDist  = getScrollingElement().scrollHeight - viewportHeight;
    //*************************************************

    let currentScrollPos = getScrollingElement().scrollTop;
    let newTop;

    let middle = currentScrollPos + viewportHeight/2;
    let middleY = maxScrollDist/2;

    if (middle >= (maxScrollDist+viewportHeight)/2) {
        let p = (middleY - Math.floor(middle - (maxScrollDist+viewportHeight)/2))*100/middleY;

        newTop = viewportHeight/2 - logoHeight/2;
        newTop += (100-p)*(viewportHeight/2)/100;
        newTop -= (100-p)*(barTopMargin +logoHeight/2)/100;
        newTop = Math.max(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
    } else {
        let p = (middleY - Math.floor(-middle + (maxScrollDist+viewportHeight)/2))*100/middleY;
        newTop = barTopMargin*(100-p)/100+(viewportHeight/2 - (logoHeight/2)*p/100 )*p/100;
        newTop = Math.min(newTop, viewportHeight/2 - logoHeight/2); /*fix*/
    }

    logos.forEach(function(el) {
        el.style.top = newTop + "px";
    });
}

function getScrollingElement() {
  if (document.scrollingElement) {
    return document.scrollingElement;
  }

  const docElement = document.documentElement;
  const docElementRect = docElement.getBoundingClientRect();

  return {
    scrollHeight: Math.ceil(docElementRect.height),
    scrollTop: Math.abs(docElementRect.top)
  }
}

function setSizes(lockUpdate) {
    logoHeight     = logos[0].offsetHeight;
    barTopMargin   = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
    viewportHeight = window.innerHeight;
    if (lockUpdate === true) return;
    update();
}

Hope this helps.

like image 81
streletss Avatar answered Sep 18 '22 11:09

streletss