Responsive, Scrollable, Non-Transparent Tables With Shadows Using CSS and Javascript

Tech

March 1, 2019 7 min read

I had a challenge at work to make the tables on the website responsive to support the move from two sites (mobile and desktop) to one responsive site. There are a lot of thoughts out there on how data tables should, or could, be handled to work well across mobile devices.

Many of these techniques require changing the html so that it is displayed completely differently for mobile devices, or a lot of complex Javascript that is case-by-case specific. Some of these techniques are great for certain use-cases, but for a basic large table with a lot of related data, these techniques came up short.

I needed something simple that could be applied to most tables throughout the site and be done with it. No new development should have to be done after the initial development, making it easy for authors to make their tables responsive in the CMS.

There is the tried and true table in a container with overflow-x: auto that seemed promising. The issue I had with this approach is that it didn't seem clear enough for the user that it was scrollable and not merely cut off. I wanted to add shadowing to the side with hidden content to make it clearer that there was more there.

There is this approach by Stephen Hay that looks ideal, unless the table is not transparent, in which case it breaks. The tables on the site I was working on had banded rows, which meant the shadowing would not work at all. So I set out to expand on this solution to make it work for non-transparent tables with some extra CSS and some Javascript.

Note: I used jQuery in this project, so the Javascript is written in jQuery. I will be releasing a Vanilla JS version in the future, so watch this space for an update!

HTML: A table within a div

The html is simple: add a div around the table with special classes for both so they can be targeted in the CSS/JS.

<div class="responsive-table-container">
  <table class="table">
    ...
  </table>
</div>

CSS: Make the div scrollable and add scrollbars for certain devices

Next I made the div scrollable with overflow: auto and added scrollbars for Webkit (Safari/Chrome) devices since they are frequently hidden until used unlike IE/Edge that shows them all the time. Adding scrollbars is optional, but seemed like a nice little help for non-touch devices where the table was still too large for the page.

.responsive-table-container {
  overflow: auto;
}
.responsive-table-container::-webkit-scrollbar {
  -webkit-appearance: none;
  height: 5px;
}
.responsive-table-container::-webkit-scrollbar-track {
  background-color: #ddd;
}
.responsive-table-container::-webkit-scrollbar-thumb {
  border-radius: 15px;
  background-color: rgba(0,0,0,.25);
}

CSS + JS: Add shadows that respond to scrolling

In the CSS, I added styling for three classes to display the shadow on the right if the table could scroll but hadn't been scrolled, on the left and right if it could scroll either left or right, and on the left if the table had already been scrolled all the way.

.responsive-table-container.scroll-start {
  box-shadow: inset -5px 0 7px 0 rgba(0,0,0,0.15);
}
.responsive-table-container.scroll-middle {
  box-shadow: inset 0 0 14px 0 rgba(0,0,0,0.3);
}
.responsive-table-container.scroll-end {
  box-shadow: inset 5px 0 7px 0 rgba(0,0,0,0.15);
}

In the Javascript, I added a method to add the beginning scroll class if the table was too large for the responsive container.

function responsiveTablesOnLoad() {
    $(".responsive-table-container").each(function() {
        var containerWidth = $(this).width();
        var width = $("table", this).width();

        if(width > containerWidth) {
            $(this).addClass('scroll-start');
        }
        else {
            $(this).removeClass('scroll-start');
        }
    })
}

I then added event listeners to call the above method when the window was resized or the orientation was changed as that could affect whether the table would have to be scrolled or not.

// Resize updates
window.addEventListener("resize", function () {
    responsiveTablesOnLoad();
})
        
// Orientation Change updates
window.addEventListener("orientationchange", function () {
    responsiveTablesOnLoad();
})

Finally, I added Javascript to handle adding and removing the three classes on scroll so that the shadows would appear appropriately. I used jQuery's .scrollLeft() method to calculate the amount of scroll that had occurred.

$(".responsive-table-container").scroll(function() {
    var left = $(this).scrollLeft();
    var containerWidth = $(this).width();
    var width = $("table", this).width();

    if(left === 0) {
        $(this).addClass('scroll-start');
        $(this).removeClass('scroll-middle');
        $(this).removeClass('scroll-end');
    }
    if(left > 0 && ((left - 1) < (width - containerWidth))) {
        $(this).removeClass('scroll-start');
        $(this).addClass('scroll-middle');
        $(this).removeClass('scroll-end');

    }
    if((left - 1) === (width - containerWidth)) {
        $(this).removeClass('scroll-start');
        $(this).removeClass('scroll-middle');
        $(this).addClass('scroll-end');
    }
})

CSS + JS: Showing the shadows and handling clicks

Since the table is a child of the .responsive-table-container div, it appears above the div, hiding the shadows if it is not transparent. To fix this issue, I made the table appear below the responsive div.

.responsive-table-container .table {
  position: relative;
  z-index: -1;
}

The issue with this approach then becomes clicks. Since the div is now above the table, clicking or tapping on a link in the table gets stopped by the div, so no clicks occur on the table. The common CSS fix for that issue would be to add pointer-events: none; to the div so that clicks flow through the div and to the element behind it. However, doing this stops the div from being scrollable (pointer events no longer work on the div itself).

So, I added some Javascript to pass clicks through the shadow div. This essentially catches the click event, moves the table up to a 0 z-index (so it is now above the shadow layer), fires the click at the point it was originally fired, and then moves the table back down to a -1 z-index.

// Pass clicks through the shadow layer
$(".responsive-table-container").click(function(e) {
    if(e.originalEvent) {
        $("table", this).css("z-index", 0);
        $(document.elementFromPoint(e.clientX, e.clientY))[0].click();
        $("table", this).css("z-index", -1);
    }
})

Final Result

Here is the codepen with the final result.

See the Pen Responsive Banded Table by Jamena McInteer (@nmcinteer) on CodePen.

Problems with this approach

This approach is not perfect. It was the best approach I could come up with to handle the majority of use-cases on my project, but there are a couple of problems I have found that I have yet to figure out.

Though links do click through correctly, hover states do not. This is more of an issue on desktops where you would expect the cursor to change when hovering over a link or expect the color or underline to change. The Javascript handles the click, but not the hover.

This approach hasn't been tested for more complex mouse events on the table or clicks that result in something other than native browser behavior.

For a tall table, it could be confusing to use on devices without touch. If the bottom scrollbar is not visible, it is not easy to figure out how to scroll the table. It can be done by scrolling to the bottom of the table to find the scrollbar or clicking on the table and using the arrow keys on the keyboard, but these steps may not be intuitive.

There are many ways to handle tables for responsive sites. This is the approach that worked best for my project requirements, but it may not work best for yours. I would love to hear your thoughts on this approach and if you have suggestions to make it better!

Share

Think others might enjoy this post? Share it!

Comments

I'd love to hear from you, let me know your thoughts!