6

I'm creating vertically auto scrolling divs using jquery with animation... I have finished creating it but the only problem is when I scroll down the window to the middle of list div where divs are scrolling, it pushes the window up to the scrolling list div. I don't know what the problem is. However when I try to give the list div width in pixels, it is not pushing up...

Try to scroll down to the middle of the scrolling list div. You will understand what the problem is. Thanks...

setInterval(function(){
  $('#list').stop().animate({scrollTop:40}, 400, 'swing', function(){
    $(this).scrollTop(0).find('div:last').after($('div:first', this));
  });
}, 1000);
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#list {
  overflow: hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list div {
  display: block;
  height: 30px;
  padding: 10px 10px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="list">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
  <div>Item 5</div>
  <div>Item 6</div>
  <div>Item 7</div>
  <div>Item 8</div>
  <div>Item 9</div>
  <div>Item 10</div>   
</div>
Shikkediel
  • 5,195
  • 16
  • 45
  • 77
Inam
  • 241
  • 2
  • 6
  • 20

2 Answers2

3

What an odd bug. From what I can tell, because of the removal/insertion of the first element to the end of the list, this results in Chrome re-rendering the list causing the change in your scrollY offset.

Personally, I can think of two ways to address the issue.

1) Remove the div (#list) from the DOM flow.

Note: This requires wrapping your #list element in a containing element.

Because your removing/appending elements to your #list element, this is causing a reflow to occur (???). Initially, I thought using position: absolute would address this, as you're no longer affecting the DOM flow. However, even position: absolute still causes the scroll position to jump.

I then looked at position: fixed, which DOES work (as it's basically sitting on top of everything) - This obviously isn't going to work as you want the list to scroll with the page! So, how can we get around this??

transform to the rescue!

Let's take advantage of a transform quirk! Usually, when an element is position: fixed, it's relative to the viewport. However, if an ancestor element has a transformation, the element that is fixed positioned will be relative to the transformed element. Let's give this a shot!

As I mentioned, you'll need to apply a wrapping/containing element to transform. In the code snippet below, I've wrapped your #list element with a new div#transformed.

CSS changes are straight forward and look like:

#transformed { transform: translateZ(0); }
#list { position: fixed; }

And that is it! Check out the snippet below to see it in action.

setInterval(function(){
  $('#list').stop().animate({scrollTop:40}, 400, 'swing', function(){
    $(this).find('div:last').after($('div:first', this));
  });
}, 1000);
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#transformed {
  transform: translateZ(0);
}

#list {
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list div {
  display: block;
  height: 30px;
  padding: 10px 10px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="transformed">
  <div id="list">
    <div>Item 1</div>
    <div>Item 2</div>
    <div>Item 3</div>
    <div>Item 4</div>
    <div>Item 5</div>
    <div>Item 6</div>
    <div>Item 7</div>
    <div>Item 8</div>
    <div>Item 9</div>
    <div>Item 10</div>   
  </div>
</div>

2) Sliding a layer

As for the second way to address the issue, this is more a discussion/opinion on how to approach this (so, unfortunately can't provide code).

Rather than manipulate the scrollTop, I would look into a "sliding" layer approach (either using transformY or top).

You'll find JS-based carousels take a similar approach (funny enough, carousels tend to either use a scroll-offset approach - as similar to this question, or a "sliding" layer approach! They just tend to do it horizontally)

As for the layer you manipulate, again I would suggest removing it from the DOM flow (so position: absolute). In theory, manipulating this layer shouldn't affect the scroll offset...

Anyway, hope this was helpful and the position: fixed approach works for you :)

Edit

Regarding your comment, you can achieve a "downward" scroll by reversing your setInterval logic. However, instead of scrolling and then moving the item that just scrolled out to the bottom of the list, you will need to move the element you want to scroll in to the top of the list, offset the scrollTop, and then scroll in.

Here's a snippet that demonstrates:

setInterval(function(){
    $('#list')
        .find('div:first')
        .before($('div:last', '#list'))
        .end()
        .scrollTop(40)
        .stop()
    .animate({scrollTop:0}, 400, 'swing');
}, 1000);
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#transformed {
  transform: translateZ(0);
}

