保持溢出div滚动到底部,除非用户滚动向上

我有一个div,只有300像素大,我希望它在页面加载时滚动到内容的底部。这个div有动态添加的内容,需要一直向下滚动。现在如果用户决定向上滚动,我不希望它跳回底部,直到用户再次向下滚动

是否有可能有一个div,将保持滚动到底部,除非用户向上滚动,当用户滚动回底部时,它需要保持自己在底部,即使添加了新的动态内容。我该怎么做呢。

448323 次浏览

这可能对你有帮助:

var element = document.getElementById("yourDivID");
element.scrollTop = element.scrollHeight;

[编辑],为了匹配注释…

function updateScroll(){
var element = document.getElementById("yourDivID");
element.scrollTop = element.scrollHeight;
}

每当添加内容时,调用函数updateScroll(),或设置一个计时器:

//once a second
setInterval(updateScroll,1000);

如果你只想在用户没有移动的情况下更新:

var scrolled = false;
function updateScroll(){
if(!scrolled){
var element = document.getElementById("yourDivID");
element.scrollTop = element.scrollHeight;
}
}


$("#yourDivID").on('scroll', function(){
scrolled=true;
});
$('#yourDiv').scrollTop($('#yourDiv')[0].scrollHeight);

现场演示:http://jsfiddle.net/KGfG2/

$('#div1').scrollTop($('#div1')[0].scrollHeight);


Or animated:


$("#div1").animate({ scrollTop: $('#div1')[0].scrollHeight}, 1000);

我刚刚实现了这个,也许你可以用我的方法。

假设我们有以下HTML:

<div id="out" style="overflow:auto"></div>

然后我们可以检查它是否滚动到底部:

var out = document.getElementById("out");
// allow 1px inaccuracy by adding 1
var isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1;

scrollHeight给出了元素的高度,包括任何由于溢出而不可见的区域。clientHeight给你CSS的高度,或者换句话说,元素的实际高度。这两个方法都返回不带margin的高度,所以你不必担心。scrollTop给出垂直滚动的位置。0是top, max是元素的scrollHeight减去元素本身的高度。当使用滚动条时,它可能很难(对我来说是在Chrome中)让滚动条一直到底部。所以我添加了1px的误差。因此,即使滚动条距离底部1px, isScrolledToBottom也将为真。你可以把它设置成你觉得合适的样子。

然后,只需将元素的scrollTop设置为底部即可。

if(isScrolledToBottom)
out.scrollTop = out.scrollHeight - out.clientHeight;

我已经为你做了一个小提琴来展示这个概念:http://jsfiddle.net/dotnetCarpenter/KpM5j/

< p >编辑: 增加了代码片段,以澄清当isScrolledToBottomtrue.

将滚动条粘贴到底部

const out = document.getElementById("out")
let c = 0


setInterval(function() {
// allow 1px inaccuracy by adding 1
const isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1


const newElement = document.createElement("div")


newElement.textContent = format(c++, 'Bottom position:', out.scrollHeight - out.clientHeight,  'Scroll position:', out.scrollTop)


out.appendChild(newElement)


// scroll to bottom if isScrolledToBottom is true
if (isScrolledToBottom) {
out.scrollTop = out.scrollHeight - out.clientHeight
}
}, 500)


function format () {
return Array.prototype.slice.call(arguments).join(' ')
}
#out {
height: 100px;
}
<div id="out" style="overflow:auto"></div>
<p>To be clear: We want the scrollbar to stick to the bottom if we have scrolled all the way down. If we scroll up, then we don't want the content to move.
</p>

$('#yourDivID').animate({ scrollTop: $(document).height() }, "slow");
return false;

这将使用$(document).height()属性从#yourDivID的高度计算ScrollTop位置,这样即使将动态内容添加到div,滚动条也将始终位于底部位置。希望这能有所帮助。但它也有一个小错误,即使我们向上滚动并将鼠标指针从滚动条上保留下来,它也会自动回到底部位置。如果有人能纠正这一点,那就太好了。

//Make sure message list is scrolled to the bottom
var container = $('#MessageWindowContent')[0];
var containerHeight = container.clientHeight;
var contentHeight = container.scrollHeight;


container.scrollTop = contentHeight - containerHeight;

以下是我基于dotnetCarpenter回答的版本。我的方法是一个纯jQuery和我命名的变量,使事情更清楚一点。发生的情况是,如果内容高度大于容器高度,我们向下滚动额外的距离以实现所需的结果。

