11

I am using clipping paths to change my logo colour base on the background colour.

In addition to this the logo scrolls from top to bottom based on the users vertical position on the page. Top of page = logo at top, bottom of page = logo at bottom etc.

Unfortunately when I added the clipping paths the logos lost their scroll position and after the first one, do not work at all.

Is there a way around this? Also, the logo position was a little off to start with so if there is any way of addressing this at the same time.

You can see the original question here: div position based on scroll position

I have tried this, but I can't seem to get it to work.

Scroll position lost when hiding div

I am using Advanced Custom Fields and each sections PHP file has this in the header as part of the clipping path using either the white or dark version of the logo accordingly. Its parent is positioned relatively and its child absolutely.

div class="logo-scroll">
        <div class="scroll-text">
                    <a href="/home"><img width="53px" height="260px" src="/wp-content/uploads/2019/07/sheree-walker-web-design-edinburgh-vertical-01.svg"/></a>
       </div>
</div>  

The Javascript

const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const logo = document.querySelector('.scroll-text');
const logoHeight = logo.offsetHeight;
// to get the pseudoelement's '#page::before' top we use getComputedStyle method
const barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);

let viewportHeight, barHeight, maxScrollDist, currentScrollPos, scrollFraction;

logo.style.top = barTopMargin + 'px';

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

setSizes();


function update() {
    currentScrollPos = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
    scrollFraction = currentScrollPos / (docHeight - viewportHeight);

    logo.style.top = barTopMargin + (scrollFraction * maxScrollDist) + 'px';
}

function setSizes() {
    viewportHeight = window.innerHeight;
    // to get the pseudoelement's '#page::before' height we use getComputedStyle method
    barHeight = parseInt(getComputedStyle(document.querySelector('#page'), '::before').height); 
    maxScrollDist = barHeight - logoHeight;
    update();
}

The CSS

.logo-scroll .scroll-text img {
    padding: 0 6px 0 17px;
}


#page::before {
    content: "";
    position: fixed;
    top: 30px;
    bottom: 30px;
    left: 30px;
    right: 30px;
    border: 2px solid white;
    pointer-events: none;
    -webkit-transition: all 2s; /* Safari prior 6.1 */
    transition: all 2s;
}

.logo-scroll {
    position: fixed;
    left: 30px;
    top: 30px;
    bottom: 30px;
    border-right: 2px solid white;
    width: 75px;
    z-index: 10;
}

.scroll-text {
    position: fixed;
}
Mr Toad
  • 202
  • 2
  • 12
  • 41

2 Answers2

3
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();
}

updated and tested.

to check it put this code in your console:

document.removeEventListener('scroll', update);
document.onscroll = function() {
    let _logoHeight     = logos[0].offsetHeight;
    let _barTopMargin   = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);
    let _viewportHeight = window.innerHeight;
    let _maxScrollDist  = document.documentElement.scrollHeight - _viewportHeight;

    let currentScrollPos = document.documentElement.scrollTop;
    let percent100 = currentScrollPos + _viewportHeight;

    let scrolledPercent = currentScrollPos * 100/_maxScrollDist;

    let newTop = ((_viewportHeight - _logoHeight/2)*scrolledPercent/100);

    let middle = currentScrollPos + _viewportHeight/2;
    let middleY = _maxScrollDist/2; // 100

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

        newTop = _viewportHeight/2 - _logoHeight/2;
        newTop += (100-p)*(_viewportHeight/2)/100;
        newTop -= (100-p)*(30 +_logoHeight/2)/100;

        newTop = Math.max(newTop, _viewportHeight/2 - _logoHeight/2); /*fix*/

    } else {
        let y2 = middleY - Math.floor(-middle + (_maxScrollDist+_viewportHeight)/2);
        let p = y2*100/middleY;
        newTop = 30*(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";
    });
}

CSS fix: custom.css :: line 767

@media (max-width: 1000px)...
.scroll-text {
    padding-left: 13px;
    /*width: 27px;*/
}
.scroll-text img {
    /* remove it. but if necessary move it to .scroll-text rule above
    width: 27px; */
}

custom.css :: line 839

@media (max-width: 599px)...
.logo-scroll {
    /*display: none; why! remove it*/
}

custom.css :: line 268

.scroll-text {
    position: fixed;
    /* height: 280px; remove it*/
    padding-left: 20px;
}

see this capture


finally, have a nice day and goodby.

