I have a ul
with a max-height
and overflow-y: auto
.
When the user enters enough li
elements, the ul
starts to scroll, but I want the last li
with the form
in it to always be present and viewable to the user.
I've tried implementing a scrollToBottom
function that looks like this:
scrollToBottom() { this.formLi.scrollIntoView({ behavior: 'smooth' }); }
But that just makes the ul
jump to the top of the screen and show the li
with the form in it as the only visible thing.
Is there a better way to accomplish this? Answers I find tend to be a bit older and use ReactDOM. Thanks!
CSS:
.prompt-box .options-holder { list-style: none; padding: 0; width: 95%; margin: 12px auto; max-height: 600px; overflow-y: auto; }
HTML:
<ul className='options-holder'> { this.state.items.map((item, index) => ( <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`}> <div className='circle' onClick={this.removeItem} /> <p className='item-text'>{ item.text }</p> </li> )) } <li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}> <div className='circle form' /> <form className='new-item-form' onSubmit={this.handleSubmit}> <input autoFocus className='new-item-input' placeholder='Type something and press return...' onChange={this.handleChange} value={this.state.text} ref={(input) => (this.formInput = input)} /> </form> </li> </ul>
5 Answers
Answers 1
Seems like you're building a chat-like interface. You can try wrapping the <ul>
and the div.circle-form
in a separate div like below:
<div class="wrapper"> <ul id="list"> <li>item</li> <li>item</li> <li>item</li> <li>item</li> <li>item</li> <li>item</li> <li>item</li> <li>item</li> </ul> <div class="circle-form"> <input type="text" name="type_here" placeholder="Enter something..." autofocus/> </div> </div>
Then the CSS
ul{ border:1px solid red; height:13em; overflow-y:auto; position:relative; } ul li{ border-bottom:1px solid #ddd; list-style:none; margin-left:0; padding:6px; } .wrapper{ background:#eee; height:15em; position:relative; } .circle-form{ background:#ddd; height:2em; padding:3px; position:absolute; bottom:0; left:0; right:0; z-index:2; } .circle-form input[type=text]{ padding:8px; width:50%; }
EDIT
And to scroll to the bottom of the list with javascript
var list = document.getElementById("list"); list.scrollTop = list.offsetHeight;
Answers 2
I have a script that I've used in one of my projects to scroll top Smoothly, I made a little refactor to scroll the height of your div (scroll bottom) I hope it helps.
scroll.js
function currentYPosition() { if (self.pageYOffset) return self.pageYOffset; if (document.documentElement && document.documentElement.scrollHeight) return document.documentElement.scrollHeight; if (document.body.scrollHeight) return document.body.scrollHeight; return 0; } function elmYPosition(eID) { let elm = document.getElementById(eID); let y = elm.offsetHeight; let node = elm; while (node.offsetParent && node.offsetParent != document.body) { node = node.offsetParent; y += node.offsetHeight; } return y; } export default function smoothScroll(eID, string) { let startY = currentYPosition(); let stopY = elmYPosition(eID); let distance = stopY > startY ? stopY - startY : startY - stopY; let speed = Math.round(distance / 10); let speedTimeout = 250; if (speed >= 100) speed = 100; if (string) speed = 1; let step = Math.round(distance / 25); let leapY = stopY > startY ? startY + step : startY - step; let timer = 0; if (stopY > startY) { for (let i = startY; i < stopY; i += step) { setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed); leapY += step; if (leapY > stopY) leapY = stopY; timer++; } return; } for (let i = startY; i > stopY; i -= step) { setTimeout('window.scrollTo(0, ' + (leapY) + ')', timer * speed); leapY -= step; if (leapY < stopY){ leapY = stopY; } timer++; } }
You should import this inside your component, there are 2 parameters(the ID of your element, in this case, you can use ref. The second one is a string that I've used to treat the speed of the scrolling.
import scroll from './your-path/scroll.js'; . . . <ul className='options-holder'> { this.state.items.map((item, index) => ( <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`} ref={el => (this.formLi = el)}> <div className='circle' onClick={this.removeItem} /> <p className='item-text'>{ item.text }</p> </li> )) } <li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}> <div className='circle form' /> <form className='new-item-form' onSubmit={this.handleSubmit}> <input autoFocus className='new-item-input' placeholder='Type something and press return...' onChange={this.handleChange} value={this.state.text} ref={(input) => (this.formInput = input)} /> </form> </li>
Idk how you are mapping this LI inside your render but you should make a verification and if there's the property Overflow you should run the scroll.
There's a reasonable answer for your component is jumping to the first element, you're hitting the ref for the FIRST element, not the last.
Possible workaround:
scroll(this.state.items[this.state.items.length - 1]);
Update 1: Gist of the original scroll.js, scrolling to the top
Answers 3
Use react-scroll library. However you will have to explicitly set ID of your ul
.
import { animateScroll } from "react-scroll"; scrollToBottom() { animateScroll.scrollToBottom({ containerId: "options-holder" }); }
You can call scrollToBottom
on setState callback.
For example
this.setState({ items: newItems}, this.scrollToBottom);
Or by using setTimeout.
Or you can even set Element
from react-scroll
and scroll to a certain li
.
Answers 4
Simply use position:sticky
s.t. the last list item is always shown.
li:last-child { position:sticky; bottom:0; }
Answers 5
I had a similar issue when it came to rendering an array of components that came from a user import. I ended up using the componentDidUpdate() function to get mine to work.
componentDidUpdate() { // I was not using an li but may work to keep your div scrolled to the bottom as li's are getting pushed to the div const objDiv = document.getElementById('div'); objDiv.scrollTop = objDiv.scrollHeight; }
0 comments:
Post a Comment