工作在IE和chrome..

我能够得到这个工作与CSS只。

诀窍在于:

display: flex;
flex-direction: column-reverse;

浏览器将底部视为顶部。假设你的目标浏览器支持flex-box,唯一需要注意的是标记的顺序必须相反。

下面是一个工作示例。https://codepen.io/jimbol/pen/YVJzBg

Jim Hall的答案更可取,因为当你向上滚动时,它确实不会滚动到底部,它也是纯CSS。

然而,非常不幸的是,这不是一个稳定的解决方案:在chrome中(可能是由于上面dotnetCarpenter描述的1像素问题),scrollTop行为不准确1像素,即使没有用户交互(在元素添加时)。你可以设置scrollTop = scrollHeight - clientHeight,但当另一个元素被添加时,这将保持div在位置上,也就是“保持自身在底部”功能不再工作。

所以,简而言之,添加少量Javascript(唉)将修复这个问题并满足所有要求:

类似https://codepen.io/anon/pen/pdrLEZ this(示例by Coo),在向列表中添加元素后,还可以执行以下操作:

container = ...
if(container.scrollHeight - container.clientHeight - container.scrollTop <= 29) {
container.scrollTop = container.scrollHeight - container.clientHeight;
}

29是直线的高度。

因此,当用户向上滚动半行时(如果可能的话?),Javascript将忽略它并滚动到底部。但我想这是可以忽略的。而且,它修复了Chrome 1px的东西。

.cont {
height: 100px;
overflow-x: hidden;
overflow-y: auto;
transform: rotate(180deg);
direction: rtl;
text-align: left;
}
ul {
overflow: hidden;
transform: rotate(180deg);
}
<div class="cont">
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
</div>

单击Run code snippet查看效果。(如果Run code snippet不工作,试试这个:https://jsfiddle.net/Yeshen/xm2yLksu/3/。)

工作原理:默认情况下溢出滚动是从上到下。transform: rotate(180deg)反转了这一点,因此滚动或加载动态块是从下到上的。

最初的想法:https://blog.csdn.net/yeshennet/article/details/88880252

下面是一个基于瑞安·亨特的一篇博客文章的解决方案。它依赖于overflow-anchor CSS属性,该属性将滚动位置固定在滚动内容底部的元素上。

function addMessage() {
const $message = document.createElement('div');
$message.className = 'message';
$message.innerText = `Random number = ${Math.ceil(Math.random() * 1000)}`;
$messages.insertBefore($message, $anchor);


// Trigger the scroll pinning when the scroller overflows
if (!overflowing) {
overflowing = isOverflowing($scroller);
$scroller.scrollTop = $scroller.scrollHeight;
}
}


function isOverflowing($el) {
return $el.scrollHeight > $el.clientHeight;
}


const $scroller = document.querySelector('.scroller');
const $messages = document.querySelector('.messages');
const $anchor = document.querySelector('.anchor');
let overflowing = false;


setInterval(addMessage, 1000);
.scroller {
overflow: auto;
height: 90vh;
max-height: 11em;
background: #555;
}


.messages > * {
overflow-anchor: none;
}


.anchor {
overflow-anchor: auto;
height: 1px;
}


.message {
margin: .3em;
padding: .5em;
background: #eee;
}
<section class="scroller">
<div class="messages">
<div class="anchor"></div>
</div>
</section>

注意overflow-anchor目前在Safari中不工作。

以下是我的做法。div的高度是650px。我决定如果滚动高度在底部的150px范围内,那么自动滚动它。否则,留给用户。

if (container_block.scrollHeight - container_block.scrollTop < 800) {
container_block.scrollTo(0, container_block.scrollHeight);
}

你可以用这样的东西,

var element = document.getElementById("yourDivID");
window.scrollTo(0,element.offsetHeight);

在2020年,你可以使用css快速,但在Chrome 81之前,布局变化将不触发重扣,一个纯CSS聊天UI在Chrome 81上工作,你也可以检查我可以使用CSS snap吗

这个演示将捕捉最后一个元素(如果可见),滚动到底部查看效果。

.container {
overflow-y: scroll;
overscroll-behavior-y: contain;
scroll-snap-type: y proximity;
}


.container > div > div:last-child {
scroll-snap-align: end;
}


.container > div > div {
background: lightgray;
height: 3rem;
font-size: 1.5rem;
}
.container > div > div:nth-child(2n) {
background: gray;
}
<div class="container" style="height:6rem">
<div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
</div>

enter image description here

编辑

使用scroll-snap-type: y proximity;,更容易向上滚动。

我不能让前两个答案工作,其他答案都对我没有帮助。所以我从Reddit r / forhireUpwork上给了三个人30美元,得到了一些非常好的答案。这个答案可以帮你省下90美元。



Justin Hundley / The Site Bros的解决方案

超文本标记语言

<div id="chatscreen">
<div id="inner">
  

</div>
</div>

CSS

#chatscreen {
width: 300px;
overflow-y: scroll;
max-height:100px;
}

