旋转 <table>
列标题是之前在 CSS-Tricks 上 讨论过 的内容,因此感谢它帮助我起步并实现这种效果。 正如文章中所指出的,如果您没有使用三角函数来计算表格样式,则必须依靠 魔数,您的表格将会很脆弱,任何响应式的梦想都会破灭。
幸运的是,在这种情况下,我们可以去除三角函数并用一些细致的几何图形来代替它,并且我们的魔数都变成了 0(一个真正的神奇数字)。
对于那些赶时间的人来说,这里有 CSS 代码(它与另一篇文章中的样式非常相似)。 下面是详细的逐步指南。
<th class="rotate"><div><span>Column Header 1</span></div></th>
table {
border-collapse: collapse;
--table-border-width: 1px;
}
th.rotate {
white-space: nowrap;
position: relative;
}
th.rotate > div {
/* place div at bottom left of the th parent */
position: absolute;
bottom: 0;
left: 0;
/* Make sure short labels still meet the corner of the parent otherwise you'll get a gap */
text-align: left;
/* Move the top left corner of the span's bottom-border to line up with the top left corner of the td's border-right border so that the border corners are matched
* Rotate 315 (-45) degrees about matched border corners */
transform:
translate(calc(100% - var(--table-border-width) / 2), var(--table-border-width))
rotate(315deg);
transform-origin: 0% calc(100% - var(--table-border-width));
width: 100%;
}
th.rotate > div > span {
/* make sure the bottom of the span is matched up with the bottom of the parent div */
position: absolute;
bottom: 0;
left: 0;
border-bottom: var(--table-border-width) solid gray;
}
td {
border-right: var(--table-border-width) solid gray;
/* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/
min-width: 30px;
padding-top: 2px;
padding-left: 5px;
text-align: right;
}
让我们来拆解这个表格并看看发生了什么。 魔法从这个有趣的 HTML 标签链开始。 我们在 <th>
中的 <div>
中放置一个 <span>
。 这真的有必要吗? 在边框的行为、我们需要的定位灵活性以及决定表格列宽度的因素之间… 是的,它们各自都有用途,并且是必要的。
让我们看看如果我们直接旋转 <th>
会发生什么
<th class="rotate">Column header 1</th>
table {
border-collapse: collapse;
}
th.rotate {
border-bottom: 1px solid gray;
transform: rotate(315deg);
white-space: nowrap;
}
td {
border-right: 1px solid gray;
min-width: 30px;
padding-top: 2px;
padding-left: 5px;
text-align: right;
}

不考虑我们没有纠正位置的事实,这里有两个主要问题:
- 列宽仍然根据标题长度计算,而这正是我们要避免的。
- 我们的边框并没有随着旋转一起旋转,因为它实际上是表格的一部分。
这些问题并不难解决。 我们知道,如果 <th>
具有带边框的子元素,浏览器不会将该边框视为表格的一部分。 此外,我们知道,绝对定位的元素将从文档流中移除,并且不会影响父元素的宽度。 让我们从左侧引入 <div>
标签… 以及右侧,我想。
<th class="rotate"><div>Column header 1</div></th>
table {
border-collapse: collapse;
}
th.rotate {
white-space: nowrap;
position: relative;
}
th.rotate > div {
position: absolute;
transform: rotate(315deg);
border-bottom: 1px solid gray;
}
td {
border-right: 1px solid gray;
min-width: 30px;
text-align: right;
padding-top: 2px;
padding-left: 5px;
}

在带有旋转 <th>
元素的图像中更容易看出,但该旋转是在元素的中心进行的(这是 transform-origin 的默认行为)。 它只是在 x 和 y 上的另一个变换来将其放到正确的位置,但这就是我们需要三角函数来计算出将它与列边框对齐需要多少 x 和 y 的地方。 如果我们相反仔细选择旋转标题的点,并使用 transform-origin 来选择它,那么最终得到的距离将比魔数更直接。
下面的动画有助于说明我们将要做什么来避免复杂的数学运算。 蓝色边框左上角的黑色点需要与表格列右边的红色点匹配,并围绕它旋转。 这样,两个边框之间就不会有间隙。
如果你不知道你在哪里,去某个地方是没有帮助的。 绝对定位将在这方面帮助我们。 通过在 <div>
上指定 bottom: 0; left: 0;
,它最终将在父 <th>
的左下角。 这意味着 <div>
边框的左下角位于左列边框的顶部并穿过它的一半。 从这里可以明显看出,我们需要向下移动一个边框宽度,向右移动一个单元格宽度,但我们如何才能以响应方式实现这一点呢? 正是在这一刻,您可能会想起我们还没有添加 <span>
— 我们需要它!
我们将使用 <div>
来“找出”表格单元格有多大,并使用 <span>
来实际容纳文本并将其绝对定位,以便溢出父元素。
<th class="rotate"><div><span>Column header 1</span></div></th>
th.rotate{
white-space: nowrap;
position: relative;
}
th.rotate > div {
position: absolute;
bottom: 0;
left: 0;
width: 100%; /* <- now the div parent is as wide as the columns */
}
th.rotate > div > span {
position: absolute;
bottom: 0;
left: 0;
border-bottom: 1px solid gray;
}
太好了! 当我们将 <div>
的宽度设置为 100% 时,它会保存有关列大小的信息,而不管表格单元格中的内容是什么。 在此基础上,我们可以轻松地将内容平移到 <div>
的宽度 — 但不要忘记,我们需要削减半个边框宽度。 我们的平移变成了
transform: translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));
<div>
现在处于正确的位置进行旋转,但我们必须确保选择正确的 transform-origin。 我们希望它位于边框的左上角,该点将在我们的 <div>
元素底部左侧并向上移动一个边框宽度
transform-origin: 0%, calc(100% - var(--table-border-width));
这让我们得到了表格标题的最终样式。
table {
border-collapse: collapse;
--table-border-width: 1px;
}
th.rotate{
white-space: nowrap;
position: relative;
}
th.rotate > div {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
transform:
translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));
rotate(315deg);
transform-origin: 0%, calc(100% - var(--table-border-width));
}
th.rotate > div > span {
position: absolute;
bottom: 0;
left: 0;
border-bottom: var(--table-border-width) solid gray;
}
请注意,变换发生在所有内容都被放置之后。 这意味着旋转的标题将尽可能地溢出到所有内容上。 您需要将整个表格包裹在某个东西中,以弥补意外高度。 我将标题和表格一起放在一个 flexbox <div>
中,并将标题的 flex-basis
设置为足够大的值,以弥补高标题。
#div-with-table {
display: flex;
flex-direction: column;
justify-content: space-around;
}
#title {
flex-basis: 140px;
}
有趣而透彻的文章,谢谢 :)
如果 thead 文本只是垂直的(而不是倾斜的),那么您可以使用 writing-mode 来达到很好的效果,而无需绝对定位或魔数(除了 line-height:0)。
(此演示已经有两年的历史了,所以可以改进一点。)
如果 thead 文本包含 <b> 或 <p> ,这将失效。
这里有另一个没有 CSS 而是只有 JavaScript 的解决方案。
https://github.com/napengam/vertical
是的,我喜欢 write mode 以及它使垂直列标题变得多么容易。 如果有一种文化用 45 度倾斜的线书写就好了!
太棒了! 谢谢你的精彩参考。
是的,我的演示只是为了单行而设计的。 ;) 我认为,如果您有多行,那么它看起来会非常乱,但每个人的情况都不同。
您的 JS 版本看起来不错,也很有用:)。 我唯一的一个问题(除了使用 JS 之外)是它无法抵抗仅文本缩放,并且内容会重叠(与我的示例不同)。 CSS tricks 演示也会遇到这个问题。
嗨,Paul
你能解释一下你的意思吗…
“… 它无法抵抗仅文本缩放,并且内容会重叠…”
此致
嗨,Heinz,
我不想让这个讨论主题偏离最初的话题(虽然答案可能也与文章相关),但为了回答您的问题,我将 Firefox 设置为“仅文本”缩放,因为我发现这样更容易阅读(而不是缩放整个布局)。 当您缩放您的示例时,文本会重叠,因为您将文本放置在带有 JS 的固定高度容器中。(当然,我知道您可以使用更多 JS 来解决这个问题,并检测缩放等)。:)
更少的魔法
这确实让你几乎走到了最后一步! 我争论过要不要讨论这个,但因为您将
<div>
的宽度设置为 100%,这意味着您的边框永远只会有单元格的宽度。 如果对您来说,整个标题是否有下划线并不重要(或者在这种情况下,您根本没有底边框),那么这当然是一个可行的方法,但请记住,如果您确实添加/想要底边框,它们会根据单元格内容的不同而有不同的长度,而没有<span>
。