skip to Main Content

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

2

Answers


  1. 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>
    Login or Signup to reply.
  2. Basically the example uses an approach I outlined in the comment section. See notes in code snippet. This could also be achieved using pseudo ::before and ::after as well. Hope this helps in finding an answer to your issue.

    // get the left span element
    const left = document.querySelector('#left');
    // get the right span element
    const right = document.querySelector('#right');
    // get the scroll overflow element
    const parent = document.querySelector('#nav');
    // get the buttons or nav items
    const btns = document.querySelectorAll('.nav-btn');
    
    // get the bounding client rect of the scrollable element
    const parRect = parent.getBoundingClientRect();
    // get the first element in the nav-items
    const leftEl = btns[0];
    // get the last element in the nav-items
    const rightEl = btns[btns.length-1];
    
    // set the ellipse on the right side initially
    // you can add another method and pass the left span
    // params if needed but in this example it is not needed
    function checkOverflow(posEl, ellipseEl, parPosEl){
      // compare the two elements positions and if the position.right of the  
      // last button is greater than the position.right of the scrollable element
      posEl.getBoundingClientRect().right > parPosEl.right ? 
        // we set the ellipse span innerHTML to show an ellipse and arrow
        // else we remove the innerHTML of the span element
        ellipseEl.innerHTML = '<strong>..&#8201;&rsaquo;</strong>' : ellipseEl.innerHTML = ''; 
        // NOTE: you could also create and remove an element here or use a pseudo
        // tags content set as a variable using el.setAttribute('--cssvar', '..&#8201;&rsaquo;') 
    }
    
    // call method and pass rightside params
    checkOverflow(rightEl, right, parRect);
    
    // method for the scroll event
    const showEllipse = () => {
      // conditional to check overflow on left side and show or hide ellipse and arrow 
      // to match the left position of the scroll element
      if(leftEl.getBoundingClientRect().left < parRect.left){
        left.innerHTML = '<strong>&lsaquo;&#8201;..</strong>';
        // left position is needed as fixed elements are relative to the view port
        // we set the position of the ellipse and arrow element
        left.style.left = `${parRect.left}px`;
      }else{
        left.innerHTML = '';
      }
      
      // call method and pass in params for rightside 
      // there is no need to set right position on fixed element as 
      // its position is relative to its relatively positioned parent and set in css
      checkOverflow(rightEl, right, parRect);
      
    }
    
    // event listener for scroll
    parent.addEventListener('scroll', showEllipse);
    #nav {
      width: 250px;
      overflow-y: auto;
      position: relative;  
      /* flex places spans inline with ul element */
      display: flex;
      margin: 50px auto;
    }
    
    ul {
      display: flex;
      list-style-type: none;
      padding: 0;  
    } 
    
    ul li {
      padding: 2px 5px;
      margin: 0 2px;
      font-family: sans-serif;
    }
    
    ul li a {
      text-decoration: none;
      color: rgb(0, 100, 255);
    }
    
    ul li a:hover {
      color:  rgb(0, 200, 255);;
    }
    
    #left {
      /* position sticky does not work well in a scrollable div 
         scrolling left we use fixed here and set the position using JS
      */
      position: fixed;
      left: 0;
      align-self: center;
      /* set background the same as your scrollable divs background */
      background-color: white;
      /* make sure your padding height is enough to cover 
         any background you may have for the buttons  
      */
      padding: 2px 4px 0;
    }
    
    #right {
      position: sticky;
      right: 0;
      align-self: center;
      /* set background the same as your scrollable divs background */
      background-color: white;
      /* make sure your padding height is enough to cover 
         any background you may have for the buttons  
      */
      padding: 0 4px 2px;
    }
    <nav id='nav'>
      <!--/ left side ellipse and arrow will be added via JS 
            span positioned fixed as sticky causes a wierd glich when scrolled 
            we get the left position of the scrollable element and place it there 
            when there is overflow using JS
      /-->
      <span id="left"></span>
      <ul>
        <li class="nav-btn"><a href=#>Insurance</a></li>
        <li class="nav-btn"><a href=#>Active</a></li>
        <li class="nav-btn"><a href=#>Documentation</a></li>
        <li class="nav-btn"><a href=#>News</a></li>
        <li class="nav-btn"><a href=#>About</a></li>
        <li class="nav-btn"><a href=#>Contact</a></li>
      </ul>
      <!--/ right side ellipse and arrow will be added via JS
            position is sticky and it is positioned in css not JS 
      /-->
      <span id="right"></span>
    </nav>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search