Javascript

$(function(){
var scrolled = false;
var lastScroll = 0;
var count = 0;
$("#chatscreen").on("scroll", function() {
var nextScroll = $(this).scrollTop();


if (nextScroll <= lastScroll) {
scrolled = true;
}
lastScroll = nextScroll;
    

console.log(nextScroll, $("#inner").height())
if ((nextScroll + 100) == $("#inner").height()) {
scrolled = false;
}
});
 

function updateScroll(){
if(!scrolled){
var element = document.getElementById("chatscreen");
var inner = document.getElementById("inner");
element.scrollTop = inner.scrollHeight;
}
}


// Now let's load our messages
function load_messages(){
$( "#inner" ).append( "Test" + count + "<br/>" );
count = count + 1;
updateScroll();
}


setInterval(load_messages,300);
});

预览网站兄弟的解决方案

portfolio



Lermex / Sviatoslav Chumakov的解决方案

超文本标记语言

<div id="chatscreen">


</div>

CSS

#chatscreen {
height: 300px;
border: 1px solid purple;
overflow: scroll;
}

Javascript

$(function(){
var isScrolledToBottom = false;
// Now let's load our messages
function load_messages(){
$( "#chatscreen" ).append( "<br>Test" );
updateScr();
}


var out = document.getElementById("chatscreen");
var c = 0;


$("#chatscreen").on('scroll', function(){
console.log(out.scrollHeight);
isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 10;
});


function updateScr() {
// allow 1px inaccuracy by adding 1
//console.log(out.scrollHeight - out.clientHeight,  out.scrollTop + 1);
var newElement = document.createElement("div");


newElement.innerHTML = c++;
out.appendChild(newElement);
    

console.log(isScrolledToBottom);


// scroll to bottom if isScrolledToBotto
if(isScrolledToBottom) {out.scrollTop = out.scrollHeight - out.clientHeight; }
}


var add = setInterval(updateScr, 1000);


setInterval(load_messages,300); // change to 300 to show the latest message you sent after pressing enter // comment this line and it works, uncomment and it fails
// leaving it on 1000 shows the second to last message
setInterval(updateScroll,30);
});

预览Sviatoslav的解决方案

portfolio



伊戈尔·鲁西诺夫的解决方案

超文本标记语言

<div id="chatscreen"></div>

CSS

#chatscreen {
height: 100px;
overflow: scroll;
border: 1px solid #000;
}

Javascript

$(function(){


// Now let's load our messages
function load_messages(){
$( "#chatscreen" ).append( "<br>Test" );
}


var out = document.getElementById("chatscreen");
var c = 0;
var add = setInterval(function() {
// allow 1px inaccuracy by adding 1
var isScrolledToBottom = out.scrollHeight - out.clientHeight <= out.scrollTop + 1;
load_messages();


// scroll to bottom if isScrolledToBotto
if(isScrolledToBottom) {out.scrollTop = out.scrollHeight - out.clientHeight; }
}, 1000);
setInterval(updateScroll,30);
});

预览Igor的解决方案

< a href = " https://rusinov。Me /" rel="nofollow noreferrer">portfolio .

以下是你所需要的(我尽了最大的努力,一路上进行了大量的谷歌搜索):

<html>
<head>
<script>
// no jquery, or other craziness. just
// straight up vanilla javascript functions
// to scroll a div's content to the bottom
// if the user has not scrolled up.  Includes
// a clickable "alert" for when "content" is
// changed.


// this should work for any kind of content
// be it images, or links, or plain text
// simply "append" the new element to the
// div, and this will handle the rest as
// proscribed.


