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/

John the Painter
  • 2,495
  • 8
  • 59
  • 101

3 Answers3

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/

Deckerz
  • 2,606
  • 14
  • 33
  • 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? – John the Painter Jun 11 '19 at 10:13
  • @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. – Deckerz Jun 11 '19 at 10:21
  • 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) – John the Painter Jun 11 '19 at 10:51
  • 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. – Deckerz Jun 11 '19 at 11:00
  • Are you able to help with these for more rep? It needs to be perfect :( – John the Painter Jun 11 '19 at 11:03
  • @JohnthePainter see updated fiddle. That is as close as i could get it. – Deckerz Jun 11 '19 at 11:32
  • 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. – John the Painter Jun 11 '19 at 11:58
  • I managed to tweak your code a bit: https://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. – John the Painter Jun 11 '19 at 15:58
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/

Raymond Natio
  • 556
  • 3
  • 22
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!!!

Akshay Mulgavkar
  • 1,727
  • 9
  • 22