多么奇怪的错误。据我所知,由于第一个元素的 removal/insertion 到列表的末尾,这导致 Chrome 重新渲染列表,从而导致您的 scrollY 偏移量发生变化。
就我个人而言,我可以想到两种方法来解决这个问题。
1) 从 DOM 流中删除 div (#list)。
注意:这需要将您的 #list 元素包装在 containing 元素中。
因为您将元素删除/附加到 #list 元素,这会导致发生回流 (???)。最初,我认为使用 position: absolute 可以解决这个问题,因为您不再影响 DOM 流。但是,即使position: absolute 仍然会导致滚动位置跳跃。
然后我查看了position: fixed,它确实有效(因为它基本上位于所有内容之上) - 这显然不会有效,因为您希望列表随页面滚动!那么,我们该如何解决呢?
transform 救援!
让我们利用transform quirk!通常,当一个元素是position: fixed 时,它是相对于视口的。但是,如果一个祖先元素有一个transformation,则定位为fixed 的元素将相对于转换后的元素。让我们试一试吧!
正如我所提到的,您需要应用一个包装/包含元素来进行转换。在下面的代码 sn-p 中,我用新的 div#transformed 包装了您的 #list 元素。
CSS 更改是直截了当的,如下所示:
#transformed { transform: translateZ(0); }
#list { position: fixed; }
就是这样!查看下面的 sn-p 以了解它的实际效果。
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) 滑动图层
至于解决问题的第二种方法,这更多是关于如何解决这个问题的讨论/意见(因此,很遗憾无法提供代码)。
我不会操纵scrollTop,而是研究一种“滑动”层方法(使用transformY 或top)。
您会发现基于 JS 的轮播采用了类似的方法(很有趣,轮播倾向于使用滚动偏移方法 - 类似于这个问题,或者使用“滑动”层方法!他们只是倾向于这样做水平)
至于您操作的层,我再次建议将其从 DOM 流中删除(所以position: absolute)。理论上,操作这一层应该不会影响滚动偏移量...
无论如何,希望这对您有所帮助,position: fixed 方法对您有用:)
编辑
关于您的评论,您可以通过反转 setInterval 逻辑来实现“向下”滚动。但是,不是滚动然后将刚刚滚动到列表底部的项目移动到列表底部,而是需要将要滚动的元素移动到列表顶部,偏移 scrollTop,然后滚动。
这是一个演示的 sn-p:
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>
编辑#2:
关于向上/向下按钮,您可以通过组合我们目前编写的两个脚本轻松实现!这是两个在单击时向上/向下动画的 sn-ps(第一个会在您每次单击时滚动,第二个 sn-p 会改变动画方向):
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>
还有改变方向的sn-p:
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>