let scrolled = false; // at bottom?
let scrolling = false; // scrolling in next msg?
let listener = false; // does element have content changed listener?
let contentChanged = false; // kind of obvious
let alerted = false; // less obvious


function innerHTMLChanged() {
// this is here in case we want to
// customize what goes on in here.
// for now, just:
contentChanged = true;
}


function scrollToBottom(id) {
if (!id) { id = "scrollable_element"; }
let DEBUG = 0; // change to 1 and open console
let dstr = "";


let e = document.getElementById(id);
if (e) {
if (!listener) {
dstr += "content changed listener not active\n";
e.addEventListener("DOMSubtreeModified", innerHTMLChanged);
listener = true;
} else {
dstr += "content changed listener active\n";
}
let height = (e.scrollHeight - e.offsetHeight); // this isn't perfect
let offset = (e.offsetHeight - e.clientHeight); // and does this fix it? seems to...
let scrollMax = height + offset;


dstr += "offsetHeight: " + e.offsetHeight + "\n";
dstr += "clientHeight: " + e.clientHeight + "\n";
dstr += "scrollHeight: " + e.scrollHeight + "\n";
dstr += "scrollTop: " + e.scrollTop + "\n";
dstr += "scrollMax: " + scrollMax + "\n";
dstr += "offset: " + offset + "\n";
dstr += "height: " + height + "\n";
dstr += "contentChanged: " + contentChanged + "\n";


if (!scrolled && !scrolling) {
dstr += "user has not scrolled\n";
if (e.scrollTop != scrollMax) {
dstr += "scroll not at bottom\n";
e.scroll({
top: scrollMax,
left: 0,
behavior: "auto"
})
e.scrollTop = scrollMax;
scrolling = true;
} else {
if (alerted) {
dstr += "alert exists\n";
} else {
dstr += "alert does not exist\n";
}
if (contentChanged) { contentChanged = false; }
}
} else {
dstr += "user scrolled away from bottom\n";
if (!scrolling) {
dstr += "not auto-scrolling\n";


if (e.scrollTop >= scrollMax) {
dstr += "scroll at bottom\n";
scrolled = false;


if (alerted) {
dstr += "alert exists\n";
let n = document.getElementById("alert");
n.remove();
alerted = false;
contentChanged = false;
scrolled = false;
}
} else {
dstr += "scroll not at bottom\n";
if (contentChanged) {
dstr += "content changed\n";
if (!alerted) {
dstr += "alert not displaying\n";
let n = document.createElement("div");
e.append(n);
n.id = "alert";
n.style.position = "absolute";
n.classList.add("normal-panel");
n.classList.add("clickable");
n.classList.add("blink");
n.innerHTML = "new content!";


let nposy = parseFloat(getComputedStyle(e).height) + 18;
let nposx = 18 + (parseFloat(getComputedStyle(e).width) / 2) - (parseFloat(getComputedStyle(n).width) / 2);
dstr += "nposx: " + nposx + "\n";
dstr += "nposy: " + nposy + "\n";
n.style.left = nposx;
n.style.top = nposy;


n.addEventListener("click", () => {
dstr += "clearing alert\n";
scrolled = false;
alerted = false;
contentChanged = false;
n.remove();
});


alerted = true;
} else {
dstr += "alert already displayed\n";
}
} else {
alerted = false;
}
}
} else {
dstr += "auto-scrolling\n";
if (e.scrollTop >= scrollMax) {
dstr += "done scrolling";
scrolling = false;
scrolled = false;
} else {
dstr += "still scrolling...\n";
}
}
}
}


if (DEBUG && dstr) console.log("stb:\n" + dstr);


setTimeout(() => { scrollToBottom(id); }, 50);
}


function scrollMessages(id) {
if (!id) { id = "scrollable_element"; }
let DEBUG = 1;
let dstr = "";


if (scrolled) {
dstr += "already scrolled";
} else {
dstr += "got scrolled";
scrolled = true;
}
dstr += "\n";


if (contentChanged && alerted) {
dstr += "content changed, and alerted\n";
let n = document.getElementById("alert");
if (n) {
dstr += "alert div exists\n";
let e = document.getElementById(id);
let nposy = parseFloat(getComputedStyle(e).height) + 18;
dstr += "nposy: " + nposy + "\n";
n.style.top = nposy;
} else {
dstr += "alert div does not exist!\n";
}
} else {
dstr += "content NOT changed, and not alerted";
}


if (DEBUG && dstr) console.log("sm: " + dstr);
}


