![粘性头布局和流式布局](https://i-blog.csdnimg.cn/blog_migrate/6c5ba6e6fa691cbc09ac9d96d3e72c79.png)
粘性头布局和流式布局
![StickyTableHeaders](https://i-blog.csdnimg.cn/blog_migrate/6620b5835487b4a1785534c9f296f510.png)
Sticky table headers are no longer a stranger to an average website user — unlike on paper when a reader’s eyes can comfortably jump in saccades between top of a lengthy table and the rows of interest, the landscape orientation of most devices makes vertically-long tables hard to read. However, this very conundrum presents itself as a rich ground for UI experimentation that is not available to the printed media.
粘滞表标题对于普通的网站用户来说不再是陌生的-与纸上的不同,当读者的眼睛可以舒适地在长条形表的顶部和感兴趣的行之间跳动时,大多数设备的横向布局使垂直长条形的表很难读书。 但是,这个难题使自己成为UI实验的丰富基础,而印刷媒体无法使用。
Sticky table headers, as their name implies, remains affixed to the top of the viewport even when the original table headers are scrolled out of view. They help to clarify the representation and purpose of data in columns when the visual reference to original table headers was lost. Besides that, the aid in orienting users in a sea of tabulated information, therefore avoiding the need to repeatedly, and frustratingly, scroll between the top of the table where the header resides, and the region of interest further down, typically lying out of the viewport.
顾名思义,粘滞表头即使在原始表头从视图中滚动出时也保持固定在视口的顶部。 当丢失对原始表标题的可视引用时,它们有助于阐明列中数据的表示形式和用途。 除此之外,它还有助于将用户定向到列表信息的海洋中,从而避免了重复而令人沮丧地在标题所在的表的顶部与感兴趣区域(通常位于列表的下方)之间滚动的麻烦。视口。
There have been a handful of scripts and jQuery plugins written for the purpose of re-establishing the flow and ease of reading tables. While their implementation is flawless and efficient, they might not be an all-encompassing panacea for long tables. In some cases, tables have to obey certain layout rules that are not accounted for by the aforementioned plugins — such as tables that are forced to overflow due to dimension restrictions (e.g. to fit within a viewport).
已经编写了一些脚本和jQuery插件,以重新建立流程并简化表的读取。 尽管它们的实现是完美无瑕且高效的,但对于长表而言,它们可能不是万能的灵丹妙药。 在某些情况下,表格必须遵守某些上述插件未解决的布局规则,例如由于尺寸限制(例如,适合视口)而被迫溢出的表格。
While this tutorial does not try to serve as an all-encompassing panacea to the decidedly sticky problem with sticky table headers, it addresses more possible layout scenarios.
尽管本教程并不试图作为解决带有粘性表标题的明显粘性问题的万能药,但它解决了更多可能的布局方案。
一个基于CSS的纯解决方案,其position: sticky
? (A pure CSS-based solution with position: sticky
?)
Last September, a somewhat promising solution surfaced — a new possible value for the CSS position property is supported in the latest nightly build of WebKit or Chrome Canary. position: sticky
sounded like a very promising new JS-free solution to the old and nuance problems of rigid table headers and beyond — its implementation can be also extremely useful in scenarios where a site navigation or a HTML5 app toolbar has to remain in view to the user at all times regardless of his/her scroll position along the document’s y-axis.
去年9月,一个有点前途的解决方案浮出水面-最新的每晚WebKit或Chrome Canary版本支持CSS position属性的新可能值。 position: sticky
听起来像是一个非常有希望的新的,无需JS的解决方案,用于解决刚性表头及其后的旧问题和细微问题-在必须保留网站导航或HTML5应用程序工具栏以解决以下问题的情况下, position: sticky
实现也非常有用无论用户在文档y轴上的滚动位置如何,都可以随时使用。
Moreover, the sticky property value is supported in barely 6% of all global visits, making it a poor candidate for choice of implementation. Although it will not break layouts as browsers are dictated by W3C directive to ignore properties values that are unrecognized, invalid or illegal, it is not an ideal candidate when cross-browser functionality is desired.
而且,粘性属性的价值仅占全球访问总数的6% ,这使其成为选择实施方案的不佳选择。 尽管它不会破坏布局,因为W3C指令要求浏览器忽略无法识别,无效或非法的属性值,但在需要跨浏览器功能时,它并不是理想的选择。
基于jQuery的解决方案 (The jQuery-based solution)
The jQuery-based solution is rather straight-forward. Before we move on with the JS itself, we should come to a common consensus how a semantically valid table should look like in the markup:
基于jQuery的解决方案非常简单。 在继续进行JS本身之前,我们应该达成一个共识,即一个语义上有效的表在标记中的外观如何:
<table>
<thead>
<tr>
<th></th>
<!-- more columns are possible -->
</tr>
</thead>
<tbody>
<tr>
<td></td>
<!-- more columns are possible -->
</tr>
<!-- more rows are possible -->
</tbody>
<tfoot><!-- optional -->
<tr>
<td></td>
</tr>
</tfoot>
</table>
我们要实现什么? (What do we want to achieve?)
We should enumerate the expectations of this script. It would be great if the script can accommodate various table layouts and situations:
我们应该列举此脚本的期望。 如果脚本可以适应各种表格布局和情况,那就太好了:
- Basic usage: Sticky table header only 基本用法:仅粘性表标题
- Biaxial table headers双轴工作台头
Wide tables:
宽桌:
- Horizontal overflow: If there is a row header, we should introduce a sticky table column, too 水平溢出:如果有行标题,我们也应该引入一个粘性表列
- Vertical overflow: Covered in basic usage垂直溢出:基本用法涵盖
Biaxial overflow: Introduce sticky table header and column
双轴溢出:介绍粘性表的标题和列
一些CSS基础(Some CSS groundwork)
Despite choosing to work with a JS-based solution, we will still have to rely on CSS for the basic styling of the headers. The important things is that we have to position the sticky header absolutely within a common parent with its full-fledge, original table sibling. The CSS is rather straight forward:
尽管选择使用基于JS的解决方案,但仍然必须依靠CSS来实现标头的基本样式。 重要的是,我们必须使用完整的原始表兄弟姐妹将粘性标头绝对定位在公共父级中。 CSS非常简单:
.sticky-wrap {
overflow-x: auto;
position: relative;
margin-bottom: 1.5em;
width: 100%;
}
.sticky-wrap .sticky-thead,
.sticky-wrap .sticky-col,
.sticky-wrap .sticky-intersect {
opacity: 0;
position: absolute;
top: 0;
left: 0;
transition: all .125s ease-in-out;
z-index: 50;
width: auto; /* Prevent table from stretching to full size */
}
.sticky-wrap .sticky-thead {
box-shadow: 0 0.25em 0.1em -0.1em rgba(0,0,0,.125);
z-index: 100;
width: 100%; /* Force stretch */
}
.sticky-wrap .sticky-intersect {
opacity: 1;
z-index: 150;
}
.sticky-wrap .sticky-intersect th {
background-color: #666;
color: #eee;
}
.sticky-wrap td,
.sticky-wrap th {
box-sizing: border-box;
}
Note: It is extremely important that you port the styles for your <table>
elements over to .sticky-wrap
. Although margins of pixel values can be easily calculated and applied to the new wrapper element, automatic margins are difficult to deal with (it is not possible to fetch the value of auto
with jQuery in a straightforward manner), and it is easier if we simply apply the margins and width of tables to the wrapper element itself.
注意:将<table>
元素的样式移植到.sticky-wrap
非常重要。 尽管可以轻松计算像素值的边距并将其应用于新的wrapper元素,但是自动边距很难处理(无法通过简单的方式使用jQuery来获取auto
的值),并且如果我们简单地将表的边距和宽度应用于包装元素本身。
Let’s say you have the following styles for your table:
假设您的表格具有以下样式:
table {
margin: 0 auto 1.5em;
width: 75%;
}
You can simply add the lines to “.sticky-wrap”, too:
您也可以简单地将行添加到“ .sticky-wrap”中:
.sticky-wrap {
overflow-x: auto; /* Allows wide tables to overflow its containing parent */
position: relative;
margin: 0 auto 1.5em;
width: 75%;
}
I shall walk you through the steps that will, with a dozens of lines of JavaScript, create functional sticky table headers. For the ease of presentation, the script is presented in a logical flow towards problem solving — declaration of variables with the var
statement can definitely be concatenated for a more compact and compressed script, but at the sake of logical flow and readability, therefore I have chosen not to adopt the latter approach.
我将引导您完成将使用数十行JavaScript创建功能性粘性表标题的步骤。 为了便于演示,脚本以逻辑流程的形式呈现,以解决问题—可以使用var
语句声明变量,以使用更紧凑和更压缩的脚本,但是出于逻辑流程和可读性的考虑,因此我拥有选择不采用后一种方法。
We shall execute our function for every single instance of table selected for upon DOM ready. Moreover, we will also want to check if the selected tables contain the <thead>
element, and that the <thead>
element is not empty and contains at least one <th>
child. If the aforementioned criteria are not satisfied, our function will simply skip that instance of <table>
and move on to the next.
在DOM准备就绪后,我们将为选定的每个表实例执行函数。 此外,我们还将要检查所选表是否包含<thead>
元素,并且<thead>
元素不为空并且包含至少一个<th>
子级。 如果不满足上述条件,我们的函数将只跳过该<table>
实例,然后继续执行下一个实例。
$(function () {
// Here we select for <table> elements universally,
// but you can definitely fine tune your selector
$('table').each(function () {
if($(this).find('thead').length > 0 && $(this).find('th').length > 0) {
// Rest of our script goes here
}
});
});
步骤1:克隆<thead>
元素 (Step 1: Clone the <thead>
element)
Before we start, we will want to close the table head, and declare some shorthand variables for ease of use:
在开始之前,我们将要关闭表头,并声明一些速记变量以便于使用:
// Declare variables and shorthands
var $t = $(this),
$w = $(window),
$thead = $(this).find('thead').clone(),
$col = $(this).find('thead, tbody').clone();
步骤2:包装表格并追加新表格 (Step 2: Wrap table and append new tables)
In order to extend compatibility towards tables that have excessive width along the x-axis (e.g. having too many columns, or columns that are necessarily yet excessively wide), we wrap the table elements in a <div>
element that is allowed to overflow along the x-axis. The width and margin properties are reset for the table so as to allow proper display within the wrapper.
为了将兼容性扩展到沿x轴的宽度过大的表(例如,列过多或列的宽度必须足够大),我们将表元素包装在允许沿其溢出的<div>
元素中x轴。 重置表格的width和margin属性,以便在包装器中正确显示。
// Wrap table
$t
.addClass('sticky-enabled')
.css({
margin: 0,
width: '100%';
})
.wrap('<div class="sticky-wrap" />');
// Check if table is set to overflow in the y-axis
if($t.hasClass('overflow-y')) $t.removeClass('overflow-y').parent().addClass('overflow-y');
// Create new sticky table head (basic)
$t.after('<table class="sticky-head" />')
// If <tbody> contains <th>, then we create sticky column and intersect (advanced)
if($t.find('tbody th').length > 0) {
$t.after('<table class="sticky-col" /><table class="sticky-intersect" />');
}
// Create shorthand for things
var $stickyHead = $(this).siblings('.sticky-thead'),
$stickyCol = $(this).siblings('.sticky-col'),
$stickyInsct = $(this).siblings('.sticky-intersect'),
$stickyWrap = $(this).parent('.sticky-wrap');
步骤3:插入克隆的表内容 (Step 3: Inserting cloned table contents)
The trick now is to insert contents cloned from our original table into the newly created tables that will serve as our sticky elements:
现在的技巧是将从原始表中克隆的内容插入到新创建的表中,这些表将用作粘性元素:
Sticky header will receive all contents from the cloned
<thead>
element粘性标头将从克隆的
<thead>
元素接收所有内容Sticky column will receive contents from the first
<th>
from<thead>
, and all the subsequent<th>
from<tbody>
. This is assuming that each row only contains one table header cell.粘性列将从
<thead>
的第一个<th>
接收内容,并从<tbody>
所有后续<th>
接收内容。 假设每一行仅包含一个表头单元格。- Sticky intersect will receive content from the top left most cell in the table 粘性相交将从表格中最左上角的单元格接收内容
// Sticky header gets all content from <thead>
$stickyHead.append($thead);
// Sticky column gets content from the first <th> of both <thead> and <tbody>
$stickyCol
.append($col)
.find('thead th:gt(0)').remove()
.end()
.find('tbody td').remove();
// Sticky intersect gets content from the first <th> in <thead>
$stickyInsct.html('<thead><tr><th>'+$t.find('thead th:first-child').html()+'</th></tr></thead>');
步骤4:功能 (Step 4: Functions)
Here comes the most important part of our jQuery script — we decide what functions are needed for sticky headers to work, and we declare them with the var
statement to allow for easy callback. Two functions immediately come to mind:
这是jQuery脚本中最重要的部分-我们决定粘性标头正常工作需要哪些功能,并使用var
语句声明它们以便于回调。 马上想到两个功能:
A function to determine the widths of individual
<th>
elements in the cloned header. Since we only cloned the<thead>
element, the computed width of the cloned header will not be the same as the actual header itself, since the content of<tbody>
itself, which may or may not influence the final width of each individual columns, is not included.确定克隆的标头中各个
<th>
元素的宽度的函数。 由于我们仅克隆了<thead>
元素,因此克隆出的标头的计算宽度将与实际标头本身不同,因为<tbody>
本身的内容可能会或可能不会影响每列的最终宽度,不包括在内。A function to position the sticky header, so that we can update the vertical offset of the cloned header that is absolutely positioned when the scroll event is fired.
定位粘性标头的功能,以便我们可以更新触发滚动事件时绝对定位的克隆标头的垂直偏移量。
A function to position the sticky column, so that we can update the horizontal offset when the parent element is overflowing.
定位粘性列的功能,以便我们可以在父元素溢出时更新水平偏移量。
A function to calculate allowance — this feature is explained later in greater detail.
计算津贴的功能-稍后将对此功能进行详细说明。
You may ask, why do I have to calculate the vertical offset of the header instead of simply using position: fixed
? I, too, have contemplated over this issue, but it came to my realization that if we are allowing the table to overflow along the x-axis, the fixed positioning option has to go out of the window, because it will not scroll with the table in the event of a horizontal overflow.
您可能会问,为什么我必须计算标题的垂直偏移,而不是简单地使用position: fixed
? 我也曾考虑过这个问题,但是我意识到,如果我们允许表格沿X轴溢出,则固定定位选项必须移出窗口,因为它不会随着窗口滚动。发生水平溢出时的桌子。
// Function 1: setWidths()
// Purpose: To set width of individually cloned element
var setWidths = function () {
$t
.find('thead th').each(function (i) {
$stickyHead.find('th').eq(i).width($(this).width());
})
.end()
.find('tr').each(function (i) {
$stickyCol.find('tr').eq(i).height($(this).height());
});
// Set width of sticky table head
$stickyHead.width($t.width());
// Set width of sticky table col
$stickyCol.find('th').add($stickyInsct.find('th')).width($t.find('thead th').width())
},
// Function 2: repositionStickyHead()
// Purpose: To position the cloned sticky header (always present) appropriately
repositionStickyHead = function () {
// Return value of calculated allowance
var allowance = calcAllowance();
// Check if wrapper parent is overflowing along the y-axis
if($t.height() > $stickyWrap.height()) {
// If it is overflowing
// Position sticky header based on wrapper's scrollTop()
if($stickyWrap.scrollTop() > 0) {
// When top of wrapping parent is out of view
$stickyHead.add($stickyInsct).css({
opacity: 1,
top: $stickyWrap.scrollTop()
});
} else {
// When top of wrapping parent is in view
$stickyHead.add($stickyInsct).css({
opacity: 0,
top: 0
});
}
} else {
// If it is not overflowing (basic layout)
// Position sticky header based on viewport scrollTop()
if($w.scrollTop() > $t.offset().top && $w.scrollTop() < $t.offset().top + $t.outerHeight() - allowance) { // When top of viewport is within the table, and we set an allowance later
// Action: Show sticky header and intersect, and set top to the right value
$stickyHead.add($sticktInsct).css({
opacity: 1,
top: $w.scrollTop() - $t.offset().top
});
} else {
// When top of viewport is above or below table
// Action: Hide sticky header and intersect
$sticky.add($stickInsct).css({
opacity: 0,
top: 0
});
}
}
},
// Function 3: repositionStickyCol()
// Purpose: To position the cloned sticky column (if present) appropriately
repositionStickyCol = function () {
if($stickyWrap.scrollLeft() > 0) {
// When left of wrapping parent is out of view
// Show sticky column and intersect
$stickyCol.add($stickyInsct).css({
opacity: 1,
left: $stickyWrap.scrollLeft()
});
} else {
// When left of wrapping parent is in view
// Hide sticky column but not the intersect
// Reset left position
$stickyCol
.css({ opacity: 0 })
.add($stickyInsct).css({ left: 0 });
}
},
// Function 4: calcAllowance()
// Purpose: Return value of calculated allowance
calcAllowance = function () {
var a = 0;
// Get sum of height of last three rows
$t.find('tbody tr:lt(3)').each(function () {
a += $(this).height();
});
// Set fail safe limit (last three row might be too tall)
// Set arbitrary limit at 0.25 of viewport height, or you can use an arbitrary pixel value
if(a > $w.height()*0.25) {
a = $w.height()*0.25;
}
// Add height of sticky header itself
a += $sticky.height();
return a;
};
}
Now, you may ask, what is allowance? What do we need it for? The basis of the allowance is simple — we do not want the sticky table header to follow us all the way to the end of the table, do we? It is unnecessary, and run the risk of obfuscating the last table row. While this feature is optional (thus allowance is set to 0, see above), I highly recommend allowing at least one table row of height remaining. The height can be computed from the table itself, or you can set a fixed height.
现在,您可能会问,什么是津贴? 我们需要什么? 津贴的依据很简单-我们不希望粘性表标题一直到表末尾跟随我们,对吗? 这是不必要的,并且存在混淆最后一个表行的风险。 虽然此功能是可选的(因此,配额设置为0,请参见上文),但我强烈建议允许至少保留一排桌子的高度。 可以从表格本身计算高度,也可以设置固定高度。
As far as my experience go, I realize that I do not need the header much after the last three rows of the table is shown — that is because by then our eyes would have probably moved away from the table into the content below. This threshold is arbitrary, and it is up to you to decide.
就我的经验而言,我意识到在显示表的最后三行之后我不需要太多标题了-这是因为到那时我们的视线可能已经从表移到了下面的内容中。 此阈值是任意的,由您决定。
// Calculate allowance
// We allow the last three rows to be shown without the need for the sticky header to remain visible
$t.find('tbody tr:lt(4)').each(function () {
allowance += $(this).height();
});
第5步:开火,开火! (Step 5: Fire away, fire away!)
Now we are done declaring all the functions we need for the correct styling and positioning of the sticky header. All is left is to bind event handlers to the $(window)
object (previously abbreviated as $w
for your convenience), and trigger the right function. Here is the game plan:
现在,我们完成了声明粘贴式标题正确样式和位置所需的所有功能。 剩下的就是将事件处理程序绑定到$(window)
对象(为方便起见,以前缩写为$w
),并触发正确的函数。 这是游戏计划:
When the DOM is ready, perform initial round of width calculations
DOM准备就绪后,执行初始宽度计算
When all resources are loaded, perform second round of width calculations. This is important especially when your table contains resources that are loaded after DOM ready event, such as images, @font-face and more, which will influence how table column widths are computed.
加载所有资源后,执行第二轮宽度计算。 这一点特别重要,尤其是当您的表包含在DOM ready事件之后加载的资源(例如图像,@ font-face等)时,这些资源会影响表列宽度的计算方式。
When the parent wrapper is scrolled, but this only happens if the content is overflowing. In the event of a scrolling event is detected, we want to reposition the sticky column
滚动父包装器时,但这仅在内容溢出时才发生。 如果检测到滚动事件,我们要重新定位粘性列
When the viewport is resized, we want to recompute widths and reposition the sticky header
调整视口大小后,我们要重新计算宽度并重新定位粘性标头
When the window is scrolled, we want to reposition the sticky header
滚动窗口时,我们要重新定位粘性标题
This can be easily summarized with the code below. Do note that the resize and scroll events are debounced and throttled respectively using Bel Alman’s jQuery throttle+debounce plugin.
可以使用以下代码轻松总结。 请注意,分别使用Bel Alman的jQuery油门+防弹插件来分别对resize和scroll事件进行去弹和抑制。
// #1: When DOM is ready (remember, we have wrapped this entire script in $(function(){...});
setWidths();
// #2: Listen to scrolling event on the parent wrapper (will fire if there is an overflow)
$t.parent('.sticky-wrap').scroll($.throttle(250, function() {
repositionStickyHead();
repositionStickyCol();
}));
// Now we bind events to the $(window) object
$w
// #3: When all resources are loaded
.load(setWidths)
// #4: When viewport is resized
// (we debounce this so successive resize event is coalesced into one event)
.resize($.throttle(250, function () {
setWidths();
repositionStickyHead();
repositionStickyCol();
})
// #5: When the window is scrolled
// (we throttled this so scroll event is not fired too often)
.scroll($.throttle(250, repositionStickyHead);
And voila, you’re done!
瞧,您完成了!
讨论区 (Discussion)
No tutorial is complete without a discussion — be it addressing potential drawbacks on the technicalities of implementation, or the explanation of my strategy in contrary to common expectations.
没有讨论,没有一部教程是完整的-它解决的是实施技术上的潜在弊端,或者是我对策略的解释与一般期望相反。
您为什么不使用position: fixed?
(Why don’t you use position: fixed?
)
Fixed positioning is a very tantalizing alternative, mainly because of the two advantages it confers:
固定定位是非常诱人的选择,主要是因为它具有两个优点:
No calculations are needed for the sticky table header’s vertical offset, because fixed position will cause the element to be positioned absolutely within the viewport and out of the document flow, and
无需计算粘性表标题的垂直偏移量,因为固定位置将导致元素完全定位在视口内并且不在文档流中,并且
Avoids the stuttering effect of the sticky table header playing catch up, as the scroll event is throttled and therefore the calculations are performed at fixed time intervals and not on the fly. It may appear less responsive to user movement and therefore less natural.
避免了粘性表头打赶上的口吃效果,作为滚动事件被节流,因此,计算以固定的时间间隔执行,而不是在飞行。 它似乎对用户移动的响应较弱,因此不自然。
However, the issue with fixed positioning is that we are effectively removing the element from the document flow. In the event when the table width exceeds that of its containing and a horizontal overflow is absolutely necessary, the fixed position header will not scroll with the table because it is detached from the document layout. This is one of the major drawbacks with many jQuery plugins out there that offers sticky table header functionality, and this tutorial was written partially to address this issue.
但是,固定位置的问题在于我们正在有效地从文档流中删除元素。 如果表格的宽度超过其容纳的宽度并且绝对需要水平溢出,则固定位置标题将不会与表格一起滚动,因为它与文档布局分开了。 这是许多提供粘滞表头功能的jQuery插件的主要缺点之一,而本教程的编写也是为了解决此问题。
为什么不使用position: sticky
? (Why don’t you use position: sticky
?)
The new position attribute, position: sticky
, is only available to the latest version of WebKit browsers and require vendor prefixes. It is not supported in Firefox, Internet Explorer and Opera, therefore risking alienating a huge user base (~95%) due to lack of support. It is not official and standardized as of yet, so I would rather err on the side of caution, and choose a more cumbersome but cross-browser friendly JS-based solution.
新的position属性position: sticky
仅适用于最新版本的WebKit浏览器,并且需要供应商前缀。 它在Firefox,Internet Explorer和Opera中不受支持,因此由于缺乏支持而有可能疏远庞大的用户群(约95%)。 到目前为止,它还不是官方的和标准化的,所以我宁可谨慎一点,而是选择一个更麻烦但跨浏览器友好的基于JS的解决方案。
翻译自: https://tympanus.net/codrops/2014/01/09/sticky-table-headers-columns/
粘性头布局和流式布局