5

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();
}
n1stre
  • 5,856
  • 4
  • 20
  • 41
Mr Toad
  • 202
  • 2
  • 12
  • 41

1 Answers1

5

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.

n1stre
  • 5,856
  • 4
  • 20
  • 41
  • Hi there - thank you very much for your time. I have tried inserting this both immediately after: `let currentScrollPos = getScrollingElement().scrollTop;` And in between the two sets of functions but it does not work. Sorry if it is obvious, but I don't understand where exactly to insert this in my current code. Thanks – Mr Toad Nov 20 '19 at 12:46
  • just tested it out. works fine if you simply define `function getScrollingElement` right after your `function update`. updated an answer to be more clear – n1stre Nov 20 '19 at 13:11
  • This works perfectly. I have awarded you the bounty - thank you very much! – Mr Toad Nov 20 '19 at 14:28