bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
38.4k stars 1.31k forks source link

scrolling behavior #1882

Open unabomber78 opened 1 year ago

unabomber78 commented 1 year ago

I used htmx websockets to create the chat and hx-swap-oob="beforeend:#messages" to add a new message to the end of the list. How can i set the scroll bar to bottom after new message. I see in the documentation that there is an option to control the scroll bar using htmx, but it only describes the hx-swap attribute. I've tried applying this to hx-swap-oob, but it doesn't get me anywhere. Am I doing something wrong or is it currently impossible to get the result I want with htmx?

danjessen commented 1 year ago

That's a very vague description. Did you try the focus scroll setting ?

unabomber78 commented 1 year ago

You are referring to focus-scroll:true described here? https://htmx.org/attributes/hx-swap/ I tried to apply this to my hx-swap-oob="beforeend:#messages" but I get an error in the console.

danjessen commented 1 year ago

@unabomber78 mind linking the error ?

unabomber78 commented 1 year ago

I'm sorry, I didn't quite follow you. English is not my native language, so sometimes I use a translator and sometimes I misunderstand my interlocutor. I thought I described my problem in detail, but maybe I did not. I am passing a new message to the message box using hx-swap-oob="beforeend:#messages" I would like to pass also the information that the scroll bar should move to the very bottom after adding a new message. I thought the documentation link I provided in the previous post described how to do what I wanted. But I have not been able to achieve what I want with htmx, although I have already achieved what I need with css.

cmalek commented 1 year ago

Hello! I'm also trying to do the same thing, and having similar problems. I'm using a websocket connection to return html wrapped in:

<div id="message-list" hx-swap-oob="beforeend">
    {content}
</div>

Like @unabomber78, I would like to scroll to the bottom of the #message-list div after adding the message. So I tried:

<div id="message-list" hx-swap-oob="beforeend focus-scroll:true">
    {content}
</div>

But, unlike @unabomber78, I don't see an error in the browser console. Instead the DOM doesn't get updated with the new HTML.

I've also tried:

<div hx-swap-oob="beforeend:#message-list focus-scroll:true">
    {content}
</div>

with the same results. And I tried using "scroll:bottom" instead of "focus-scroll:true" with the same results: no DOM update, and no console messages. Removing "focus-scroll:true" causes the DOM to be properly updated again.

I'm using htmx-1.9.6 with the ws.js extension. I also have Tailwind CSS loaded into the page, if that has any effect. I've tested in Chrome (118.0.5993.70) and Firefox (118.0.2).

Should using "focus-scroll:true" or "scroll:bottom" work here in these cases?

davidwmartines commented 1 year ago

Seeing the same with the sse extension.

<div style="height: 200px; overflow: scroll;"  hx-ext="sse" sse-connect="my_url..."  sse-swap="message" hx-swap="innerHTML scroll:bottom" ></div>

Events from SSE do get swapped into the div, but no scrolling occurs.

davidwmartines commented 1 year ago

A workaround. Add this to the swap container element. The contents will need to contain some identifiable element you want to scroll to. hx-on:htmx:after-settle="document.getElementById('some-inner-element').scrollIntoView(false)"

TheQueenIsDead commented 12 months ago

I have a similiar use case to you all that I am struggling to get working. I noticed that there is a console error when I enable logs with htmx.logAll() and have code that looks like the following:

index.html


<div hx-ext="ws" ws-connect="ws://localhost:1323/ws">

    <ol id="items" class="list-group list-group-numbered">
    </ol>

    <form id="input-area" class="input-area" ws-send>
        <input name="chat_message" type="text" class="input-field" placeholder="Type a message..."/>
        <button class="send-button">Send</button>
    </form>

</div>

websocket response

<ol class="list-group list-group-numbered" hx-swap-oob="beforeend:#items scroll:#items:bottom">
  <li class="message incoming" >{contents}</li>
</ol>

