### ResizeObserver chat example

# ResizeObserver not detected. Demo will not work.

Chatbot delay:
Example 1. Scroll to bottom on every resize.
Example 2. Example 1 + preserve user scroll position.
Example 3. Example 2 + smooth scroll.

#### The problem

ResizeObserver API can be used to solve "how to keep chat window scrolled to the bottom" problem.

By default, browsers preserve scroll position from the top. This is because important content is at the top.

In chat window, important content is at the bottom: the last message. Instead of preserving top scroll position, we need to keep user pinned to bottom scroll position when user has scrolled all the way down. We can do this by scrolling to the bottom on every resize.

#### How to scroll to bottom on resize?

This is easily implemented with ResizeObserver.

The simplest implementation is:

    .chat {
      overflow: scroll;
    }

    <div class="chat">  <!-- chat has the scrollbar -->
      <div class="chat-text"> <!-- chat-text contains chat text -->
        <div>jack: hi </div>
        <div>jill: hi </div>
      </div>
    </div

    let ro = new ResizeObserver( entries => {
      for (let e of entries) {
        let chat = e.target.parentNode;
        chat.scrollTop = chat.scrollHeight - chat.clientHeight;
      }
    });
    ro.observe(document.querySelector('.chat-text'))

`chat` has the scrollbar, and `chat-text` contains the chat text. ResizeObserver observes `chat-text`'s size, and scrolls `chat` to bottom on every resize. Example 1 implements this. You can try it out by in live demo at the bottom of this page by clicking on Chat/Chatbot buttons.

Appending new messages to `chat-text` will also trigger a resize, and scroll to the bottom, which is great.

But, there is a problem. What happens if user has scrolled higher up trying to read older messages? Scrolling to the bottom would make user lose text they were reading. Our chat window implementation has a new constraint:

#### How to not lose user's scroll position?

We can use `scroll` event to detect when user has scrolled. There is no API to distinguish whether scroll event was initiated by user, or javascript. This is problematic, because we only want to save user-initiated scroll. The hacky solution is to use flags to distinguish between user and programmatic scroll. It is implemented as an Example 2.

    let ro = new ResizeObserver();

    function initScrollPositionChat(chat) {
      let chatText = chat.firstElementChild;
      chatText.resizeHandler = entry => {
        let chat = entry.target.parentNode;
        // Scroll to bottom, unless user has scrolled.
        if (!chat.saveUserScroll) {
          chat.isResizeScrollEvent = true;
          chat.scrollTop = chat.scrollHeight - chat.clientHeight;
        }
      }
      ro.observe(chatText);
      chat.addEventListener('scroll', ev => {
        // Ignore scrolls generated by ResizeObserver
        if (chat.isResizeScrollEvent) {
          delete chat.isResizeScrollEvent;
          return;
        }
        // Save user's position unless scrolled to the bottom,
        if (chat.scrollTop != chat.scrollHeight - chat.clientHeight) {
          chat.saveUserScroll = true;
        } else {
          chat.saveUserScroll = false;
        }
      });
    }

Example 3 extends Example 2 with smooth scrolling. Unfortunately, it has to implement smooth scrolling from scratch, instead of using `scrollIntoView(behavior:'smooth')`. scrollIntoView fires scroll events that cannot be distinguished from user initiated events.

For completeness, examples also implemented chat pruning to prevent chat window from getting too big.