setTimeout(() => { scrollToBottom("messages"); }, 1000);


/////////////////////
// HELPER FUNCTION
//   simulates adding dynamic content to "chat" div
let count = 0;
function addContent() {
let e = document.getElementById("messages");
if (e) {
let br = document.createElement("br");
e.append("test " + count);
e.append(br);
count++;
}
}
</script>


<style>
button {
border-radius: 5px;
}


#container {
padding: 5px;
}


#messages {
background-color: blue;
border: 1px inset black;
border-radius: 3px;
color: white;
padding: 5px;
overflow-x: none;
overflow-y: auto;
max-height: 100px;
width: 100px;
margin-bottom: 5px;
text-align: left;
}


.bordered {
border: 1px solid black;
border-radius: 5px;
}


.inline-block {
display: inline-block;
}


.centered {
text-align: center;
}


.normal-panel {
background-color: #888888;
border: 1px solid black;
border-radius: 5px;
padding: 2px;
}


.clickable {
cursor: pointer;
}
</style>
</head>
<body>
<div id="container" class="bordered inline-block centered">
<div class="inline-block">My Chat</div>


<div id="messages" onscroll="scrollMessages('messages')">
test<br>
test<br>
test<br>
test<br>
test<br>
test<br>
test<br>
test<br>
test<br>
test<br>
</div>


<button onclick="addContent();">Add Content</button>
</div>
</body>
</html>

注意:你可能需要调整scrollToBottomscrollMessages中的警报位置(nposxnposy)来满足你的需要…

还有一个链接到我自己的工作示例,托管在我的服务器上:https://night-stand.ca/jaretts_tests/chat_scroll.html

我设法把它修好了。诀窍是计算:(a)当前div用户滚动位置和(b) div滚动高度,都是在附加新元素之前。

如果a === b,则在追加新元素之前就知道用户位于底部。

    let div = document.querySelector('div.scrollableBox');


let span = document.createElement('span');
span.textContent = 'Hello';


let divCurrentUserScrollPosition = div.scrollTop + div.offsetHeight;
let divScrollHeight = div.scrollHeight;


// We have the current scroll positions saved in
// variables, so now we can append the new element.
div.append(span);


    

if ((divScrollHeight === divCurrentUserScrollPosition)) {
// Scroll to bottom of div
div.scrollTo({ left: 0, top: div.scrollHeight });
}

我试着用Bootstrap 5来做同样的事情。我正在编写的页面是一个单窗口html工具,我想要两列具有可滚动内容,其中一列需要反向,因为它是一个日志(另一列不太可能滚动,除非故意这么做)。列表和它们的标题也是底部锚定的,我很难让标题保持在一个灵活的可滚动列表的顶部。

多亏了上面的例子,我才能够找出我所缺少的内容并获得正确的类类型。

这是我的完整的示例。在我实际的应用程序中,有其他两个类mh-100 col overflow-auto的第三列,不需要内部行/列,因为没有标题贴在顶部(如果视口太小,它只会正常滚动)。列表有一个ID,我用来选择和添加到它们前面或删除顶部元素(这是反向列表的底部<li>项)。

这里提供了一个较小的版本:

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<div class="vh-100 w-75 container-fluid">
<h1>2nd Level Scrolling Example</h1>
<div class="h-75 row align-items-end">
<div class="mh-100 col d-flex flex-column">
<div class="row align-items-end">
<div class="col"><h3>Normal scroll list, grow on top</h3></div>
</div>
<div class="row align-items-end overflow-auto">
<div class="mh-100 col">
<ul class="list-group">
<li>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ut</li>
<li>tortor eu ex tincidunt pretium non eu nisl. Ut eu libero ac velit</li>
<li>ultricies dapibus. Donec id augue scelerisque, gravida est ut,</li>
<li>commodo sapien. Interdum et malesuada fames ac ante ipsum primis</li>
<li>in faucibus. Suspendisse volutpat fermentum finibus. Cras egestas</li>
<li>tempor tempor. Suspendisse potenti. Mauris ac tellus ultrices lectus</li>
<li>accumsan pellentesque. Nullam semper, nisi nec euismod ultrices, leo</li>
<li>sem bibendum sapien, in rutrum sapien massa id mi.</li>
</ul>
</div>
</div>
</div>
<div class="mh-100 col d-flex flex-column">
<div class="row align-items-end">
<div class="col"><h3>Reverse scroll list, grow on bottom</h3></div>
</div>
<div class="row align-items-end d-flex flex-column-reverse overflow-auto">
<div class="mh-100 col">
<ul class="list-group">
<li>sem bibendum sapien, in rutrum sapien massa id mi.</li>
<li>accumsan pellentesque. Nullam semper, nisi nec euismod ultrices, leo</li>
<li>tempor tempor. Suspendisse potenti. Mauris ac tellus ultrices lectus</li>
<li>in faucibus. Suspendisse volutpat fermentum finibus. Cras egestas</li>
<li>commodo sapien. Interdum et malesuada fames ac ante ipsum primis</li>
<li>ultricies dapibus. Donec id augue scelerisque, gravida est ut,</li>
<li>tortor eu ex tincidunt pretium non eu nisl. Ut eu libero ac velit</li>
<li>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ut</li>
</ul>
</div>
</div>
</div>
</div>
</div>