#list {
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list div {
  display: block;
  height: 30px;
  padding: 10px 10px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="transformed">
  <div id="list">
    <div>Item 1</div>
    <div>Item 2</div>
    <div>Item 3</div>
    <div>Item 4</div>
    <div>Item 5</div>
    <div>Item 6</div>
    <div>Item 7</div>
    <div>Item 8</div>
    <div>Item 9</div>
    <div>Item 10</div>
  </div>
</div>

Edit # 2:

Regarding up/down buttons, you can easily achieve that by combining the two scripts we've written so far! Here's are two snippets that animates up/down when you click (the first will scroll each time you click, and the second snippet will change the animation direction):

function animateList(direction) {
if (direction === 'down') {
    $('#list')
        .find('div:first')
        .before($('div:last', '#list'))
        .end()
        .scrollTop(40)
        .stop()
        .animate({scrollTop:0}, 400, 'swing');
} else {
    $('#list')
        .animate({scrollTop:40}, 400, 'swing', function(){
            $(this)
                .find('div:last')
                .after($('div:first', this));
        });
}
}

$('button').on('click', function () {
var direction = $(this).attr('id');
animateList(direction);
});
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#transformed {
  transform: translateZ(0);
}

#list {
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list div {
  display: block;
  height: 30px;
  padding: 10px 10px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="transformed">
  <div id="list">
    <div>Item 1</div>
    <div>Item 2</div>
    <div>Item 3</div>
    <div>Item 4</div>
    <div>Item 5</div>
    <div>Item 6</div>
    <div>Item 7</div>
    <div>Item 8</div>
    <div>Item 9</div>
    <div>Item 10</div>
  </div>
</div>

<button id="up">Scroll Up</button>
<button id="down">Scroll Down</button>

And the snippet that changes the direction:

var interval;
function animateList(direction) {
    // Reset interval
    if (interval) {
        clearInterval(interval);
    }
    if (direction === 'down') {
        interval = setInterval(function () {
            $('#list')
                .find('div:first')
                .before($('div:last', '#list'))
                .end()
                .scrollTop(40)
                .stop()
                .animate({scrollTop:0}, 400, 'swing');
        }, 1000);
    } else {
        interval = setInterval(function () {
            $('#list')
                .animate({scrollTop:40}, 400, 'swing', function(){
                    $(this)
                        .find('div:last')
                        .after($('div:first', this));
                });

        }, 1000);
    }
}
$('button').on('click', function () {
    var direction = $(this).attr('id');
    animateList(direction);
});

// Initial animation
animateList('up');
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#transformed {
  transform: translateZ(0);
}

#list {
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list div {
  display: block;
  height: 30px;
  padding: 10px 10px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="transformed">
  <div id="list">
    <div>Item 1</div>
    <div>Item 2</div>
    <div>Item 3</div>
    <div>Item 4</div>
    <div>Item 5</div>
    <div>Item 6</div>
    <div>Item 7</div>
    <div>Item 8</div>
    <div>Item 9</div>
    <div>Item 10</div>
  </div>
</div>

<button id="up">Scroll Up</button>
<button id="down">Scroll Down</button>
Jack
  • 9,151
  • 2
  • 32
  • 44
  • Thank you sir for your solution it fixed my problem but one more issue is that it is scrolling upward but how to scroll it downward ? – Inam May 03 '19 at 12:13
  • 1
    HI @Inam, glad to hear it fixed your problem. I've updated the answer to include downward scrolling :) – Jack May 04 '19 at 00:53
  • Thank you sir but how to make it work with up and down buttons ? – Inam May 04 '19 at 05:20
  • Hi @Inam, I've updated the answer to include up/down buttons. Quite simply, you combine the two scripts we've written so far and based on which button you click, it will call either the "up" logic or the "down" logic. I've added two different examples - one where you click and it animates once, and another where you click and it will change the direction of the automated scrolling. Hope this helps! – Jack May 04 '19 at 06:55
  • 1
    Thank you sir .. this is what i needed :) – Inam May 04 '19 at 08:55
2

I have converted your inner divs to a unordered list. I added an overflow-y:scroll initially, which also worked, but changed it to overflow-y:hidden on seeing your comment. I found but both work. If you scroll down the page and then scroll back up, it doesn't 'start again' (unless it's had the time to iterate though all 10 list items). You may need to adjust the css/box height to get the red border at the bottom, but I'll leave this to you

Hope this helps

Overflow-hidden:

