John09
John09

Reputation: 43

Parallax elements not the same positon when scroll back

I created Parallax script for some elements (rect & circles) so when I scroll from top to bottom, elements should move to the top

Elements starting position is added in HTML directly using < style > The problem is when I scroll back elements move down, but they are not in the same starting position when I reach the top of the page.

HTML:

<img style="top: 62%; left: 46.3%;" class="l-parallax_item" src="./path/to/img">
<img style="top: 74%; left: 42.7%;" class="l-parallax_item" src="./path/to/img">

CSS: Parent has position relative, l-parallax_item is position absolute

JS:

var parallaxElements = document.getElementsByClassName("l-parallax_item");
var lastScrollTop = 0;

window.onscroll = function() {

    var st = window.pageYOffset || document.documentElement.scrollTop; 

    if (st > lastScrollTop){
        console.log("bottom");
        for(i = 0; i < parallaxElements.length; i++) {
            var position = parallaxElements[i].offsetTop;
            var movePx = parallaxElements[i].getAttribute("data-px-per-scroll");
            parallaxElements[i].style.top = (position - parseInt(movePx))+"px";

        }
    } else {
        console.log("top");
        for(i = 0; i < parallaxElements.length; i++) {
            var position = parallaxElements[i].offsetTop;
            var movePx = parallaxElements[i].getAttribute("data-px-per-scroll");
            parallaxElements[i].style.top = (position + parseInt(movePx))+"px";
        }
    }

   lastScrollTop = st <= 0 ? 0 : st; // For Mobile or negative scrolling

}

I noticed when I scroll from top to bottom I get more console.log("bottom") messages. * When I scroll top > bottom and vise versa. So I guess that is the reason why the element is not in the same position too when I go bottom > top.

http://prntscr.com/ka9osq

How can I fix this?

EDIT: http://jsfiddle.net/r9751p8q/6/ Try to scroll to the bottom and then back you will see that some element disappear

Upvotes: 2

Views: 286

Answers (2)

Jensei
Jensei

Reputation: 480

The reason it's not working:

Scrolling is not precise and because you base your position on the total sum of scrolls the positioning will end up unpredictable. To show you what I mean:

Your console.log() fires every time you scroll. If you change both logs to console.log(parallaxElements[0].style.top) it will show you the top position of the first parallaxed element each time you make a scroll move. Now take your mouse and scroll one "tick" down, then one "tick" up and repeat many times. The position numbers will not be the same every time, and therein lies the problem.

Solution:

Base your parallax elements position on the actual pageYOffset. Since Ale already posted a working solution with this in mind one more code example is reduntant.

Upvotes: 1

Ale
Ale

Reputation: 1032

Here is a possible solution of your problem.

HTML

<div class="section">
  <div style="top: 100px; left: 46.3%;" data-px-per-scroll="0.5" data-initial-position="100" class="l-parallax_item"></div>
  <div style="top: 300px; left: 37.7%;" data-px-per-scroll="0.9" data-initial-position="300" class="l-parallax_item"></div>
  <div style="top: 80px; left: 56%;" data-px-per-scroll="0.3" data-initial-position="80" class="l-parallax_item"></div>
  <div style="top: 230px; left: 75%;"  data-px-per-scroll="0.8" data-initial-position="230"  class="l-parallax_item"></div>
  <div style="top: 60px;left: 7.1%;" data-px-per-scroll="0.5" data-initial-position="60" class="l-parallax_item"></div>
</div>

JS

function parallaxBlocks(){
  let parallaxElements = document.getElementsByClassName("l-parallax_item");
  let st = window.pageYOffset || document.documentElement.scrollTop; 
  let elementsLength = parallaxElements.length;

   for(i = 0; i < elementsLength; i++) {

    let movePx = 1 + parseFloat(parallaxElements[i].dataset["pxPerScroll"]);
    let position = 1 + parseInt(parallaxElements[i].dataset["initialPosition"]);
    parallaxElements[i].style.top = Math.floor(position - (st * movePx)) + 'px';

   }
}

window.addEventListener('scroll', parallaxBlocks)

But I also changed your html. For working example check this fiddle .

And some explanation what I'm actually doing in the JS code:

  • In this code we don't need to check if we scroll up or down because the logic of the positioning of the elements is shifted form what was their current position (as in your code) to what was their initial position.
  • I'm using let to declare variables instead of var for scope reasons (and because I love new things). Still if you are going to support older browsers you might want to check caniuse for compatibilities.
  • For the top css property I'm using fixed values, and I store their original top offset in a new data attribute called initial-position. This initial position will be used later to calculate the new position of each element. If you need a % values then you can keep the top property with % value, but you will need also another loop to go through all the .l-parallax_item and check their offset from the top and record this value in their data-initial-position.
  • Note that I'm using dataset instead getAttribute. dataset is made for all data attributes. And see how the cebab-case became camelCase. More to read here
  • Also the px-per-scroll is no longer a fixed amount of pixels instead it is a ratio from the scroll offset from top. You can play with the fiddle to see how it works.
  • Bonus: why I added another variable for the elements length instead just using it in the for loop arguments.

I believe that there is another way to do this but hope that this one will help you.

Upvotes: 1

Related Questions