Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complex continuous scroll loop

I have a code similar to:

<div id='right-column'>
    <div id='results'>
        <div id='result1>
            <div class='main'></div>
            <div class='details'></div>
        </div>
        <!-- ... -->
        <div id='result50>
            <div class='main'></div>
            <div class='details'></div>
        </div>
    </div>
</div>
  • the total number of results depends of the ajax query, I insert all the results dynamically in one go.
  • div.main is always visible (fixed height) and div.details "unfolds/folds" below div.main when the user clicks on a result div.
  • the details div height can vary.

If #results scrollHeight is bigger than #right-column height, I would like to create a continuous scroll loop.
In this case, scrolling past #result50 would show #result1, scrolling before #result1 would show #result50.

I can't .append() the first child to the bottom as in some cases a portion of a result can be seen on top and at the bottom of the column.
I can't duplicate a result unless I detect if .details is unfolded/folded.
The fact that the height of a result can change when a user unfolds the .details div, makes it even more complicated...

Here is an example of continuous scroll loop (2 columns):

$(document).ready(function() {
  var num_children = $('#up-left').children().length;
  var child_height = $('#up-left').height() / num_children;
  var half_way = num_children * child_height / 2;
  $(window).scrollTop(half_way);

  function crisscross() {
    $('#up-left').css('bottom', '-' + window.scrollY + 'px');
    $('#down-right').css('bottom', '-' + window.scrollY + 'px');
    var firstLeft = $('#up-left').children().first();
    var lastLeft = $('#up-left').children().last();
    var lastRight = $('#down-right').children().last();
    var firstRight = $('#down-right').children().first();

    if (window.scrollY > half_way ) {
      $(window).scrollTop(half_way - child_height);
      lastRight.appendTo('#up-left');
      firstLeft.prependTo('#down-right');
    } else if (window.scrollY < half_way - child_height) {
      $(window).scrollTop(half_way);
      lastLeft.appendTo('#down-right');
      firstRight.prependTo('#up-left');
    }
  }

  $(window).scroll(crisscross);
});
div#content {
  width: 100%;
  height: 100%;
  position: absolute;
  top:0;
  right:0;
  bottom:0;
  left:0;
}
#box {
  position: relative;
  vertical-align:top;
  width: 100%;
  height: 200px;
  margin: 0;
  padding: 0;
}
#up-left {
  position:absolute;
  z-index:4px;
  left: 0;
  top: 0px;
  width: 50%;
  margin: 0;
  padding: 0;
}
#down-right {
  position:fixed;
  bottom: 0px;
  z-index: 5px;
  left: 50%;
  width: 50%;
  margin: 0;
  padding: 0;
}
h1 {margin: 0;padding: 0;color:#fff}
.black {background: black;}
.white {background: grey;}
.green {background: green;}
.brown {background: brown;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>

<div id="content">
  <div id="up-left">
    <div id="box" class="brown">
      <h1>ONE</h1>
    </div>
    <div id="box" class="black">
      <h1>TWO</h1>
    </div>
    <div id="box" class="white">
      <h1>THREE</h1>
    </div>
    <div id="box" class="black">
      <h1>FOUR</h1>
    </div>
    <div id="box" class="white">
      <h1>FIVE</h1>
    </div>
    <div id="box" class="black">
      <h1>SIX</h1>
    </div>
  </div><!-- #up-left -->
  <div id="down-right">
    <div id="box" class="white">
      <h1>SIX</h1>
    </div>
    <div id="box" class="black">
      <h1>FIVE</h1>
    </div>
    <div id="box" class="white">
      <h1>FOUR</h1>
    </div>
    <div id="box" class="black">
      <h1>THREE</h1>
    </div>
    <div id="box" class="white">
      <h1>TWO</h1>
    </div>
    <div id="box" class="green">
      <h1>ONE</h1>
    </div>
  </div><!-- #down-right -->
</div><!-- .content -->
(fiddle: http://jsfiddle.net/franckl/wszg1d6c/)

Any hint/ideas on how I could do it ?

like image 651
Franckl Avatar asked May 07 '15 00:05

Franckl


1 Answers

Move items to top or bottom based on scroll direction

You can use jQuery's .append() and .prepend() to move items without cloning them.

You'll use similar techniques to infinite scrolling with lazy loading (AJAX), but in this scenario you want to handle scrolling up as well as down, and instead of loading new content from the server, you're just recycling existing DOM elements in the list.

Below I demonstrate one technique. I store the scroll position in the element's .data cache for easy retrieval when detecting scrolling direction. I chose to detect scrolling direction to avoid making unnecessary variable assignments upfront to improve performance. Otherwise, you'd be getting elements and doing math for a scroll event that isn't going to happen in that direction.

The scroll handler:

$('#right-column').on('scroll', function (e) {
    var $this = $(this),
        $results = $("#results"),
        scrollPosition = $this.scrollTop();

    if (scrollPosition > ($this.data('scroll-position') || 0)) {
        // Scrolling down
        var threshold = $results.height() - $this.height() - $('.result:last-child').height();

        if (scrollPosition > threshold) {
            var $firstResult = $('.result:first-child');
            $results.append($firstResult);
            scrollPosition -= $firstResult.height();
            $this.scrollTop(scrollPosition);
        }
    } else {
        // Scrolling up
        var threshold = $('.result:first-child').height();
        if (scrollPosition < threshold) {
            var $lastResult = $('.result:last-child');
            $results.prepend($lastResult);
            scrollPosition += $lastResult.height();
            $this.scrollTop(scrollPosition);
        }
    }
    $this.data('scroll-position', scrollPosition)
});

A complete working example:

$('#right-column').on('scroll', function (e) {
    var $this = $(this),
        $results = $("#results"),
        scrollPosition = $this.scrollTop();

    if (scrollPosition > ($this.data('scroll-position') || 0)) {
        // Scrolling down
        var threshold = $results.height() - $this.height() - $('.result:last-child').height();
      
      	if (scrollPosition > threshold) {
          	var $firstResult = $('.result:first-child');
            $results.append($firstResult);
          	scrollPosition -= $firstResult.height();
            $this.scrollTop(scrollPosition);
        }
    } else {
        // Scrolling up
        var threshold = $('.result:first-child').height();
        if (scrollPosition < threshold) {
            var $lastResult = $('.result:last-child');
            $results.prepend($lastResult);
          	scrollPosition += $lastResult.height();
            $this.scrollTop(scrollPosition);
        }
    }
  	$this.data('scroll-position', scrollPosition)
});

$('#results').on('click', '.result', function (e) {
  	$(this).find('.details').toggle();
});

$('#newNumber').on('input', function (e) {
    var results = '';
    for (var n = 1; n <= $(this).val(); n++) {
        results += 
          '<div class="result" id="result' + n + '">' +
          '    <div class="main">Result ' + n + '</div>' +
          '    <div class="details">Details for result ' + n + '</div>' +
          '</div>';
    }
  	$('#results').html(results);
});
body {
  font-family: sans-serif;
}
h1 {
  font: bold 2rem/1 Georgia, serif;
}
p {
  line-height: 1.5;
  margin-bottom: 1em;
}
label {
  font-weight: bold;
  margin-bottom: 1em;
}
.column {
  box-sizing: border-box;
  float: left;
  width: 50%;
  height: 100vh;
  padding: 1em;
  overflow: auto;
}
#right-column {
  background-color: LemonChiffon;
}
.result {
  padding: 1em;
  cursor: pointer;
}
.result .main {
  height: 2em;
  font-weight: bold;
  line-height: 2;
}
.result .details {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class=
"column" id="left-column">
  <p>Existing DOM elements are moved to the top or bottom of the list depending on your scroll direction.</p>
  <label>Change the number of results to display
    <input id="newNumber" type="number" value="10" />
</div>
<div class=
"column" id="right-column">
  <div id="results">
    <div id="result1" class="result">
      <div class="main">Result 1</div>
      <div class="details">Details for result 1</div>
    </div>
    <div id="result2" class="result">
      <div class="main">Result 2</div>
      <div class="details">Details for result 2</div>
    </div>
    <div id="result3" class="result">
      <div class="main">Result 3</div>
      <div class="details">Details for result 3</div>
    </div>
    <div id="result4" class="result">
      <div class="main">Result 4</div>
      <div class="details">Details for result 4</div>
    </div>
    <div id="result5" class="result">
      <div class="main">Result 5</div>
      <div class="details">Details for result 5</div>
    </div>
    <div id="result6" class="result">
      <div class="main">Result 6</div>
      <div class="details">Details for result 6</div>
    </div>
    <div id="result7" class="result">
      <div class="main">Result 7</div>
      <div class="details">Details for result 7</div>
    </div>
    <div id="result8" class="result">
      <div class="main">Result 8</div>
      <div class="details">Details for result 8</div>
    </div>
    <div id="result9" class="result">
      <div class="main">Result 9</div>
      <div class="details">Details for result 9</div>
    </div>
    <div id="result10" class="result">
      <div class="main">Result 10</div>
      <div class="details">Details for result 10</div>
    </div>
  </div>
</div>

A complete working example on CodePen, if you prefer.

like image 161
gfullam Avatar answered Nov 01 '22 19:11

gfullam