Search code examples
javascripthtmlcss

How to have horizontal scrollbar on a table and a vertical scrollbar on the body page while also using position sticky for thead of an html table?


It's really difficult to explain what I need in the title, so here's an example.

This is my table:

body { 
  margin: 0;
  padding: 0; 
}

header { 
  margin-bottom: 15px;
  display: flex;
  justify-content: center;
}

.header__info-wrapper { 
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 10px;
}

.infoboard__wrapper { 
  display: flex;
  gap: 15px;
  background-color: #f1f1f1;
  padding: 5px 10px;
  border-radius: 10px;
}

.title { 
  font-size: 30px;
  font-weight: bold;
}

.view {
  margin: auto;
  width: 100vw;
}

table {
  border-collapse: collapse;
  width: 100%;  

  table-layout: fixed;
}

th {
  position: sticky;
  top:0;  
}

td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
  background-color: white;
  box-sizing: border-box;    
}

table th:not(:nth-child(1)):not(:nth-child(2)) {
  width: 300px;
}

tr:nth-child(even) td {
  background-color: #dddddd;
}

.wrapper {
  position: relative;
  /* overflow-x: auto; */
  height: 100vh;
}

.sticky-col {  
  position: sticky;
  z-index: 1;
}

th.sticky-col{
  z-index: 2;
}

.first-col {
  width: 150px;
  min-width: 150px;
  max-width: 150px;
  left: 0;  
}

.second-col {
  width: 150px;
  min-width: 150px;
  max-width: 150px;
  left: 100px;
}

.wrapped-content{
  width: 250px;
  white-space: normal;
}
<header>
    <div class="header__info-wrapper">
      <div class="title">Some Title</div>
      <div class="infoboard__wrapper">
        <div class="basic__info">
          <div>Total Number:</div>
          <div>
            <ifx-number-indicator id="number__indicator-total"></ifx-number-indicator>
          </div>
        </div>
        <div class="basic__info">
          <div>Completed:</div>
          <div>
            <ifx-number-indicator id="number__indicator-completed"></ifx-number-indicator>
          </div>
        </div>
        <div class="basic__info">
          <div>Planned:</div>
          <div>
            <ifx-number-indicator id="number__indicator-planned"></ifx-number-indicator>
          </div>
        </div>
      </div>
    </div>
  </header>

  <div class="view">
    <div class="wrapper">
      <table class="table">
        <thead>
          <tr>
            <th class="sticky-col first-col">Preview</th>
            <th class="sticky-col second-col">Component</th>
            <th>Version 1</th>
            <th>Version 2</th>
            <th>Version 3</th>
            <th>Version 4</th>
            <th>Version 5</th>

          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="sticky-col first-col">1</td>
            <td class="sticky-col second-col">Mark</td>
            <td>Ham</td>
            <td>Micro</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">2</td>
            <td class="sticky-col second-col">Jacob</td>
            <td>Smith</td>
            <td>Adob Adob Adob AdobAdob Adob Adob Adob Adob</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">3</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td><div class="wrapped-content">Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog Goog Goog</div></td>
          </tr>
          <tr>
            <td class="sticky-col first-col">3</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">4</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">5</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">6</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
          <tr>
            <td class="sticky-col first-col">7</td>
            <td class="sticky-col second-col">Larry</td>
            <td>Wen</td>
            <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>

This is the behavior that I want! Fixed th and fixed first two cols + fixed horizontal scrollbar. The problem is that, because the horizontal scrollbar appears on the body, the whole body is moving! So, the content inside Header moves too. I don't want that. I want the horizontal scrollbar to only move the content inside the table, not the whole body. I want the vertical scollbar to be on the body so the entire page can scroll down.

If you uncomment the overflow-x: auto;, then the horizontal scrollbar moves the table content, but the vertical scrollbar only moves the table content too, and not the entire body, which is what I want! On top of that, because of the header content, there are actually two scrollbars.

How to achieve what I want? How to make the horizontal scrollbar only move the content of the table while the vertical one move the entire page?

Update:

Screenshots of td border before scroll movement. enter image description here

Screenshots of td border after scroll movement. enter image description here

Screenshots of th border before scroll movement. enter image description here

Screenshots of th border after scroll movement. enter image description here

Borders do not align:

enter image description here


