Search code examples
javascripthtmlcssscrolloverflow

Horizontal scroll overflow ellipsis in navigation tabs at start and end


Problem

I have navigation tabs which are horizontaly scrollable and my task now is to add function that if you scroll this "menu" left or right and the title in nav tab overflows it should add "..." dots to that title. Basically when you scroll to right and you stop scrolling in the middle of the word it should replace this remaining part with dots (same thing when you scroll left )

At first this menu was implemented without these dots and arrows but customers were arguing that they did not know that there were another tabs they could click (on mobile phones)

Figma preview

preview of figma implementation with visible right and left arrow with text overflow dots preview of what should I implement if Figma

So far all my attempts failed and I'm trying to understand it from scratch but from what I see I do not know if it's even possible ?

My jsfiddle link

<div class="with-elipsis">Test paragraph with <code>ellipsis</code>. It needs quite a lot of text in order to properly test <code>text-overflow</code>.</div>

<div class="with-elipsis-scroll">Test paragraph with <code>string</code>. It needs quite a lot of text in order to properly test <code>text-overflow</code>.</div>
.with-elipsis {
  width: 500px;
  text-overflow: ellipsis;
  border: 1px solid #000000;
  white-space: nowrap;
  overflow: auto;
}

.with-elipsis-scroll {
  border: 1px solid #000000;
  white-space: nowrap;
  overflow: auto;
  height: 40px;
  display: flex;
  overflow-x: scroll;
  width: 200px;
  text-overflow: ellipsis;
}

In this code sample:

  1. context is rendered right but when I scroll the content it scrolls with "dots"
  2. does not work

Solution

  • I would not try to use text-overflow for this. For one thing, it’s not designed to work on both the left and right sides.

    Instead, I would solve this as per the suggestion from Dale Landry. Personally I would leave out the ellipsis and simply have an arrow or an angle bracket to indicate that scrolling is possible.

    const d1 = document.querySelector('.d1')
    const d2 = document.querySelector('.d2')
    
    const updateArrowState = () => {
      if (d2.scrollLeft > 0)
        d1.classList.add('show-left-arrow')
      else
        d1.classList.remove('show-left-arrow')
      if (d2.scrollLeft + d2.offsetWidth < d2.scrollWidth)
        d1.classList.add('show-right-arrow')
      else
        d1.classList.remove('show-right-arrow')
    }
    
    d2.addEventListener('scroll', updateArrowState, {passive: true})
    
    updateArrowState()
    .d1 {
      position: relative;
    }
    
    .d1::before, .d1::after {
      position: absolute;
      top: 0;
      opacity: 0;
      background: white;
      transition: 250ms;
      color: red;
    }
    
    .d1::before {
      content: '< ... ';
      left: 0;
    }
    
    .d1::after {
      content: ' ... >';
      right: 0;
    }
    
    .d1.show-left-arrow::before, .d1.show-right-arrow::after {
      opacity: 1;
    }
    
    .unhide {
      opacity: 1;
    }
    
    .d2 {
      display: flex;
      gap: 1em;
      overflow: auto;
      -ms-overflow-style: none;  /* IE and Edge */
      scrollbar-width: none; /* Firefox */
    }
    
    .d2::-webkit-scrollbar {
      display: none; /* Chrome, Safari, Opera */
    }
    <div class="d1">
      <div class="d2">
        <a>One</a>
        <a>Two</a>
        <a>Three</a>
        <a>Four</a>
        <a>Five</a>
        <a>Six</a>
        <a>Seven</a>
        <a>Eight</a>
        <a>Nine</a>
        <a>Ten</a>
        <a>Eleven</a>
        <a>Twelve</a>
        <a>Thirteen</a>
        <a>Fourteen</a>
        <a>Fifteen</a>
        <a>Sixteen</a>
        <a>Seventeen</a>
        <a>Eighteen</a>
        <a>Nineteen</a>
        <a>Twenty</a>
      </div>
    </div>