setInterval(function() {
  $('#ulList').stop().animate({
    scrollTop: 40
  }, 400, 'swing', function() {
    $(this).scrollTop(0).find('li:last').after($('li:first', this));
  });
}, 1000);
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#list {
  overflow-y:hidden;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list ul {
  margin: 10px 10px;
  padding: 10px 0px;
}

#list ul li {
  display: block;
  height: 30px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="list">
  <ul id="ulList">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
    <li>Item 6</li>
    <li>Item 7</li>
    <li>Item 8</li>
    <li>Item 9</li>
    <li>Item 10</li>
  </ul>

</div>

Overflow:scroll

setInterval(function() {
  $('#ulList').stop().animate({
    scrollTop: 40
  }, 400, 'swing', function() {
    $(this).scrollTop(0).find('li:last').after($('li:first', this));
  });
}, 1000);
* {
  box-sizing: border-box;
}

body {
  height: 1000px;
}

#list {
  overflow-y:scroll;
  width: 100%;
  height: 250px;
  background: red;
  padding: 10px;
  margin-top: 100px;
}

#list ul {
  margin: 10px 10px;
  padding: 10px 0px;
}

#list ul li {
  display: block;
  height: 30px;
  margin-bottom: 10px;
  background: yellow;
}

.item:last-child {
  margin-bottom: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="list">
  <ul id="ulList">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
    <li>Item 6</li>
    <li>Item 7</li>
    <li>Item 8</li>
    <li>Item 9</li>
    <li>Item 10</li>
  </ul>

</div>
Rachel Gallen
  • 27,943
  • 21
  • 72
  • 81
  • 4
    by the way, I'm not a 'sir', but I appreciate the polite manner :) – Rachel Gallen Apr 26 '19 at 19:13
  • oh sorry ma'm Thank you ... But still there is a problem try to position your window(main document window scroller) in the middle of scrolling list divs the main document window scroller automatically moves up as the scrolling li moves up .. you didn't understand my problem ma'm – Inam Apr 26 '19 at 19:21
  • @Inam oh I know what you mean now. That's happening because it's continuously animating. You could probably fix the top position of the ul, this might be a workaround (but as I haven't tried it, I don't know how it would affect the animation). Tricky though. – Rachel Gallen Apr 26 '19 at 20:02
  • yes ma'm ... it is working fine in firefox but not in chrome and opera I m working on it for about 2 -3 days but failed to fix it .. i tried different plugins as well but that is also giving me same problem .. it might be the browser issue . – Inam Apr 26 '19 at 20:07
  • if it's working on other browsers I would try disabling extensions in Chrome and retrying. Have you tried using position:sticky;? – Rachel Gallen Apr 26 '19 at 20:13
  • i tried every possible solution none of it solved the problem including position:sticky – Inam Apr 26 '19 at 20:17
  • @Inam I would recommend clearing your cache also - full of non-working code that may be affecting the appearance of your current attempts. And do disable your extensions. – Rachel Gallen Apr 26 '19 at 20:19
  • Thank you ma'm for your help if you find anything related to this issue then kindly update me Thanks :) – Inam Apr 26 '19 at 20:21
  • @Inam Sure thing. Let me know how you go after clearing your chrome cache. Sometimes it is the answer! – Rachel Gallen Apr 26 '19 at 20:22
  • I cleared cache and disabled all the extensions but still same problem – Inam Apr 26 '19 at 20:28
  • @Inam it's possible that it could be a bug in Chrome (if it's working in other browsers). I would log it. Try position:absolute and fixed first though. And specify a ``top`` position. See how you go. I'm taking a break now for 15 mins but I'll be back – Rachel Gallen Apr 26 '19 at 20:31
  • I tried position absolute on div where scrolling items are but it didn't work but when i give postion absolute to the parent div of scrolling item div as i m using it in my project then position absolute disturbed my whole design so i didn't go with it however last choice i m left with is to change width of div to 100vw instead of 100% as it is working fine with it – Inam Apr 26 '19 at 20:34
  • @Inam a final suggestion.. make the div slightly bigger (310px) , and set the overflow-y to hidden. that way you can see the bottom red margin and the items vertically scrolling. The only thing you will achieve by changing the width from % to hw is you will make the scrollbar harder to see (it will slightly farther over).. so you might as well set it to hidden. My last thought on the matter – Rachel Gallen Apr 26 '19 at 21:06