Solution

  • Making the horizontal scrollbar fixed is not possible yet but to make the table X-axis scroll instead of the whole body scrolling Horizontally and set the table height dynamically according to the header. This will result in almost the same impression as your requirements.

    The steps needed to be done are:

    • Add overflow-x: hidden; to the body element
    • Add width: 100% and overflow: scroll to the wrapper element of the table.
    • Calculate the header height using JavaScript and set the height as a CSS custom property
    • Finally add height: calc(100vh - var(--header-height) - 15px); to the wrapper element. (15px is the scrollbar height)

    EDIT: The second problem you are getting is the border getting invisible when you are scrolling horizontally or vertically. I have tried multiple solutions and found the answer. The cause of this problem is the border-collapse: collapse on the table element. Revoming it and changing the border-spacing to 0 does the job and the border now stays while scrolling but there was a problem with this. The td and th border overlaps the sibling border and create a thick (2px) border border at some areas. I have solved this issue by removing the border from td and th and replacing it with the box-shadow extending 1px with 0 blur. Another 1 small change is the table top-border was missing so I have added the border-top: 1px; to the th elements in the thead. Now it looks perfect.

    Additionally, I have changed the left position of .second-col element "Component" to 150px. Now this prevents it from moving horizontally as per your comment request.

    You can preview the updated result in the snippet below

    const headerEl = document.querySelector("header");
    const headerHeight = headerEl.offsetHeight;
    const wrapperEl = document.querySelector(".wrapper");
    
    wrapperEl.style.setProperty('--header-height', `${headerHeight}px`);
    body {
        margin: 0;
        padding: 0;
        overflow-x: hidden;
    }
    
    header {
        margin-bottom: 15px;
        display: flex;
        justify-content: center;
    }
    
    .header__info-wrapper {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        gap: 10px;
    }
    
    .infoboard__wrapper {
        display: flex;
        gap: 15px;
        background-color: #f1f1f1;
        padding: 5px 10px;
        border-radius: 10px;
    }
    
    .title {
        font-size: 30px;
        font-weight: bold;
    }
    
    .view {
        margin: auto;
        width: 100vw;
    }
    
    table {
        width: 100%;
        table-layout: fixed;
    
        /* Removed the border-collapse and added border spacing 0  */
        border-spacing: 0;
    }
    
    thead tr th {
        /* Added the border top to the table header */
        border-top: 1px solid #dddddd;
    }
    
    th {
        position: sticky;
        top: 0;
    }
    
    td,
    th {
        text-align: left;
        padding: 8px;
        
        /* Removed the border */
        border: none;
    
        /* Added the box-shadow as the border */
        box-shadow: 0 0 0 1px #dddddd;
        
        background-color: white;
        box-sizing: border-box;
    }
    
    table th:not(:nth-child(1)):not(:nth-child(2)) {
        width: 300px;
    }
    
    tr:nth-child(even) td {
        background-color: #dddddd;
    }
    
    .wrapper {
        position: relative;
        width: 100%;
        height: calc(100vh - var(--header-height) - 15px);
        overflow: scroll;
    }
    
    .sticky-col {
        position: sticky;
        z-index: 1;
    }
    
    th.sticky-col {
        z-index: 2;
    }
    
    .first-col {
        width: 150px;
        min-width: 150px;
        max-width: 150px;
        left: 0;
    }
    
    .second-col {
        width: 150px;
        min-width: 150px;
        max-width: 150px;
        left: 150px;
    }
    
    .wrapped-content {
        width: 250px;
        white-space: normal;
    }
    <header>
        <div class="header__info-wrapper">
            <div class="title">Some Title</div>
            <div class="infoboard__wrapper">
                <div class="basic__info">
                    <div>Total Number:</div>
                    <div>
                        <ifx-number-indicator id="number__indicator-total"></ifx-number-indicator>
                    </div>
                </div>
                <div class="basic__info">
                    <div>Completed:</div>
                    <div>
                        <ifx-number-indicator id="number__indicator-completed"></ifx-number-indicator>
                    </div>
                </div>
                <div class="basic__info">
                    <div>Planned:</div>
                    <div>
                        <ifx-number-indicator id="number__indicator-planned"></ifx-number-indicator>
                    </div>
                </div>
            </div>
        </div>
    </header>
    
    <div class="view">
        <div class="wrapper">
            <table class="table">
                <thead>
                    <tr>
                        <th class="sticky-col first-col">Preview</th>
                        <th class="sticky-col second-col">Component</th>
                        <th>Version 1</th>
                        <th>Version 2</th>
                        <th>Version 3</th>
                        <th>Version 4</th>
                        <th>Version 5</th>
    
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td class="sticky-col first-col">1</td>
                        <td class="sticky-col second-col">Mark</td>
                        <td>Ham</td>
                        <td>Micro</td>
                        <td>Micro</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">2</td>
                        <td class="sticky-col second-col">Jacob</td>
                        <td>Smith</td>
                        <td>Adob Adob Adob AdobAdob Adob Adob Adob Adob</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">3</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>
                            <div class="wrapped-content">Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog Goog Goog
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">3</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">4</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">5</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">6</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                    <tr>
                        <td class="sticky-col first-col">7</td>
                        <td class="sticky-col second-col">Larry</td>
                        <td>Wen</td>
                        <td>Goog Goog Goog GoogGoog Goog Goog Goog Goog Goog</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

    Note: You might be seeing a little overlap on the last right cells and the td element right border not aligning properly with the th element. This is because cells are missing in this table. If you even add empty cells (td elements) all over the missing spots. It will be fixed and will look perfect. Its summary is: "Make sure that the count td in every tr element matches the count of th elements that are in the thead element."