Parallax scrolling – decrease height of container to match transform gap
Asked Answered
S

3

8

I have an arrangement of elements on the page – positions controlled via a CMS which gives each element a width, top position, left position, z-index and 'speed'.

The speed is used to create a parallax effect using JS. It takes it's 'speed' and calculates this by the window.pageYOffset – if the speed is less than 0 then it divides the window.pageYOffset by the speed and if it's more than 0 then it multiplies the window.pageYOffset by the speed. On scroll it then adjusts the Y translation to give this 'parallax' effect.

This is generally all fine but of course as you alter the Y positions of the elements on scroll you are left with a 'gap' at the bottom (where the elements would be if the scroll speed matched the user scroll speed).

To rectify this I thought I would get the most bottom element, and get it's getBoundingClientRect().bottom position and on scroll reduce the height of the container to match the bottom of this element so as you scroll down or up the container would contract/expand to match and thus removing the gap.

However, this doesn't seem to work. The math/logic is either wrong or I'm missing the entire thing.

Below is my code and I have set up a JSFiddle to help visualise this.

https://jsfiddle.net/6up3vqjn/2/

// Runs on init and resize
function parallaxInit() {

  var $container = $('.parallax'),
    container = $container[0];

  var testArray = [],
    $testLastElement;

  $('.parallax > .group').each(function() {

    var $group = $(this),
      group = $group[0],
      groupBounds = group.getBoundingClientRect(),
      $lastElement,
      lastElementBoundsBottom = 0;

    $group.find('> div').each(function() {

      var $div = $(this),
        div = $div[0],
        initTop = $div.attr('data-top');

      if (initTop == 0) {
        $div.css('top', '0');
      } else {
        $div.css('top', $(window).width() / 12 * initTop - 26 + 'px');
      };

      group.removeAttribute('style');
      $group.height(group.scrollHeight).attr('data-height', group.scrollHeight);

      var divBounds = div.getBoundingClientRect();
      testArray.push(divBounds.bottom);

    });

  });

  $('.parallax > .group > div').each(function() {

    var divBottomBounds = $(this)[0].getBoundingClientRect().bottom;

    if (divBottomBounds == Math.max.apply(Math, testArray)) {
      $testLastElement = $(this);
      $(this).addClass('is--last');
    }

    var letters = "0123456789ABCDEF";
    var color = '#';
    for (var i = 0; i < 6; i++) color += letters[(Math.floor(Math.random() * 16))];
    $(this).css('background-color', color);


  });

  $container[0].style.height = $testLastElement[0].getBoundingClientRect().bottom + 'px';

}
parallaxInit();
$(window).on('resize', parallaxInit);

// Runs on scroll
function parallax() {

  var $container = $('.parallax');

  var test = 0;
  var testArray = [],
    $testLastElement;

  $('.parallax > .group').each(function() {

    var $group = $(this),
      group = $group[0],
      groupHeight = $group.attr('data-height'),
      groupBounds = group.getBoundingClientRect();

    $group.find('> div').each(function() {

      var $this = $(this),
        speed = $this.attr('data-speed');

      if (speed < 0) {
        speed = Math.abs(speed);
        var yPos = window.pageYOffset / speed;
      } else {
        var yPos = window.pageYOffset * speed;
      }
      yPos = -yPos;

      $this[0].style.transform = "translate3d(0, " + yPos + "px, 0)";

      var divBounds = $this[0].getBoundingClientRect(),
        divRelativeBounds = {};

      testArray.push(divBounds.bottom);

    });

  });


  $('.parallax > .group > div').each(function() {

    var divBottomBounds = $(this)[0].getBoundingClientRect().bottom;
    $(this).removeClass('is--last');

    if (divBottomBounds == Math.max.apply(Math, testArray)) {
      $testLastElement = $(this);
      $(this).addClass('is--last');
    }

  });

  $container[0].style.height = $testLastElement[0].getBoundingClientRect().bottom + 'px';

}
$(window).bind('scroll', parallax);

UPDATED JSFiddle with container height calculation removed: https://jsfiddle.net/ejqhvz2c/

Semipalmate answered 6/6, 2019 at 15:18 Comment(9)
Apart from scrolling randomly scrolling back up. im not sure what your actual problem is?Zel
@Zel The whitespace gap at the bottom is the problem.Semipalmate
I'm having a hard time observing the current behavior while matching your desired one. Because of this random scrolling up and down issue.Royalty
@ColdCerberus It's reducing the height of the container as you scroll but the math is wrong hence the issue. Let me remove this so you can see the white gap.Semipalmate
@JohnthePainter i dont see any whitespace at the bottom because i get scrolled back up instantly. I think the fiddle needs this scrolling thing fixing before we can help with the actual problem.Zel
jsfiddle.net/ejqhvz2cSemipalmate
@Deckerz, The bottom dead space is observable if you continuously scroll down until "it happens". But yeah, the scrolling up issue has to be fixed first.Royalty
@JohnthePainter do you want to keep the large scrollbar? or remove it?Zel
@Zel Probably remove it.Semipalmate
Z
2

