Search code examples
javascripthtmlcsshtmx

How can I keep the x-scroll position of a div, after inserting elements inside it?


I try to implement an infinite x-scroll with HTMX, but the request is infinitely triggered, because the triggering element gets inserted on the left side of the div and triggers a new request to load items inside the div.

How can I avoid that: append the new elements, but keep the x scroll position? It might be a CSS or a HTMX issue, I am not sure.

<div class="flex h-full space-x-2">
  <div class="bg-amber-600 flex-initial w-1/4"></div>
  <div class="flex space-x-2 w-full overflow-x-auto">
    <div class="flex-none w-16 ml-auto" 
      hx-get="/scroll" 
      hx-trigger="intersect once" 
      hx-swap="outerHTML">
    </div>
    <div class="bg-amber-400 flex-none w-16">0</div>
    <div class="bg-amber-400 flex-none w-16">1</div>
    <div class="bg-amber-400 flex-none w-16">2</div>
    <div class="bg-amber-400 flex-none w-16">3</div>
  </div>
</div>

I tried also with beforebegin and it behaves the same:

<div class="bg-amber-400 flex-none w-16 ml-auto"
  hx-get="/scroll"
  hx-trigger="intersect once"
  hx-swap="beforebegin">x</div>

Here is a codepen to explain what I mean: https://codepen.io/viggiesmalls/pen/RwQPROJ


Solution

  • That's exactly what happens: div maintains the scroll-x position, which is measured from the left edge. Since the default direction is LTR (left-to-right), assumption is that new content should appear on the right.

    So the most natural approach of infinite x-scroll would be to add items to the right, in that case you won't face that issue.

    Workarounds

    Assuming you really need to add items to the left, here are some workarounds I can think of:

    Changing the direction via CSS

    One of the possible approaches could be to change the direction for your container to RTL. In that case items will be displayed from right to left and scroll position will also be measured from the right edge.

    .my-container {
      direction: rtl;
    }
    
    .my-container * {
      direction: ltr;
    }
    

    I had to reorder your initial items to get expected result See codepen: https://codepen.io/burikella/pen/ExJWeKQ

    Forcing scroll position manually in JS

    Another option would be to add custom JavaScript that will leverage htmx:beforeSwap and htmx:afterSwap events and reset the scroll position:

      const container = document.getElementById("infinite-x-scroll");
    
      container.addEventListener('htmx:beforeSwap', function(evt) {
        const scrollDiff = container.scrollWidth - container.scrollLeft;
        container.attributes['data-last-scroll'] = scrollDiff;
      });
    
      container.addEventListener('htmx:afterSwap', function(evt) {
        const scrollDiff = container.scrollWidth - container.scrollLeft;
        const lastDiff = container.attributes['data-last-scroll'];
        if (typeof lastDiff === "number" && scrollDiff !== lastDiff) {
          container.scrollLeft = container.scrollWidth - lastDiff;
        }
      });
    

    In order to work smooth I had to change the way you added delay. I've added it to the trigger, instead to the swap:

    <div class="h-[400px] flex-none w-16 ml-auto" hx-get="/scroll" hx-trigger="intersect delay:0.5s" hx-swap="outerHTML"></div>
    

    See codepen: https://codepen.io/burikella/pen/zYXZJNW