4

I am making a chatbot. I want to scroll to the bottom of the chat box when a new input is given by the user or the Data is sent through API.

It doesn't scroll and scroll just stays in the same position but the data is being added in the chat box

I Have tried the code from other chat bot but it didn't work either

var outputArea = $('#chat-output');
$('#user-input-form').on('submit', function (e) {
  e.preventDefault();

  var message = $('#user-input').val();

  outputArea.append(`
    <div class='bot-message'>
      <div class='message'>
        ${message}
      </div>
    </div>
  `);



  const req = https.request(options, (res) => {
    res.setEncoding('utf8');
    res.on('data', (d) => {
      const myobj = JSON.parse(d);
      if ('narrative' in myobj.conversationalResponse.responses[0]) {
        const temp = myobj.conversationalResponse.responses[0].narrative.text;
        outputArea.append(`
      <div class='user-message'>
        <div class='message'>
          ${temp}
        </div>
      </div>
    `);
      } else if ('imageUrl' in myobj.conversationalResponse.responses[0]) {
        const img = myobj.conversationalResponse.responses[0].imageUrl;
        if ('narrative' in myobj.conversationalResponse.responses[1]) {
          const text_r = myobj.conversationalResponse.responses[1].narrative.text;
          outputArea.append(`
      <div class='user-message'>
      <div class ="message">
      ${text_r}
      <a href=""></a>
      </div>
      </div>
    `);
        } else {
          outputArea.append(`
      <div class='user-message'>
        <div class='message'>
         <img src="" width="300" height="200">
        </div>
      </div>
    `);
        }
      }
    });
  });

  req.on('error', (error) => {
    console.error(error);
  });

  req.write(data);
  req.end();

  $('#user-input').val('');
.form-container {
  width: 400px;
  height: 450px;
  padding: 10px;
  background-color: white;
  overflow: scroll;
  position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="chat-popup" id="myForm">
<div class="form-container">

  <div class="chat-output" id="chat-output">
    <div class="user-message">
      <div class="message">Hi! I'm Bot, what's up?</div>
    </div>
  </div>
  <div class="chat-input">
    <form action="#0" id="user-input-form" autocomplete="off">
      <input type="text" id="user-input" class="user-input" placeholder="Talk to the bot.">
    </form>
  </div>
  </br></br>
  <button type="button" class="btn cancel" onclick="closeForm()">Close</button>
</div>
</div>
Andrew Stegmaier
  • 3,429
  • 2
  • 14
  • 26
  • where did you determine that the screen should scroll down? – mahdi gholami Jun 15 '22 at 19:38
  • scroll the container after a new message is appended. `outputArea[0].scrollTop = 9e9;` If `outputArea` is not the container, adjust that selector to point to the container, trying each parent all the way to `document.documentElement`; somewhere along the chain it will scroll... – dandavis Jun 15 '22 at 19:39
  • thanks the outputArea[0].scrollTop = 9e9; worked for me :D – justarandomguy123 Jun 15 '22 at 19:54

5 Answers5

26

Another interesting method is by using pure CSS, using the flex-direction method, which works by creating a wrapper for the content inside the scrolling element.

I've whipped up a quick demo below (with a button and some JavaScript for adding new items). You can also check out this separate demo-page.

The trick then lies in reversing the content direction using column-reverse in the scroller. Because the items are in another container, they don't get 'flipped' but instead always line up to the bottom. This, in fact, makes the scroller scrolled to the bottom whenever stuff is added.

Breakdown, click for demo

Added bonus: keeps scroll position

Also, and this is something I really like about the method; whenever the user has started scrolling (up), the scroller will not lose its scroll position when stuff is being added. So, it will only 'stick' tot the bottom if it was already scrolled (by default, or by the user) to the bottom. This makes sure there's no annoying content jumping, offering a better user experience.

Demo

let scrollerContent = document.getElementById('scrollerContent');

document.getElementById('addItems').addEventListener('click', function() {
  let newChild = scrollerContent.lastElementChild.cloneNode(true);
  newChild.innerHTML = "Item " + (scrollerContent.children.length + 1);
  scrollerContent.appendChild(newChild);
});
.scroller {
overflow: auto;
height: 100px;
display: flex;
flex-direction: column-reverse;
}

.scroller .scroller-content .item {
  height: 20px;
  transform: translateZ(0); /* fixes a bug in Safari iOS where the scroller doesn't update */
}
<div class="scroller">
  <div class="scroller-content" id="scrollerContent">
    <div class="item">Item 1</div>
    <div class="item">Item 2</div>
    <div class="item">Item 3</div>
    <div class="item">Item 4</div>
    <div class="item">Item 5</div>
    <div class="item">Item 6</div>
    <div class="item">Item 7</div>
    <div class="item">Item 8</div>
    <div class="item">Item 9</div>
    <div class="item">Item 10</div>
  </div>
</div>
<br/><br/>
<button id="addItems">Add more items</button>
Klaas Leussink
  • 2,208
  • 16
  • 23
  • 1
    Although I implemented it with scrollTop, I also like the pure CSS way, it's interesting – Ian Jun 16 '22 at 10:33
  • 1
    I discovered this method last year, and I really like how clean and simple (and JS independent) it is – Klaas Leussink Jun 16 '22 at 10:41
  • 1
    Awesome solution! A lot of unnecessary code left my project. You made my day :D – aksioto Aug 08 '22 at 15:22
  • 1
    This in my humble opinion should be the accepted answer. – matewilk Dec 14 '22 at 21:11
  • 1
    An interesting approach! Thank you, @KlaasLeussink – Oussama Jan 01 '23 at 11:04
  • I love this, but on iOS Safari it looks like when you add a new element to the list while you're scrolling in the middle, the list moves – Christopher Demicoli Apr 10 '23 at 07:04
  • @ChristopherDemicoli yes, that's something I can't get my head around. As you can see, I use a 'trick' to place the scroll items into the 3D layer @ Safari. This takes the middle ground, as it makes the list update when it's scrollled to the bottom, which is probably what you want anyway. You can work around this by updating the `scrollTop` of the scroller with JS, but you'll run into challenges as Safari sees this as positive, while Chrome (for instance) thinks it's negative, due to the `column-reverse` bit. If you come up with something, I'd be happy to update the answer! – Klaas Leussink Apr 11 '23 at 13:57
1

I tried several methods and found success with the one described in this css-tricks article using overflow-anchor. I'd recommend checking out the article as it has a proper explanation.

#messages-container * {
  overflow-anchor: none;
}

#anchor {
  overflow-anchor: auto;
  height: 1px;
}
<div id="messages-container">
  <!-- Insert messages here -->
  <div id="anchor"></div>
</div>
0

You can use scrollTop to achieve it.

I simply wrote an example for your reference, you will find that there are two ways to implement scrollTop, one is to use the animation wrapper, the other is to use it directly, you can compare the difference between the two methods.

const sendMessage = (selector, isAnimate = true) => {
  const text = $(selector).val();
  const $container = $('.form-container');
  $container.append(`<p>${text}</p>`);
  if (isAnimate) {
    $container.animate({
      scrollTop: $container.prop('scrollHeight')
    }, 1000);
  } else {
    $container.scrollTop($container.prop('scrollHeight'));
  }
  $(selector).val('');
};

$('button:eq(0)').on('click', function() {
  sendMessage('input[type=text]');
});

$('button:eq(1)').on('click', function() {
  sendMessage('input[type=text]', false);
});
.form-container {
  width: 400px;
  height: 100px;
  padding: 10px;
  background-color: white;
  overflow: scroll;
  position: relative;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<div class='form-container'>
  <p>This is line 1 of text</p>
  <p>This is line 2 of text</p>
</div>
<div>
  <input type='text' placeholder="Type a message.">
  <button type='button'>Use animation</button>
  <button type='button'>Don't use animation</button>
</div>
Ian
  • 1,198
  • 1
  • 5
  • 15
0

The div containing the messages: #chat-output

#chat-output {
    border: 1px solid #ccc;
    overflow-x: hidden;
    overflow-y: auto;
    height: 400px;
}

function scrollToBottom()

function scrollToBottom(){
    var d = $('#chat-output');
    d.scrollTop(d.prop("scrollHeight"));
}

Call scrollToBottom() every time you want the #chat-output to scroll to the bottom:

E.g. On DOM-ready

$(function () {
    scrollToBottom()
})

E.g after appending new chat messages to the #chat-output:

function appendNewChatItem() {
    // your append() code here
    scrollToBottom();
}
Klaas
  • 108
  • 9
-1

This worked for me:

outputArea[0].scrollTop = 9e9; 
Lee Taylor
  • 7,761
  • 16
  • 33
  • 49