Use page scrolling for table with sticky headers.

Today features the same sticky table header effect as yesterday, only the page’s scrollbars are used instead of the scrollbars of a container wrapping around the table. The page with the example effect is here:

This is the first time on this blog that an effect has been accomplished by removing CSS instead of adding it. The following lines were removed:

.sticky-table {
	max-height: 80vh;
	max-width: 100%;
	overflow: auto;
	position: relative;

This removed the scrollbars from the containing element and means that the body, not the containing element, is the new reference point for the position: sticky effect.

When using CSS to create the sticky effect, choosing between using the page scroll or a container element scroll is an all-or-nothing affair. That is, it is not possible, in pure CSS at least, to say that the body is the reference element for vertical scrolling while a container element is the reference element for horizontal scrolling. In the words of the specification: “the offset is computed with reference to the nearest ancestor with a scrolling box.” There is no differentiation between the ancestors horizontal and vertical scrolling boxes.

Post-exercise thoughts

To accomplish the sticky table header effect using a container element for horizontal scrolling but the page scrolling for vertical scrolling JavaScript must be used. My initial thoughts on how to do this are:

  1. Use CSS to set a container element wrapping the table to position: relative.
  2. Let the page do the initial render as a normal, static table.
  3. Then, in JavaScript, do theadRect = getBoundingClientRect() for the thead element.
  4. Go through all columns, and set each first row cell and header row cell to Math.max(headerCell.width, firstRowCell.width).
  5. Set thead position to fixed and z-index to 2.
  6. Offset the top of the tbody by theadRect.height and set its position to relative (maybe relative is not necessary here).
  7. Set the max-width of the tbody and thead to the width of the page (may need to change display type to block for tbody) and overflow to hidden for thead but auto for tbody. This means that tbody should have a horizontal scrollbar while thead‘s scrollbar is hidden (although it still has a scrolling box in theory).
  8. Listen for scroll event on both thead and tbody and ensure their horizontal scrolling offsets are in sync whenever horizontal scrolling occurs.
  9. Additionally, listen for scroll events on the page body and when they occur, set the top of thead to Math.max(scrollTopOffset, 0). This will move the table header up with the body but ‘stick’ it to the top of the viewport once it reaches that point. Also in this event, check if the bottom of the thead element is greater than or equal to the bottom of the tbody element and, if this is the case, set the top of the thead element to the bottom of the tbody element minus the height of the thead element. This will end the sticky effect once the user scrolls past the end of the table.

All of the above means that the body of the table ‘naturally’ (without JavaScript) uses the scroll of the page to scroll through vertically over the table and a horizontal scroll using the tbody as the container. The JavaScript described above, would, in theory, ensure the table header sticks to the top of the page while scrolling down the table, and syncronize the widths of the columns in the table body and header as well as the horizontal scrolling offset. I’ll try putting all that together in the next post.

Color scheme: