Change scroll position on LiveView event?

I am creating my first LiveView application and it involves a chatroom. Is there a way to set the scroll position when an event is submitted?

Right now I have a very primitive method of scrolling to the bottom of the chat messages, but I want the messages to simulate slack where the newest message appears at the bottom of the div. Using this anchor method is very slow (I can send 5-6 messages before it scrolls to the bottom) so I was wondering if there was a way I could auto scroll to the bottom when a new message is sent. Another issue I have noticed with using an anchor is if I were to scroll through the history it would auto scroll to the bottom after a couple seconds, making it impossible to read the history.

This is an application I am making to get back in to Phoenix and learn about LiveView so any help is appreciated.

    <div id="gen-chat-container">
      <div id="chat-messages" phx-update="append">
        <p id="<%= uuid %>"><strong><%= username %>:</strong> <%= content %></p>
        <div id="anchor"></div>
      </div>
      <%= f = form_for :chat, "#", id: "gen-chat-form", phx_submit: :submit_message, phx_change: :form_update %>
        <%= text_input f, :message, placeholder: "Enter your message..." %>
      </form>
    </div>
#chat-messages {
    ...
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: flex-end;
    overflow: scroll;
    overflow-anchor: none;
}

#anchor {
  bottom: 0;
}

I would use a client hook on the container of chat messages. Set a scroll event on mounted, that sets a flag if user scrolls to the end of messages. In updated, check the flag, if yes, scroll to the bottom (which means user was already at the bottom of messages, probably waiting for new ones), if no then do not scroll.

I do have the same kind of problem, but I’ve not yet implemented it. This is the logic I’m gonna use, so it may have flaws or implementation details. I’ll be happy if you share the end result!

3 Likes

Thank you! I did precisely this and the client hook and flag works great!

Found a lamer way:

<script>
function scrollToBottom(id){
   var element = document.getElementById(id);
   element.scrollTop = element.scrollHeight - element.clientHeight;
}

document.addEventListener('phx:update', phxUpdateListener);

function phxUpdateListener(_event) {
  scrollToBottom('box_to_scroll');
}
</script>
2 Likes

Your solution just helped a lot!
Thanks for sharing it!

For some reason the mounted callback works but updated callback don’t.
I’m just saving all the debugging trouble for now and using your solution.

I have used hooks before with success, I don’t know why update isn’t working but honestly, this was a long day, I’ll ignore it for now. hahaha

Here’s my phx-hook where it will only scroll to the bottom on mount and only if you’re within a certain distance of the bottom. This allows users to scroll up to read previous messages and not have it auto scroll to the bottom if a new message comes in.

export default {
  mounted() {
    this.el.scrollTo(0, this.el.scrollHeight);
  },

  updated() {
    const pixelsBelowBottom =
      this.el.scrollHeight - this.el.clientHeight - this.el.scrollTop;

    if (pixelsBelowBottom < this.el.clientHeight * 0.3) {
      this.el.scrollTo(0, this.el.scrollHeight);
    }
  },
};

Make sure to put a unique id on your container if you have multiple chat rooms otherwise it wont remount and will just update.

4 Likes

This worked perfectly…thanks!