Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When element enters viewport, scroll its entire contents horizontally before it leaves the viewport

I have a horizontally scrolling element containing images that is situated out of view down the page.

To start with, I have the following markup and styles. I've used overflow:hidden because I don't want scrollbars. For the sake of simplicity, I have also removed some less-important styles:

<ul id="players-horizontal">
    <li class="player-box"><img src="..."></li>
    <li class="player-box"><img src="..."></li>
    ...
</ul>

#players-horizontal {
    overflow: hidden;
    height: 340px;
    width: 100%;
}

#players-horizontal .player-box {
    display: inline-block;
    width: 300px;
    height: 340px;
}

#players-horizontal .player-box:first-child {
    margin-left: 90%;
}

Which gives me the following view:

enter image description here

When this element scrolls into view, I want to scroll its entire contents horizontally until it's about to go out of view, so the user gets to see its entire contents whilst scrolling down.

The desired look when the element is about to leave the viewport would be:

enter image description here

And vice-versa, the reverse action should happen when the user scroll upwards.

To know when the element has entered the viewport I have use the Waypoints plugin:

var waypoints = $('#players-horizontal').waypoint(
    function(direction) {
        if (direction === 'down') {
            //console.log("Into view");
            $window.scroll(function () {
                var s = $(this).scrollTop();

            });
        } else {
            //console.log("Out of view");
            $window.scroll(function () {
                var s = $(this).scrollTop();

            });
        }
    }, { offset: '90%' }
);

But I'm stuck understanding what to do next, more specifically, how to work out how much to scroll (alter the margin of) the element in the short timeframe it has before leaves the viewport, ensuring all elements inside of it are shown before it does.

I would appreciate a help over the finishing line. I don't expect working code, although I'd be grateful, I really just need an easy to understand explanation of what is required.

UPDATE:

I have just tried using an alternative method which doesn't use the Waypoints plugin but instead uses ScrollMagic. This has definitely lifted a lot of the work required.

var scene = new ScrollMagic.Scene({triggerElement: "#players-horizontal", duration: 200})
.addTo(controller)
.on("progress", function (e) {
    console.log((e.progress * 100));
});

The above snippet detects when the element #players-horizontal enters and leaves the viewport.

When the element enters the bottom of viewport when scrolling down, the value returned starts at 0 and stops at 100 just before leaving the top of the viewport. When I scroll back up, the value starts at 100 and stops at 0 when the element is about to leave the bottom of the viewport. So, I'm definitely a lot closer.

like image 901
Martin James Avatar asked Jan 29 '19 11:01

Martin James


1 Answers

I would think about this differently. First I would consider using translation to make it easier to handle. You also need a little JS code to handle this

I will have the main container that I will translate by 90% to have your margin effect, then I will translate the content inside it from 0% to 100%. The translation will consider the scroll and screen height. Basically when the element enter the sreen I will start translating consider the difference between the offset top and the screen height.

Here is a first example.

var h =document.querySelector('#players-horizontal');

document.body.onscroll = function(e) {
  
  var offset = h.getBoundingClientRect()
  var top = offset.top;
  if(top < window.innerHeight) {
     h.style.setProperty('--x',(top - window.innerHeight)+'%');
  }
}
body {
 margin:0;
 overflow-x:hidden;
}
.top,.bottom {
  min-height:150vh;
  background:yellow;
}
.container {
    transform:translateX(90%);
}
#players-horizontal {
    white-space:nowrap;
    margin:0;
    padding:0;
    display:inline-block; /*This is important !!*/
    transform:translateX(var(--x,0%));
    border:5px solid;
}

#players-horizontal .player-box {
    display: inline-block;
    width: 200px;
    height: 240px;
    margin:0 10px;
    background:red;
}

#players-horizontal .player-box:first-child {
    background:green;
    margin-left:0;
}
#players-horizontal .player-box:last-child {
    background:blue;
    margin-right:0;
}
<div class="top"></div>
<div class="container">
<ul id="players-horizontal">
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
</ul>
</div>
<div class="bottom"></div>

This above will create the scrolling effect but now we need to define the correct values.

It's clear that we need the scroll to start at top = window.innerHeight (translate(0%)) and when top = 0 we want our element to be fully shown and the last item at the right edge which means that we need to have translate(-100% + width of screen) but since the width of screen is also the same as the container width and we already translated the container by 90% then we can only reach translate(-100%) (we can add negative margin to last element to rectify the position).

We simply need to convert the difference (top - window.innerHeight) to a percentage value considering window.innerHeight as 100% (when top=0).

var h =document.querySelector('#players-horizontal');

document.body.onscroll = function(e) {
  
  var offset = h.getBoundingClientRect()
  var top = offset.top;
  if(top < window.innerHeight && top >=0) {
     h.style.setProperty('--x',(top - window.innerHeight)*(100/window.innerHeight)+'%');
  }
}
body {
 margin:0;
 overflow-x:hidden;
}
.top,.bottom {
  min-height:150vh;
  background:yellow;
}
.container {
    transform:translateX(90%);
}
#players-horizontal {
    white-space:nowrap;
    margin:0;
    padding:0;
    display:inline-block; /*This is important !!*/
    transform:translateX(var(--x,0%));
    border:5px solid;
}

#players-horizontal .player-box {
    display: inline-block;
    width: 200px;
    height: 240px;
    margin:0 10px;
    background:red;
    vertical-align:top;
}

#players-horizontal .player-box:first-child {
    background:green;
    margin-left:0;
}
#players-horizontal .player-box:last-child {
    background:blue;
    margin-right:-10vw; /*the missing 10%*/
}
<div class="top"></div>
<div class="container">
<ul id="players-horizontal">
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
</ul>
</div>
<div class="bottom"></div>

I have used CSS variable (a personnal preference) but it's not really mandatory and you can simply set transform with the JS code:

var h =document.querySelector('#players-horizontal');

document.body.onscroll = function(e) {
  
  var offset = h.getBoundingClientRect()
  var top = offset.top;
  if(top < window.innerHeight && top >=0) {
     h.style.transform="translateX("+(top - window.innerHeight)*(100/window.innerHeight)+'%)';
  }
}
body {
 margin:0;
 overflow-x:hidden;
}
.top,.bottom {
  min-height:150vh;
  background:yellow;
}
.container {
    transform:translateX(90%);
}
#players-horizontal {
    white-space:nowrap;
    margin:0;
    padding:0;
    display:inline-block; /*This is important !!*/
    transform:translateX(0%);
    border:5px solid;
}

#players-horizontal .player-box {
    display: inline-block;
    width: 200px;
    height: 240px;
    margin:0 10px;
    background:red;
    vertical-align:top;
}

#players-horizontal .player-box:first-child {
    background:green;
    margin-left:0;
}
#players-horizontal .player-box:last-child {
    background:blue;
    margin-right:-10vw;
}
<div class="top"></div>
<div class="container">
<ul id="players-horizontal">
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
    <li class="player-box"></li>
</ul>
</div>
<div class="bottom"></div>
like image 184
Temani Afif Avatar answered Oct 22 '22 21:10

Temani Afif