3

I want to have a table like this in HTML5:

table like this

That is, titles from column 2 on rotated 270°, and vertically aligned to the bottom, and horizontally centered, and white font on black background, but without setting an explicit height to the header row/columns, and preferably without having to resort to using JavaScript for layout purposes...

Now, until now, I do it by using server-side image generation like
<img src="handler.ashx?text=bla&fg=FFFFFF&bg=000000" />
Unfortunately, this disables searching for text with CTRL + F, which is rather unfortunate since there are many many groups (hundreds).

Now there are a few posts on SO like
Rotate HTML SVG Text by 270 degrees
How to use CSS Rotate() in TH Table Tags
Rotating table header text with CSS transforms
https://jsfiddle.net/t5GgE/1/

But they all either set height explicitly (or indirectly), or it doesn't work properly with a background-color in the table-header.

Now what I have so far is this:
https://jsfiddle.net/kn46f38n/6/

Which has the problems that vertical align bottom doesn't work as it should, and height does not adjust automatically (unless I add the canvas image).

All of which is rather discouraging, and which basically means the only progress has been replacing the handler with a canvas, which reliefs the server, but isn't any progress in searchability, and, worst of all, using JS for layout while there are still browsers out that don't support canvas.

Is there really no way to do this in HTML/inlineSVG without having to set an explicit height (height of any kind, like including transform-origin) and without having to resort to javascript ?

Without jQuery:

var maxH = 0;
// Find the column label with the tallest height
var hdrs = document.querySelectorAll(".hdr")
for (i = 0; i < hdrs.length; i++)
{
    var bbox = hdrs[i].getBoundingClientRect();
    if (bbox.height > maxH)
        maxH = bbox.height;
}


// Make all the label cells that height
var cols = document.querySelectorAll(".col")
for (i = 0; i < cols.length; i++)
    cols[i].style.height = maxH + "px";
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442

3 Answers3

6

Ah, never mind, I got it myselfs.
The secret is having it vertical-lr, so width and height are already correct.
Then all you have to do is rotate the text 180 degrees with transform-origin center...

Works in Chrome and Firefox and IE 11 & 10 (according to MDN backwards-compatible to IE9, but since ms-transform-rotate doesn't work properly, it degrades gracefully to only writing-mode vertical-lr if you omit ms-transform).

https://developer.mozilla.org/en-US/docs/Web/CSS/text-orientation
https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode#Browser_compatibility
https://web.archive.org/web/20160320101147/https://msdn.microsoft.com/en-us/library/ms531187(v=vs.85).aspx

.blackhd
{
    vertical-align: bottom;
    width: 40px;
    #height: 100px;
    border: 1px solid hotpink;
    background-color: black;
    text-align: center;
}

.vert
{
    display: inline-block;
    color: white;
    #font-weight: bold;
    font-size: 15px;
    writing-mode: vertical-lr;
    #writing-mode: vertical-rl;
    -ms-writing-mode: tb-rl;
    transform-origin: center;
    transform: rotate(180deg);
    padding-top: 2mm;
    padding-bottom: 3mm;
}


<table>
    <tr>
        <td class="blackhd"><span class="vert">abc</span></td>
        <td class="blackhd"><span class="vert">defghijkl</span></td>
    </tr>
    <tr>
        <td>abc</td>
        <td>defghijklmnopqr</td>
    </tr>
</table>
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
0

How about the following? Just uses a fairly minimal bit of jQuery.

// Find the column label with the tallest height
var maxH = 0;
$(".hdr").each(function(i, item) {
  var bbox = $(item).get(0).getBoundingClientRect();
  if (bbox.height > maxH) {
    maxH = bbox.height;
  }
});

// Make all the label cells that height
$(".col").css('height', maxH + 'px');
.col
{
  background-color:black;
  width: 2em;
  line-height: 2em;
  vertical-align: bottom;
}

.hdr
{
  transform: rotate(270deg);
  transform-origin: left top 0;
  color: white;
  position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
  <td class="col"><div class="hdr">Test</div></td>
  <td class="col"><div class="hdr">Test 2</div></td>
  <td class="col"><div class="hdr">Objekt&uuml;bersicht</div></td>
  <td class="col"><div class="hdr">Test3</div></td>
</tr>
<tr>
  <td>1</td>
  <td>2</td>
  <td>3</td>
  <td>4</td>
</tr>
</table>
Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • Yea, JavaScript for layout, position absolute, adding a dependency to jQuery for 3 lines of code, and having to define width at 2 places, which means I have the choice between adding either less or SASS as dependency. Exactly the kind of thing that I don't want. When you use position absolute, the question is not if, but only when it breakes. By the way, I played with it for like 2minutes, and as soon as I put border-collapse on table and I give it a 2mm padding from the bottom, it broke with a 1px difference in Chrome. Looks like one has to use display inline-box and not display block. – Stefan Steiger Mar 08 '16 at 12:21
  • Additionally, as soon as a table-cell content has a longer width than the header column, the header-column is no longer in the middle... – Stefan Steiger Mar 08 '16 at 15:12
  • Thanks very much for your sarcastic comments. Criticizing my solution for not meeting your secret unstated requirements is unfair. Your example image had fixed-width columns, so I assumed that was all you needed. And I used jQuery for simplicity in my answer. I never said you couldn't use vanilla JS. And the width-in-two-places thing can be avoided (I just modified my example to do so). – Paul LeBeau Mar 08 '16 at 15:18
  • OK, granted the one with the columns was unfair, though I only realized the requirement myselfs when I put some longer text into the table for testing. And jQuery I already had removed. Still, the question was how to do it WITHOUT having to resort to JavaScript. Anyway, I found my answer after hours of trying. Basically it's very simple, once you realize that it needs to be done by departing from vertical text in the first place. – Stefan Steiger Mar 09 '16 at 16:04
  • You said "preferably no JS", And your main objection to the linked approaches seemed to be that they all set the height to a specific predetermined size. I thought you might be okay with some simple JS that worked out the correct size automatically. – Paul LeBeau Mar 09 '16 at 16:24
0

Nor jQuery nor SASS are necessary, but a minimum of Javascript seems to be needed : https://jsfiddle.net/yy3pgvoy/

Here is a minimal solution. First the HTML can be this :

    <table>
        <tr id="headers">
            <td>Test</td>
            <td>Test 2</td>
            <td>Objektübersicht</td>
            <td>Test3</td>
            <td>Veni vidi vici</td>
        </tr>
        <tr>
            <td>1</td><td>2</td><td>3</td><td>4</td><td>5</td>
        </tr>
    </table>

The headers' text are put directly inside the TD, then the Ctrl-F will work properly.

Then, this tiny script will compute the height of the header looking at the maximum width of the TD before rotation.

var size = 0;
var cells = document.querySelectorAll("#headers > td");
Array.prototype.forEach.call(cells, function(e) {
    var rect = e.getBoundingClientRect();
    size = Math.max( size, rect.width );
    e.innerHTML = "<div class='rot'><span>" + e.textContent + "</span></div>";
});
var headers = document.querySelector("#headers");
headers.style.height = Math.ceil( size ) + "px";
headers.className = "rotate";

Finally, you can have this CSS:

td { width: 32px; text-align: center; }
#headers > td {
    position: relative;
    line-height: 32px;
    font-weight: bold;
    color: #fff;
    background: #000;
    white-space: nowrap;
    padding: 0 1ex;
    text-align: center;
    vertical-align: bottom;
}
#headers.rotate > td { width: 32px; max-width: 32px; white-space: normal; padding: 0; }
#headers.rotate > td > div
{
    position: relative;
    display: block;
    width: 32px;
    max-width: 32px;
    height: 32px;
    line-height: 32px;
    text-align: center;
    overflow: show;
    transform: rotate(-90deg);
}
#headers.rotate > td > div > span
{
    position: absolute;
    left: 1ex;
    display: inline-block;
    white-space: nowrap;
    line-height: 32px;
}

I just used transform without the exceptions for all browsers, but it is easy to add all the vendor prefixes.

Tolokoban
  • 2,297
  • 14
  • 17