Боюсь, что не существует идеального решения.
Я предоставлю вам разные варианты, и вам нужно решить, от чего отказаться.
Абсолютно позиционированные столбцы .
✅ Pure CSS - ✅ Старые браузеры - ❌ Гибкая ширина - ❌ Правильная подсветка
Ключ должен установить фиксированный width
для первых трех столбцов, а затем установить margin-left
свойство элемента .wrapped
, равное общей ширине столбцов.
.wrapper {
overflow-x: scroll;
overflow-y: visible;
margin-left: 18em;
width: 15em;
background-color: greenyellow;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th, tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1),
td:nth-of-type(1) {
position: absolute;
left: 0em;
width: 6em;
}
th:nth-of-type(2),
td:nth-of-type(2) {
position: absolute;
left: 6em;
width: 6em;
}
th:nth-of-type(3),
td:nth-of-type(3) {
position: absolute;
left: 12em;
width: 6em;
}
tr:hover td {
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-5</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
Абсолютно позиционированные столбцы + JavaScript.
❌ Pure CSS - ✅ Старые браузеры - ✅ Гибкая ширина - ❌ Правильная подсветка
Здесь вместо жесткого кодирования width
столбцов заранее мы используем JS для вычисления и установки width
.
Возможна гораздо лучшая реализация JS. Возьмите это только в качестве примера.
let wrapper = document.querySelector('.wrapper');
let cols = document.querySelectorAll('th');
let widthCol0 = cols[0].offsetWidth;
let widthCol1 = cols[1].offsetWidth;
let widthCol2 = cols[2].offsetWidth;
stylesheet = document.styleSheets[0]
stylesheet.insertRule('th:nth-of-type(1), td:nth-of-type(1) { left: 0px; position: absolute; width: ' + widthCol0 + 'px;}', 0);
stylesheet.insertRule('th:nth-of-type(2), td:nth-of-type(2) { left: ' + widthCol0 + 'px; position: absolute; width: ' + widthCol1 + 'px;}', 0);
stylesheet.insertRule('th:nth-of-type(3), td:nth-of-type(3) { left: ' + (widthCol0 + widthCol1) + 'px; position: absolute; width: ' + widthCol2 + 'px;}', 0);
wrapper.style.marginLeft = (widthCol0 + widthCol1 + widthCol2) + 'px';
.wrapper {
overflow-x: scroll;
overflow-y: visible;
width: 15em;
background-color: greenyellow;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th, tr:nth-of-type(1) td {
height: 1em;
}
tr:hover td {
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-5</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
Закрепленные столбцы.
✅ Pure CSS - ❌ Старые браузеры - ❌ Гибкая ширина - ✅ Собственные выделите
Мне очень нравится решение position: sticky
, которое показало @ Domini c. Это способ go, если вам не требуется поддержка IE.
Отбрасывание IE поддержка окупается. Вы можете иметь правильное выделение, и ширина первых трех столбцов будет адаптироваться к содержанию.
Тем не менее, даже если вам не нужно жестко кодировать width
, вам нужно жестко закодировать свойство left
, чтобы установить, в какой точке столбцы становятся липкими. Такого рода поражения имеют смысл иметь гибкий width
. Обходного пути нет.
.wrapper {
width: 40em;
overflow-x: scroll;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th,
tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1), td:nth-of-type(1),
th:nth-of-type(2), td:nth-of-type(2),
th:nth-of-type(3), td:nth-of-type(3) {
background-color: white;
position: sticky;
}
th:nth-of-type(1), td:nth-of-type(1) {
left: 0em;
}
th:nth-of-type(2), td:nth-of-type(2) {
left: 6em;
}
th:nth-of-type(3), td:nth-of-type(3) {
left: 12em;
}
tr:hover td{
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-6</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
</div>
Прикрепленные столбцы + JavaScript.
❌ Pure CSS - ❌ Старые браузеры - ✅ Гибкая ширина - ✅ Правильное выделение
Этот пример имеет те же значения HTML и CSS, что и выше.
Как и во втором примере, мы используем JS для вычисления width
колонны. В этом случае мы используем его для переопределения свойства left
. Кроме того, код JS более прост.
Почему мы устанавливаем свойство left
в CSS, если мы собираемся установить его позже с JS? Потому что, если клиент не запускает JS, мы не хотим, чтобы столбцы полностью разрушались, нарушая наш макет.
let cols = document.querySelectorAll('th');
let widthCol0 = cols[0].offsetWidth;
let widthCol1 = cols[1].offsetWidth;
stylesheet = document.styleSheets[0];
stylesheet.insertRule('th:nth-of-type(2), td:nth-of-type(2) { left: ' + widthCol0 + 'px !important;}', 0);
stylesheet.insertRule('th:nth-of-type(3), td:nth-of-type(3) { left: ' + (widthCol0 + widthCol1) + 'px !important;', 0);
.wrapper {
width: 40em;
overflow-x: scroll;
}
td,
th {
vertical-align: top;
border-top: 1px solid #ccc;
padding: 0.8em;
}
th,
tr:nth-of-type(1) td {
height: 1em;
}
th:nth-of-type(1), td:nth-of-type(1),
th:nth-of-type(2), td:nth-of-type(2),
th:nth-of-type(3), td:nth-of-type(3) {
background-color: white;
position: sticky;
}
th:nth-of-type(1), td:nth-of-type(1) {
left: 0em;
}
th:nth-of-type(2), td:nth-of-type(2) {
left: 6em;
}
th:nth-of-type(3), td:nth-of-type(3) {
left: 12em;
}
tr:hover td{
background-color: yellow;
}
<div class="wrapper">
<table>
<thead>
<tr>
<th>Header-1</th>
<th>Header-2</th>
<th>Header-3</th>
<th>Header-4</th>
<th>Header-5</th>
<th>Header-6</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>col 1 - A (WITH LONGER CONTENT)</td>
<td>col 2 - A</td>
<td>col 3 - A</td>
<td>col 4 - A (WITH LONGER CONTENT)</td>
<td>col 5 - A</td>
<td>col 6 - A</td>
</tr>
<tr>
<td>col 1 - B</td>
<td>col 2 - B</td>
<td>col 3 - B</td>
<td>col 4 - B</td>
<td>col 5 - B</td>
<td>col 6 - B</td>
</tr>
<tr>
<td>col 1 - C</td>
<td>col 2 - C (WITH_A_LONG_WORD)</td>
<td>col 3 - C</td>
<td>col 4 - C</td>
<td>col 5 - C</td>
<td>col 6 - C (WITH_A_LONG_WORD)</td>
</tr>
</tbody>
</table>
</div>
Заключительные замечания
Вам решать, какой подход лучше всего соответствует вашим потребностям.
Лично я считаю 4-е место самым мощным, потому что:
Если JS отключено, столбцы рухнут за пределы оптимальной точки, но все будет работать правильно. Просто поиграйте со стандартным свойством left
в CSS.
В IE11 таблица будет красиво возвращаться к таблице нефиксированных столбцов.
Не думаю, что это большое дело. Конечно, если вы программируете для intr anet, где IE11 является выбранным браузером, используйте первый или второй подход.