console error

Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Document': '#items scroll:#items:bottom' is not a valid selector.
    at Object.ye [as oobSwap] (https://unpkg.com/htmx.org@1.9.8:1:8792)
    at WebSocket.<anonymous> (https://unpkg.com/htmx.org/dist/ext/ws.js:148:10)
ye @ htmx.org@1.9.8:1
(anonymous) @ ws.js:148

Behaviour

The list is updated with the child <li> successfully when the scroll:#items:bottom is not present. But when the scroll modifier is added, the DOM is not updated, and those console errors start showing. It seems like the OOB CSS selector separates on a :, which leaves it trying to find an element named everythingAfterTheColon

Related issues

After a bit of being nosey, it seems that this is a relatively common pitfall:

The latter issue mentions that he could get around it with an HTML plugin, however, I do not think the classes plugin would aid us here :/

ChristianSch commented 11 months ago

I have a similar problem. I send this over websockets and the following just doesn't update the DOM

<div hx-swap-oob="scroll:bottom swap:.5s innerHTML:#msg-7739b8b5"><p>Text</p>\n</div>

I get the error: Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Document': 'bottom swap:.5s innerHTML:#msg-7739b8b5' is not a valid selector.

I tried different orderings, to no avail. This however works:

<div hx-swap-oob="innerHTML:#msg-7739b8b5"><p>Text</p>\n</div>
MorrisMorrison commented 10 months ago

@TheQueenIsDead I think the documentation for hx-swap-oob is a bit misleadding, or at least I made the same assumptions as you did reading it :D

I had a look at the implementation of hx-swap-oob and it only supports hx-swap values, so the actual swap strategies with a optional css selector separated by :. It does not support any additional hx-swap modifiers like scroll

moonstripe commented 10 months ago

I have a simple workaround as follows:

  <script>
    document.addEventListener("htmx:wsAfterMessage", e => {
      const messagesDiv = document.getElementById("messages");

      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    })
  </script>

Kind of disappointing, since I was trying to build the entire site without a single line of javascript, but I don't think it's a bad solution for the time being.

Would love to see hx-swap-oob take hx-swap modifiers.

khowling commented 9 months ago

Seeing the same with the sse extension.

<div style="height: 200px; overflow: scroll;"  hx-ext="sse" sse-connect="my_url..."  sse-swap="message" hx-swap="innerHTML scroll:bottom" ></div>

Events from SSE do get swapped into the div, but no scrolling occurs.

+1

Jlopez2045 commented 9 months ago

A workarround using hyperscript:

<div
  id="chatMessagesWrapper"
  _="
  on load set my scrollTop to my scrollHeight end
  on htmx:afterSettle set my scrollTop to my scrollHeight end
  on htmx:oobAfterSwap set my scrollTop to my scrollHeight end
  "
>

or with js:

document.body.addEventListener("htmx:oobAfterSwap", function (evt) {
  const chatWrapper = document.querySelector("#chatMessagesWrapper");
  chatWrapper.scrollTop = chatWrapper.scrollHeight;
});

htmx:afterProcessNode can also work.

dev-SR commented 8 months ago
        <script>
            window.addEventListener('load', () => {
                const messageContent = document.getElementById('content');
                setTimeout(() => {
                    window.scrollTo({
                        top: messageContent.scrollHeight,
                        behavior: 'smooth'
                    });
                }, 100);
            });
            document.body.addEventListener('htmx:wsAfterMessage', (event) => {
                const messageContent = document.getElementById('content');
                window.scrollTo({
                    top: messageContent.scrollHeight,
                    behavior: 'smooth'
                });
            });
        </script>
schungx commented 7 months ago

Is there a way to do this without JavaScript or HyperScript?

I'm also trying to build an entire web app without a single line of JavaScript...

danjessen commented 7 months ago

Thats an odd question in a repository related to a javascript library

kissgyorgy commented 7 months ago

Thats an odd question in a repository related to a javascript library

That's the whole point and philosophy of HTMX; not having to write JavaScript when using HTMX.

occult commented 5 months ago

I have a simple workaround as follows:

  <script>
    document.addEventListener("htmx:wsAfterMessage", e => {
      const messagesDiv = document.getElementById("messages");

      messagesDiv.scrollTop = messagesDiv.scrollHeight;
    })
  </script>

Kind of disappointing, since I was trying to build the entire site without a single line of javascript, but I don't think it's a bad solution for the time being.

Would love to see hx-swap-oob take hx-swap modifiers.

Didn't want to use vanilla JS but this worked great, than you.