如果你的视口高度小于整体内容,标题应该位于列表的顶部,而所有内容都位于页面的底部(实际上是视口高度的75%,但在这个例子中,标题并没有占据它设计的空间)。

注:我不是一个真正的web开发人员,只是编写一些方便的基于html的工具来处理日常工作,所以非常欢迎评论。

基于吉姆霍尔斯的解决方案和意见。https://stackoverflow.com/a/44051405/9208887

我另外添加了一个带有flex 1 1 0%的元素,以确保当容器未满时,文本从容器顶部开始。

// just to add some numbers, so we can see the effect
// the actual solution requires no javascript
let num = 1001;
const container = document.getElementById("scroll-container");
document.getElementById("adder").onclick = () =>
container.append(
Object.assign(document.createElement("div"), {
textContent: num++
})
);
.scroll-wrapper {
height: 100px;
overflow: auto;
display: flex;
flex-direction: column-reverse;
border: 1px solid black;
}


.scroll-start-at-top {
flex: 1 1 0%;
}
<div class="scroll-wrapper">
<span class="scroll-start-at-top"></span>
<div id="scroll-container">
<div>1000</div>
</div>
</div>


<button id="adder">Add Text</button>

我发现最友好的解决方案是结合scroll-snap-align方法和一点Javascript。前一种解决方案本身的问题是,抓拍太强了,你必须滚动很远才能摆脱它。

相反,我们可以在容器滚动到底部时使用捕捉动态,然后在用户向上滚动超过某个阈值时禁用它。

这个解决方案还有一个额外的好处,那就是它是一个渐进的增强:如果用户禁用了Javascript,它就会退回到只使用css的方法。

const container = document.getElementById("container");
const snap = document.getElementById("snap");


// Scroll the view to the bottom once initially
container.scrollTop = container.scrollHeight;


container.addEventListener("scroll", (event) => {
const target = event.currentTarget;
const scroll = target.scrollTop;
const maxScroll = target.scrollHeight - target.clientHeight;
const threshold = 50; // px
isScrollBottomedOut = maxScroll - scroll < threshold;
// If the user scrolls up more than the threshold, disable snapping
// If the user scrolls down again, reenable snapping
snap.style.display = isScrollBottomedOut ? "block" : "none";
});
#container {
width: 200px;
height: 500px;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
-ms-scroll-chaining: none;
overscroll-behavior: contain;
-ms-scroll-snap-type: y proximity;
scroll-snap-type: y proximity;
border: 2px solid black;
}


#snap {
scroll-snap-align: end;
}
<div id="container">
<ol>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
</ol>
<!-- This is the snapping target, if visible -->
<div id="snap"></div>
</div>

这个问题有原生的支持。

有一个方法叫做 *.scrollIntoView。 在运行此方法一次之后,它将容器滚动保持在底部。 即使在容器中添加了新内容,它也会滚动到底部
import {
AfterViewInit,
Directive,
ElementRef,
} from '@angular/core';


@Directive({
selector: '[aeScrollIntoView]',
})
export class ScrollIntoViewDirective implements AfterViewInit {
constructor(private readonly el: ElementRef<HTMLDivElement>) {}
ngAfterViewInit(): void {
this.el.nativeElement.scrollIntoView({ behavior: 'smooth' });
}
}

<div aeScrollIntoView>
Your long and dynamic content.
Whenever new content is added to this container, it scrolls to the bottom.
<div>