tdjprog
  • 706
  • 6
  • 11
  • Hi there - thanks for this, this is working quite well apart from there is a bit of a lag before it starts moving and for some reason it always stops short at the bottom, the offset amount varies depending on the browser width. I tried switching from SVG logos to png just incase it was something to do with the sizing but it doesn't make a difference. You can see it in place at the link in the original post – Mr Toad Aug 16 '19 at 08:56
  • Have updated but have no movement at all? Sorry. Thanks!\ – Mr Toad Aug 17 '19 at 13:01
  • please check it again using console. – tdjprog Aug 17 '19 at 18:57
  • donn't forget updating setSizes function too, and let me see your updates. thanks – tdjprog Aug 17 '19 at 19:11
  • my bad... i have used undefined variable (_viewportHeight)! in setSize function. [fixed] i'm sorry – tdjprog Aug 17 '19 at 20:11
  • Hi there - this is perfect, is it possible to have it recalculate on window resize? I'm thinking for instance such as rotating devices, you need to refresh the page each time. Thanks again for your time – Mr Toad Aug 19 '19 at 10:11
  • you need some fix for your CSS rules. the script is perfect and its idea was implemented for the first time for you (my pleasure to help). check update – tdjprog Aug 19 '19 at 12:52
  • "is it possible to have it recalculate on window resize?"!!!! already you have it. window.addEventListener('resize', setSizes); no need to refresh the page each time. – tdjprog Aug 19 '19 at 13:18
  • Thank you so much for your help. I still cannot get it to work 100% correctly when the page is resized as you can see in the link below but your snippit seems to work. I made those changes to the CSS - the display none at max-width:599px is because I do no use the vertical menu on mobile. Thanks again https://imgur.com/fVqdvQl – Mr Toad Aug 21 '19 at 21:29
  • Yes! That's perfect. Your an absolute star, really appreciate the effort and perseverance on this. – Mr Toad Aug 22 '19 at 11:04
  • I have realised that this does not work on Safari, the logo does not move at all? Any help would be much appreciated – Mr Toad Sep 03 '19 at 20:51
  • Sorry to bother you again, but do you have any ideas as to why this would not work on ipad? Thank you so much – Mr Toad Oct 12 '19 at 17:06
  • Hmm that did not seem to work. Sorry - I keep coming back to this when time allows – Mr Toad Nov 01 '19 at 14:28
0

You are selecting only the first 'logo-text'. Instead of:

const logo = document.querySelector('.scroll-text');

You should use querySelectorAll:

const logos = document.querySelectorAll('.scroll-text');

Then, in your scroll handler, you should move them all.

So, you would then substitute each instance of usage of logo with a loop through the all the logo elements:

logos.forEach(logo => logo.style.top = ...);

Please be aware that you are doing quite expensive stuff in a scroll handler, which is not great for rendering performance. You might also want to use requestAnimationFrame to improve the rendering performance. Check out the reference page on MDN. Actually, I quickly whipped a version using requestAnimationFrame but there was no sensible performance improvement. This is probably due to the fact that apparently rAF fires roughly at the same rate than the scroll event. Anyway, I removed it to avoid confusion. If you detect performance issues, let me know.

I suggest, though, that you move the logo using transform: translate() rather than top. Here you have a complete solution. I tried it in Chrome.

const docHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const logos = document.querySelectorAll('.scroll-text');
const logoHeight = logos[0].offsetHeight;
// to get the pseudoelement's '#page::before' top we use getComputedStyle method
const barTopMargin = parseInt(getComputedStyle(document.querySelector('#page'), '::before').top);

let viewportHeight, barHeight, maxScrollDist, currentScrollPos, scrollFraction;

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

setSizes();

function update() {
  currentScrollPos = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
  scrollFraction = currentScrollPos / (docHeight - viewportHeight);
  const translateDelta = barTopMargin + (scrollFraction * maxScrollDist);

  logos.forEach(logo => logo.style.transform = `translateY(${translateDelta}px)`);
}

function setSizes() {
  viewportHeight = window.innerHeight;
  // to get the pseudoelement's '#page::before' height we use getComputedStyle method
  barHeight = parseInt(getComputedStyle(document.querySelector('#page'), '::before').height);
  maxScrollDist = barHeight - logoHeight;
  update();
}
  • Hi there. Thank you for this. I changed the line you suggested, but the effect is that none of them now move. In terms of using transform: translate() - I am familiar with this use is CSS but I do not know how to integrate that into the JS in place of 'top' as a use supplied me with the original JS here https://stackoverflow.com/questions/57129154/div-position-based-on-scroll-position/57129715?noredirect=1#comment100840522_57129715 Though I never got it to work properly. – Mr Toad Aug 15 '19 at 20:34
  • @MrToad I have update my answer with an example usage. Today I will be out, but when I get back I will try to provide a better implementation that uses `transform`s and `requestAnimationFrame`. – Emanuele Feliziani Aug 16 '19 at 07:38
  • Hi @Emanuele - thank you for your time. Sorry to be an idiot, but I can't seem to get these changes to work, though I suspect I might be editing it wrong. – Mr Toad Aug 16 '19 at 08:06
  • @MrToad Don’t worry, we’ve all been there. I will provide a complete working solution when I get back home, either today or tomorrow. Cheers. – Emanuele Feliziani Aug 16 '19 at 08:44
  • @MrToad I added a working snippet I tested on Chrome. Hope it helps. – Emanuele Feliziani Aug 16 '19 at 19:32
  • @MrToad I forgot to mention that you must set `top: 0` to the `.scroll-text` element to move it to the top of that scroll area, or find another solution for all that. – Emanuele Feliziani Aug 16 '19 at 19:40
  • Emanuele thank you for your help with this, unfortunately in this case I had to award the bounty to tdjprog but I appreciate your time – Mr Toad Aug 27 '19 at 22:24