I'm not 100% this is exactly what you are after but I have stopped the over-scroll and the scrolling into white space. First I changed the way the event is triggered. Since it is a parallax I made it bound to the wheel event rather than actual scrolling, which in doesn't actually matter when page actually has content on it. Then I used a global variable called paralaxYOffset which i decide was best to increment in +- 50 intervals for smoothness and to stop over-scroll i added a check to see if .is--last y position is <= 0 and then dont allow parallax scrolling any further.

Event changes:

if(e.originalEvent.deltaY  > 0){
        if($(".is--last")[0].getBoundingClientRect().y > 0){
            window.paralaxYOffset += 50;
      }
  }else{
    window.paralaxYOffset -= 50;
  }

  if(window.paralaxYOffset < 0)
    window.paralaxYOffset = 0;

To make sure everything looked right and to remove the overflow scroll bar, i made .parallax set to 100vh and overflow: hidden

I believe this achieves what you requested :)

https://jsfiddle.net/fynmtk9b/4/

Zel answered 11/6, 2019 at 9:54 Comment(8)
Thanks for your help on this. It's an interesting approach. Do you know why there is still white space at the bottom and why it doesn't stop when the bottom of the last element is reached?Semipalmate
@JohnthePainter its because the scroll of 50 isn't quite flush with the bottom. Lowering the += 50 to something like 20 might mitigate it completely. Or maybe adding an additional calc to set the position so that it hits the bottom.Zel
I wonder if we can make the increment (50) the e.originalEvent.deltaY instead so it's more related to the how the user scrolls (rather than just a static number)Semipalmate
You can make it that yes. I think the best way to handle blank is to make it so it does checks to see if its bottom corner is smaller than the bottom x of the viewport.Zel
Are you able to help with these for more rep? It needs to be perfect :(Semipalmate
@JohnthePainter see updated fiddle. That is as close as i could get it.Zel
Thanks. When you fast scroll the calculation isn't always right and the bottom elements get cut – don't worry. I appreciate your help in trying to solve this for me.Semipalmate
I managed to tweak your code a bit: jsfiddle.net/gvap7f9z and now it checks if window.innerHeight >= $lastElement[0].getBoundingClientRect().bottom which seems to work well but I get an odd bounce when you hit the bottom of the page. Hmm.Semipalmate
R
0

I am not really sure what you trying to achieve here. But if you want to limit the container height to match the group div components height, just set the container display as contents and remove all heights attribute from group div components then it will work.

<div class="parallax" style="display:contents;">

Here is the fiddle: https://jsfiddle.net/bf3e82o4/3/

Rascally answered 18/6, 2019 at 4:20 Comment(0)
A
0

Try this edits! I have limited the container height to fit all the components height

function parallaxInit() {
  var $container = $('.parallax'),
    container = $container[0];

  var testArray = [],
    $testLastElement;

  $('.parallax > .group').each(function() {

    var $group = $(this),
      group = $group[0],
      groupBounds = group.getBoundingClientRect(),
      $lastElement,
      lastElementBoundsBottom = 0;

    $group.find('> div').each(function() {

      var $div = $(this),
        div = $div[0],
        initTop = $div.attr('data-top');

      if (initTop == 0) {
        $div.css('top', '0');
      } else {
        $div.css('top', $(window).width() / 12 * initTop - 26 + 'px');
      };

      group.removeAttribute('style');
      //$group.height(group.scrollHeight).attr('data-height', group.scrollHeight);

      var divBounds = div.getBoundingClientRect();
      testArray.push(divBounds.bottom);

    });

  });

  $('.parallax > .group > div').each(function() {

    var divBottomBounds = $(this)[0].getBoundingClientRect().bottom;

    if (divBottomBounds == Math.max.apply(Math, testArray)) {
      $testLastElement = $(this);
      $(this).addClass('is--last');
    }

    var letters = "0123456789ABCDEF";
    var color = '#';
    for (var i = 0; i < 6; i++) color += letters[(Math.floor(Math.random() * 16))];
    $(this).css('background-color', color);

  });
}
parallaxInit();
$(window).on('resize', parallaxInit);

function parallax() {
	var $container = $('.parallax');

	var test = 0;
	var testArray = [],
		$testLastElement;

	$('.parallax > .group').each(function() {

		var $group = $(this),
			group = $group[0],
			groupHeight = $group.attr('data-height'),
			groupBounds = group.getBoundingClientRect();

		$group.find('> div').each(function() {

			var $this = $(this),
				speed = $this.attr('data-speed');

			if (speed < 0) {
				speed = Math.abs(speed);
				var yPos = window.pageYOffset / speed;
			} else {
				var yPos = window.pageYOffset * speed;
			}
			yPos = -yPos;

			$this[0].style.transform = "translate3d(0, " + yPos + "px, 0)";

			var divBounds = $this[0].getBoundingClientRect(),
				divRelativeBounds = {};
			testArray.push(divBounds.bottom);
		});
	});
	$('.parallax > .group > div').each(function() {

		var divBottomBounds = $(this)[0].getBoundingClientRect().bottom;
		$(this).removeClass('is--last');

		if (divBottomBounds == Math.max.apply(Math, testArray)) {
			$testLastElement = $(this);
			$(this).addClass('is--last');
	  }
	});
}
$(window).bind('scroll', parallax);
.parallax {
	position: relative;
  overflow: hidden;
	z-index: 1;
	.group {
		position: relative;
		> div {
			position: absolute;
      picture {
        display: block;
      }
		}
	}
}
<div class="parallax" style="display:contents;">
  <div class="group" data-height="">
    <div style="width:50%;left:0%;z-index:1" data-top="1" data-speed="2">
      <picture data-ratio data-format="portrait" style="padding-bottom:128.97%;"></picture>
    </div>
    <div style="width:83.333333333333%;left:8.3333333333333%;z-index:2" data-top="6" data-speed="1">
      <picture data-ratio data-format="landscape" style="padding-bottom:93.31%;"></picture>
    </div>
    <div style="width:33.333333333333%;left:66.666666666667%;z-index:3" data-top="2" data-speed="2.5">
      <picture data-ratio data-format="portrait" style="padding-bottom:129.29%;"></picture>
    </div>
    <div style="width:50%;left:0%;z-index:4" data-top="14" data-speed="2.5">
      <picture data-ratio data-format="portrait" style="padding-bottom:133.33%;"></picture>
    </div>
    <div style="width:50%;left:50%;z-index:5" data-top="14" data-speed="1">
      <picture data-ratio data-format="landscape" style="padding-bottom:69.38%;"></picture>
    </div>
    </div>
    <div class="group" data-height="">
    <div style="width:83.333333333333%;left:8.3333333333333%;z-index:1" data-top="1" data-speed="2">
      <picture data-ratio data-format="landscape" style="padding-bottom:75%;"></picture>
     </div>
    <div style="width:50%;left:41.666666666667%;z-index:2" data-top="6" data-speed="2">
      <picture data-ratio data-format="portrait" style="padding-bottom:133.33%;"></picture>
     </div>
   </div>
   <div class="group" data-height="">
   <div style="width:33.333333333333%;left:0%;z-index:1" data-top="1" data-speed="3">
      <picture data-ratio data-format="portrait" style="padding-bottom:129.66%;"></picture>
    </div>
    <div style="width:33.333333333333%;left:25%;z-index:2" data-top="5" data-speed="3.5">
      <picture data-ratio data-format="portrait" style="padding-bottom:133.33%;"></picture>
    </div>
    <div style="width:58.333333333333%;left:16.666666666667%;z-index:3" data-top="5" data-speed="3">
      <picture data-ratio data-format="landscape" style="padding-bottom:66.5%;"></picture>
    </div>
    <div style="width:58.333333333333%;left:16.666666666667%;z-index:4" data-top="13" data-speed="3.6">
      <picture data-ratio data-format="landscape" style="padding-bottom:43.3%;"></picture>
    </div>
  </div>
  <div class="group" data-height="">
    <div style="width:100%;left:0%;z-index:1" data-top="0" data-speed="3.2">
      <picture data-ratio data-format="landscape" style="padding-bottom:74.89%;"> </picture>
    </div>
    <div style="width:50%;left:0%;z-index:2" data-top="5" data-speed="4">
      <picture data-ratio data-format="portrait" style="padding-bottom:133.33%;"></picture>
    </div>
    <div style="width:41.666666666667%;left:58.333333333333%;z-index:3" data-top="0" data-speed="3.5">
      <picture data-ratio data-format="landscape" style="padding-bottom:75%;"></picture>
     </div>
  <div style="width:33.333333333333%;left:58.333333333333%;z-index:4" data-top="18" data-speed="4.7">
      <picture data-ratio data-format="portrait" style="padding-bottom:133.33%;"></picture>
    </div>
  </div>
</div>

Hope this helps. You're good to go!!!

Abducent answered 18/6, 2019 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.