javascripthtmlcssformulaparallax

Parallax effect in Js: Is there a formula for the scroll modificator so the background image and the website content always reach the bottom together?


let me start by wishing you a wonderful day!

To summarise my website: The body is made of a header and a main section. The main section has a background-image and elements. Those elements's (combined) height is variable because it depends on the font-size of the user, the screen size etc. For those that want to test it themselves or need it to answer my question, here is the html and css code:

document.querySelector("body").onscroll = function() {
  const main = document.querySelector("main");
  const scrolltotop = document.scrollingElement.scrollTop;

  const factor = 0.5; // <- this factor here is my problem

  const yvalue = scrolltotop * factor;
  const xvalue = "center";
  main.style.backgroundPosition = xvalue + " " + yvalue + "px";
}
* {
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
}

header {
  height: 40%;
}

main {
  width: 100%;
  background-image: url("https://images.pexels.com/photos/30005397/pexels-photo-30005397/free-photo-of-colorful-macaws-perched-in-lush-cancun-jungle.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2");
  /* <- This image is just an example */
  background-repeat: no-repeat;
  background-size: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}

main>* {
  width: 25em;
  height: 40em;
  background: gray;
  margin: 2%;
  font-size: 150%;
  align-content: center;
  text-align: center;
  color: blue;
}
<header>
  <p>Header p content</p>
</header>
<main>
  <p>Main element 1</p>
  <p>Main element 2</p>
  <p>Main element 3</p>
  <p>Main element 4</p>
  <p>Main element 5</p>
  <p>Main element 6</p>
  <p>Main element 7</p>
  <p>Main element 8</p>
  <p>Main element 9</p>
</main>

The problem with this code is that it works but not the way I need it. When I use constant factors like 0.5, 0.3, 0.7 etc. there always either is a white space between the background-image and the bottom if you scroll down, or - and this is the opposite effect - you don't reach the bottom of the image when you scroll down, cutting off parts. Constant factors only work for very specific circumstances but I want it to work everywhere.

So what I need is a formula that automatically (re-)calculates the factor so no matter how you resize the screen, no matter the height of the header, main or the elements in main, the formula always ensures the background-image's bottom perfectly matches the website's bottom. If you could help me, I would really appreciate that!


Solution

  • I have decided to share the answer I've come up with on my own in case someone else after me has the same question and hopes to find an answer here:

        document.querySelector("body").onscroll = function() {
        const main             = document.querySelector("main");
        const backgroundHeight = main.offsetWidth * (3.0/2.0);
        const screenHeight     = Math.max(window.screen.availHeight, window.innerHeight);
        const mainHeight       = main.offsetHeight;
        
        const factor = 1.0 - ((backgroundHeight - screenHeight) / (mainHeight - screenHeight));
        const yvalue = - main.getBoundingClientRect().top * factor;
        const xvalue = "center";
        main.style.backgroundPosition = xvalue + " " + yvalue + "px";
        }
    

    const main = document.querySelector("main");

    I do this because the background image I want the parallax effect to work on applies to the main element.

    const backgroundHeight = main.offsetWidth * (3.0/2.0);

    The formula I came up with to always align the background image's bottom with the page's bottom requires the height of the background image. For that I use main.offsetWidth (I set main to take up 100% of the width of the page so this works) multiplied with the height/width ratio of the background image. In the case of the parrot image I used as example, it's 3/2.

    screenHeight = Math.max(window.screen.availHeight, window.innerHeight);

    One also needs the height of the screen. The problem is that I cannot use a single value as sometimes it gives me a too small result. However, using the maximum of either window.screen.availHeight or window.innerHeight always seems to work to me.

    mainHeight = const mainHeight = main.offsetHeight;

    And one needs the height of the element you want to apply the parallax effect on.

    const factor = 1.0 - ((backgroundHeight - screenHeight) / (mainHeight - screenHeight));

    This is the formula I came up with thanks to a geometric sketch.

    const yvalue = - main.getBoundingClientRect().top * factor;

    I found out that "- main.getBoundingClientRect().top" seems to work better than "const scrolltotop = document.scrollingElement.scrollTop" I used before. main.getBoundingClientRect().top basically returns the distance from the top of the main element to the top of the screen and becomes a negative value once you have scrolled past main's top. This is why I added a minus but Math.abs() works too.

    const xvalue = "center"; main.style.backgroundPosition = xvalue + " " + yvalue + "px";

    Here you just insert the values into the background.

    Right now this function is executed everytime you scroll. main, backgroundHeight, screenHeight and mainHeight stay constant while just scrolling so it would make sense to initialise those values in a separate function executed on load or on resize but not on scroll - unless main changes its size automatically or upon user interaction. I also learnt first-hand while testing that Chrome has massive performance issues with repositioning background images larger than 1000x1000 so please keep this in mind.