3

I have a terminal-like contentEditable div. I'd like new output to cause it to scroll the window so it is in view, unless the user has manually moved the scroll bar to be viewing another position. In that case, I wish to leave it viewing the position they were at.

I'd prefer to avoid doing this with JavaScript hooks or timer callbacks if at all possible. So I was interested in a promising CSS-only solution of using display: flex with flex-direction: column-reverse;. (A comment on that solution explains that you can avoid the hassle of the reversal of elements by using an outer container with the attribute.)

Borrowing a snippet from another one of those answers, here is a demonstration that this technique works--in my browser--but only for a fixed-size div.

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

setInterval(function() {
    const newElement = document.createElement("div")
    newElement.textContent = "Line #" + (++c)
    inner.appendChild(newElement)
}, 500)
#outer { /* contents of this div are reversed */
    height: 100px;
    display: flex;
    flex-direction: column-reverse;
    overflow: auto;
}
#inner { /* this div has content in normal order */
}
<div id="outer"><div id="inner"></div></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>

But changing that to 100% instead breaks it. Same for height: auto. Is there any magic I can apply to keep the behavior and use 100% height?

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

setInterval(function() {
    const newElement = document.createElement("div")
    newElement.textContent = "Line #" + (++c)
    inner.appendChild(newElement)
}, 500)
#outer { /* contents of this div are reversed */
    height: auto;
    display: flex;
    flex-direction: column-reverse;
    overflow: auto;
}
#inner { /* this div has content in normal order */
}
<div id="outer"><div id="inner"></div></div>
<p>Having changed it to 100%, new content never gets scrolled into view, even if the scroll bar was "stuck" to the bottom</p>

2 Answers2

2

For a flexbox you must specify its dimension along the flex-axis (height in this case as it is a column flexbox) - at least it must inherit an intrinsic width - see demo below where it works when a parent element #wrap has a specified height:

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

setInterval(function() {
  const newElement = document.createElement("div")
  newElement.textContent = "Line #" + (++c)
  inner.appendChild(newElement)
}, 500)

function format() {
  return Array.prototype.slice.call(arguments).join(' ')
}
#wrap {
  height: 100px;
}
#outer {
  /* contents of this div are reversed */
  height: 100%;
  display: flex;
  flex-direction: column-reverse;
  overflow: auto;
}

#inner {
  /* this div has content in normal order */
}
<div id="wrap">
  <div id="outer">
    <div id="inner"></div>
  </div>
  <p>Having changed it to 100%, new content never gets scrolled into view, even if the scroll bar was "stuck" to the bottom</p>
</div>
kukkuz
  • 41,512
  • 6
  • 59
  • 95
  • Is that to say "no you can't do that"? I would like my terminal to be as tall as the window. Can I get that as an intrinsic width in pure CSS somehow, or must it be set with code--that then responds to window sizing events and resets it appropriately? *(Again, what I'm looking for here is a "pure CSS" solution that tackles this aspect of display, it seemed very close.)* – HostileFork says dont trust SE Feb 11 '19 at 09:18
  • yeah, you can't do that... try *viewport* units - vh instead of 100%? `100vh` if you want it as tall as the window :) – kukkuz Feb 11 '19 at 09:20
  • Thanks for the suggestion, but viewport units don't seem to help in my example. Worse is that in my more complex example--even with a fixed height--it also doesn't seem to work once you move the scroll bar. :-( It sticks until you do, but once the scroll bar has moved it won't go back to sticking. I tried adding a scroll bar listener that would "snap" it to the absolute bottom, but it seems once the user has assigned an intent it keeps it...I see no way to go back to a "default" state...and `dir(document.getElementById(...))` in Chrome's debug view shows no special default setting. – HostileFork says dont trust SE Feb 11 '19 at 10:27
  • @HostileFork, *you can't do that* in *flexbox* is what I meant here... that's a nice thread you've linked :) – kukkuz Feb 11 '19 at 13:31
0

I couldn't get this to work using the method I describe in the question. See @kukkuz's answer that "you can't do that... in flexbox", that the fixed size is basically a requirement for it to work. Not only that, but even keeping fixed size I found that the basic example seems to lose its ability to stick to the bottom if you deviate slightly from the code in the snippet I quote.

What I found that did work was to flip it upside down (text upside down, everything), then flip it back in an outer container. The method was suggested by this answer:

https://stackoverflow.com/a/34345634

This transformation makes the browser think the scroll bottom is actually the top. Pinning to the top is easier for it to do than getting pinned to the bottom (since it can be modeled as the constant value 0, as opposed to an ever-growing number).

Pretty weird, but it appears to work in the browsers I've tried!