Привет, Хабр!
Начать данную статью хотелось бы с небольшого лирического отступления. Недавно, в очередной раз столкнувшись со сложным кейсом на работе и прошерстив добрую половину интернета в поисках истины, попутно приобретя с десяток седых волос, у меня начало болеть сердечко за начинающих специалистов, которые выбрали нелегкий путь разработчика JS. Как вы, вероятно, догадались, сам я не отношусь к категории людей 'В детстве мама включала мне вместо мультиков курсы Javascript для самых маленьких, в 5 я написал свой интернет магазин,а в 10 в первый раз взломал сайт Пентагона'. Удивительно, но на похожие резюме я не раз натыкался на просторах интернета, те самые 25-ти летние специалисты с 22-х летним опытом на самом деле существуют и, наверное, не имеют тех проблем, с которыми приходится сталкиваться мне - человеку, которому разработка даётся довольно нелегко и который до недавнего времени считал, что нормальный код могут писать только люди со сверхразумом и поэтому боялся даже попробовать. Я считаю, что таких как я на самом деле очень много, особенно учитывая безумный рост популярности программирования (и Javascript в частности) в широких массах в последнее время. Поэтому решил периодически публиковать здесь свои заметки и кейсы, с которыми я сталкивался в работе и которые вызывали у меня определенные трудности, чтобы несколько облегчить жизнь разработчикам, встречающим схожие проблемы.
P.S. я не претендую на экспертность знаний, поэтому, с определенной вероятностью могут найтись куда более оптимальные решения. Помните: 'Я художник - я так вижу'.
А теперь плавно переходим к теме статьи. Ниже приведен список вопросов, которые я постараюсь осветить в данной заметке:
Как сделать таблицу с фиксированной шапкой и скроллом в body?
Как быть, когда cодержимое таблицы съезжает относительно шапки при появлении скролла? Установка css-переменной scroll-width.
Оптимизация и кастомизация скролла: плавность, scroll margin, изменение цвета и формы.
Как сделать таблицу с фиксированной шапкой и скроллом в body?
Начнем с того, что сделаем самую обыкновенную табличку и заполним ее данными из faker для наглядности. Получаем примерно такую картину(верстку делаем самую базовую, просто чтобы глаз не дергался от одного ее вида):
Код
import React from 'react';
import { render } from 'react-dom';
import faker from 'faker';
import './index.css';
// генерируем данные
const users = Array(100).fill(null).map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email(),
}));
const App = () => (
<table >
<thead>
<tr>
<th>First</th>
<th>Last</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map(x => (
<tr key={x.id}>
<td>{x.first}</td>
<td>{x.last}</td>
<td>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
render(<App />, document.getElementById('root'));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
thead {
background-color: teal;
}
td,
th {
padding: 15px;
text-align: left;
}
th {
color: white;
}
Как мы видим, по умолчанию снаружи у нас появился скролл, но нам такая большая таблица не нужна, по условию мы хотим, чтобы скролл у нас был только у тела таблицы, а header над всем этим делом нависал сверху, ну и чтобы в высоту наша табличка была не больше 200 пикселей. В общем, что-то наподобие вот этого:
Нужного нам результата можно добиться несколькими способами:
Использовать библиотеку по типу React-Table, React-Virtualized, либо какую-нибудь еще, чтобы вообще не заморачиваться. Настоятельно рекоммендую хотя бы с одной из них ознакомиться, поскольку они очень значительно облегчают жизнь разработчика при работе с таблицами.
Немного подшаманить CSS, чем мы сейчас и займемся
Первое,что мне лично пришло на ум, когда я в первый раз столкнулся с подобной задачей - нужно всего лишь поменять свойство display у тегов tbody и thead на 'block' и накинуть на tbody ограничение по высоте (здесь я считаю необходимым прояснить небольшой момент, вызывающий трудности у начинающих: многие пытаются просто задать высоту для body, а потом не могут понять, почему это не работает. Нужно просто не забыть еще прописать свойство overflow, и все получится).
Зачем нам менять display? Дело в том, что свойство display у элементов thead и tbody имеет значение row-group(table-header-group и table-row-group соответственно), что в свою очередь не позволяет нам вводить ограничение по высоте, а значит и скролла у них не будет, поэтому мы меняем их на понятный нам display: 'block', которым проще управлять .
Напоминалка про поведение атрибута height в таблицах
Когда в документе задан <!DOCTYPE>, браузеры игнорируют высоту таблицы, заданную через атрибут height. По умолчанию высота вычисляется на основе содержимого таблицы.
Здесь есть несколько выжных моментов, о которых не стоит забывать:
Меняя свойство display на 'block', мы как бы ломаем семантику, и наш браузер уже больше не считает нашу таблицу таблицей, что также оказывает влияние на скринридеры.
Теперь наш тег tr уже не занимает все свободное пространство контейнера и теперь нам нужно все это дело выровнять.
Код
import React from 'react';
import { render } from 'react-dom';
import faker from 'faker';
import './index.css';
// генерируем данные
const users = Array(100).fill(null).map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email(),
}));
const App = () => (
<table >
<thead>
<tr>
<th>First</th>
<th>Last</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map(x => (
<tr key={x.id}>
<td>{x.first}</td>
<td>{x.last}</td>
<td>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
render(<App />, document.getElementById('root'));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
thead {
display: block;
background-color: teal;
}
tbody {
display: block;
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
min-height: 100px;
}
tr {
border: 1px solid black;
}
td,
th {
text-align: left;
padding: 15px;
}
th {
color: white;
}
Как будем выравнивать?
В общем, в любой непонятной ситуации используйте Flex или Grid и будет вам счастье. Ниже приведу два готовых базовых примера, html трогать не буду, но я думаю, что и так понятно, что можно сразу верстать на div и маркированных списках при желании. Дальше уже по ситуации можете довести до ума верстку, например: добавить ограничение таблицы по ширине, нужный вам формат переноса строк и адаптив, если конечно он нужен, так как все же в большинстве своем контент таблиц предназначен для использования на больших экранах. В любом случае, более продвинутая верстка легко гуглится.
Готовый вариант с display: 'Flex'
import React from 'react';
import { render } from 'react-dom';
import faker from 'faker';
import './index.css';
// генерируем данные
const users = Array(100).fill(null).map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email(),
}));
const App = () => (
<table className='table'>
<thead>
<tr className='header'>
<th className='firstColumn'>First</th>
<th className='secondColumn'>Last</th>
<th className='thirdColumn'>Email</th>
</tr>
</thead>
<tbody>
{users.map(x => (
<tr className='body' key={x.id}>
<td className='firstColumn'>{x.first}</td>
<td className='secondColumn'>{x.last}</td>
<td className='thirdColumn'>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
render(<App />, document.getElementById('root'));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
thead {
background-color: teal;
color: white;
}
thead,
tbody,
th,
td {
display: block;
text-align: left;
}
tbody {
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
min-height: 100px;
}
tr {
display: flex;
}
.firstColumn,
.secondColumn {
flex: 1;
}
.thirdColumn {
flex: 3;
}
td,
th {
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Готовый вариант с display 'Grid'
import React from 'react';
import { render } from 'react-dom';
import faker from 'faker';
import './index.css';
// генерируем данные
const users = Array(100).fill(null).map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email(),
}));
const App = () => (
<table className='table'>
<thead>
<tr className='header'>
<th>First</th>
<th>Last</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map(x => (
<tr className='body' key={x.id}>
<td>{x.first}</td>
<td>{x.last}</td>
<td>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
render(<App />, document.getElementById('root'));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
tr {
display: grid;
grid-template-columns: 1fr 1fr 2fr;
}
thead {
background-color: teal;
color: white;
}
thead,
tbody,
th,
td {
display: block;
text-align: left;
}
tbody {
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
min-height: 100px;
}
td,
th {
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Чуть не забыл отметить одну очень важную вещь, с которой я в свое время достаточно долго пытался разобраться: НА МАКАХ ЕСТЬ ФУНКЦИЯ АВТОМАТИЧЕСКОГО СКРЫТИЯ СКРОЛЛБАРА В БРАУЗЕРАХ, КОТОРАЯ ВЫКЛЮЧАЕТСЯ В НАСТРОЙКАХ. Если вы выставляете overflow: scroll, и ждете, что scroll теперь будет виден постоянно, то на маке он по умолчанию может пропадать и быть видимым только в момент прокрутки. Плюс на маках скролл у нас как бы парит в воздухе над контентом, аналогично position: absolute и не занимает места, если соответствующая настройка включена (по умолчанию она всегда включена).
Да-да, получается что внешний вид скролла зависит также от операционной системы, а не только от браузера.
Глядя на результат для windows, мы плавно подходим к следующей теме.
Как быть, когда cодержимое таблицы съезжает относительно шапки при появлении скролла? Установка css-переменной scroll-width
Мы отчетливо видим, что на windows скролл при появлении съедает часть пространства, что в свою очередь приводит к тому, что контент нашей таблицы съезжает относительно шапки. Это лишь один из примеров, но есть и другие кейсы, когда скролл негативно влияет на качество верстки. Казалось бы, проблема очевидная, и наверняка должен быть способ как-то с этим бороться, однако, к сожалению, на данный момент есть только костыльные решения (во всяком случае я не нашел того, что бы меня устроило). Ниже я расскажу о самом распространенном варианте решения данной проблемы, плюс затрону относительно новое CSS свойство, которое должно потенциально избавить разработчиков от этой головной боли.
Ну что ж, приступим! Алгоритм прост и содержит всего лишь 2 шага:
Задаем для overflow-y значение scroll, чтобы в любом состоянии скролл у нас присутствовал в таблице (отмечу, что часто дизайнеры не в восторге от такого поворота событий, не по фэншую это всё и всю красоту портит).
Высчитываем ширину скролла в браузере и двигаем шапку на нужное расстояние.
Как вы уже догадались из заголовка, значение ширины скролла очень удобно хранить в css переменной, чтобы иметь к нему доступ в любом месте в приложении. Ниже я расскажу, как это сделать
Как посчитать размер скролла и положить его в css-переменную?
Я приведу лишь один из методов, которым пользуюсь сам, но вы также можете воспользоваться для этих целей либо одним из готовых решений, либо хотя бы посмотреть, как именно они реализованы и сделать на их основе свое. Принцип везде один и тот же.
const setScrollbarWidth = () => {
// Создаем контейнер-болванку
const outerContainer = document.createElement('div');
//Накидываем стили
outerContainer.style.visibility = 'hidden';
outerContainer.style.overflow = 'scroll';
//Добавляем его к body
document.body.appendChild(outerContainer);
// Создаем еще один контейнер и помещаем его внутрь outerContainer
const innerContainer = document.createElement('div');
outerContainer.appendChild(innerContainer);
// Высчитываем ширину нашего скроллбара
const scrollbarWidth =
(outerContainer.offsetWidth - innerContainer.offsetWidth);
// Создаем css-переменную
document.documentElement.style.setProperty('--scroll-width',
`${scrollbarWidth}px`);
// Удаляем наш контейнер-болванку
outerContainer.parentNode.removeChild(outerContainer);
}
Теперь нам остается только вызвать функцию при загрузке приложения и использовать значение ширины скролла в нужном нам месте. Если кто-то ранее не сталкивался с css-переменными, то можете поискать на хабре, тут есть неплохие статьи на эту тему с большим кол-вом примеров. Использовать css-переменные очень просто:
padding-right: var(--scroll-width)
Применяя все вышеописанное к моему изначальному коду получаем:
Код
import React, { useEffect } from "react";
import { render } from "react-dom";
import faker from "faker";
import "./index.css";
const setScrollbarWidth = () => {
const outerContainer = document.createElement("div");
outerContainer.style.visibility = "hidden";
outerContainer.style.overflow = "scroll";
document.body.appendChild(outerContainer);
const innerContainer = document.createElement("div");
outerContainer.appendChild(innerContainer);
const scrollbarWidth =
outerContainer.offsetWidth - innerContainer.offsetWidth;
document.documentElement.style.setProperty(
"--scroll-width",
`${scrollbarWidth}px`
);
outerContainer.parentNode.removeChild(outerContainer);
};
// генерируем данные
const users = Array(100)
.fill(null)
.map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email()
}));
const App = () => {
useEffect(() => setScrollbarWidth(), []);
return (
<table className="table">
<thead>
<tr className="header">
<th>First</th>
<th>Last</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map((x) => (
<tr className="body" key={x.id}>
<td>{x.first}</td>
<td>{x.last}</td>
<td>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
};
render(<App />, document.getElementById("root"));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
tr {
display: grid;
grid-template-columns: 1fr 1fr 2fr;
}
thead {
padding-right: var(--scroll-width);
background-color: teal;
color: white;
}
thead,
tbody,
th,
td {
display: block;
text-align: left;
}
tbody {
overflow-y: scroll;
overflow-x: hidden;
max-height: 200px;
min-height: 100px;
}
td,
th {
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
В очередной раз хочу напомнить, что на маке ничего съезжать не будет по умолчанию, но если уж вы включите скролл в настройках, то предложенное мной решение также отлично отработает и всё будет ровненько.
Раньше было еще одно решение данной проблемы - использование overflow в значении overlay, что позволяло сделать скроллбар примерно таким же, как сейчас на маках, т.е. не занимающим места (поверх контента), однако это свойство устаревшее и использовать его - это очень плохая идея.
Можно ли каким-то образом все-таки избежать использования overflow: scroll?
Ответ: в данной ситуации можно, нужно всего лишь немного усовершенствовать наш костыль.
Алгоритм действий:
Объявляем отдельный класс с нашим padding
Меняем overflow на auto (чтобы скролл появлялся только по необходимости)
При загрузке страницы определяем ширину шапки и body
Используем classnames, чтобы установить условие на добавление нашего новго класса к шапке: ширина body не должна быть равна ширине шапки (то есть мы добавляем сдвиг, только если в body есть scroll). Для удобства храним результат проверки в стэйте.
Финальный код
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
import faker from "faker";
import cx from "classnames";
import "./index.css";
const setScrollbarWidth = () => {
const outerContainer = document.createElement("div");
outerContainer.style.visibility = "hidden";
outerContainer.style.overflow = "scroll";
document.body.appendChild(outerContainer);
const innerContainer = document.createElement("div");
outerContainer.appendChild(innerContainer);
const scrollbarWidth =
outerContainer.offsetWidth - innerContainer.offsetWidth;
document.documentElement.style.setProperty(
"--scroll-width",
`${scrollbarWidth}px`
);
outerContainer.parentNode.removeChild(outerContainer);
};
// генерируем данные
const users = Array(30)
.fill(null)
.map(() => ({
id: faker.random.uuid(),
first: faker.name.firstName(),
last: faker.name.lastName(),
email: faker.internet.email()
}));
const App = () => {
const [isShiftAllowed, setIsShiftAllowed] = useState(false);
useEffect(() => {
setScrollbarWidth();
const headerWidth = document.querySelector(".header").clientWidth;
const bodyWidth = document.querySelector(".body").clientWidth;
headerWidth - bodyWidth !== 0
? setIsShiftAllowed(true)
: setIsShiftAllowed(false);
}, []);
return (
<table className="table">
<thead className={cx({ shift: isShiftAllowed })}>
<tr className="header">
<th>First</th>
<th>Last</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{users.map((x) => (
<tr className="body" key={x.id}>
<td>{x.first}</td>
<td>{x.last}</td>
<td>{x.email}</td>
</tr>
))}
</tbody>
</table>
);
};
render(<App />, document.getElementById("root"));
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
}
.shift {
padding-right: var(--scroll-width);
}
tr {
display: grid;
grid-template-columns: 1fr 1fr 2fr;
}
thead {
background-color: teal;
color: white;
}
thead,
tbody,
th,
td {
display: block;
text-align: left;
}
tbody {
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
min-height: 100px;
}
td,
th {
padding: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Если кто-нибудь знает, как можно всё это сделать более оптимальным способом, то пожалуйста дайте мне об этом знать в комментариях.
Я упоминал о том, что есть новое css свойство, которое может стать настоящим спасением в данном вопросе, имя ему scrollbar-gutter. Еще раз повторюсь, это новое свойство, и на данный момент оно поддерживается только браузерами Opera, Chrome и Edge, так сказать still work in progress. Как объясняется в MDN, scrollbar gutter - это собственно и есть контейнер, в котором лежит наш бегунок скроллбара (если конечно overflow не находится в значении overlay). Соответственно, комбинирование значений данного свойства со значениями свойства overflow позволит гибко управлять поведением этого контейнера, резервировать место и т.д. Очень надеюсь, что в ближайшем будущем его наконец полноценно допилят.
Заметка получилась уже довольно большой, но у нас осталась еще одна тема, потерпите еще немного, постараюсь написать чего-нибудь интересненького.
Оптимизация и кастомизация скролла.
Большую часть информации для данного раздела я подчеркнул из статей, автором которых является Josh W Comeau, всем рекоммендую посетить его вебсайт https://www.joshwcomeau.com/, там вы найдете очень много полезностей.
Добавляем плавность
Возможно, глядя на заголовок, кто-то из вас сейчас сидит в недоумении: 'Плавность? Скролл ведь и так плавный, о чем ты?'. Так-то оно так, но я вот о что имею ввиду: все мы прекрасно знаем о том, что в html можно сделать ссылку-якорь, чтобы при клике на заголовок перемещаться к нужному фрагменту текста, самый базовый пример можно найти на всеми любимой википедии:
Как мы видим, перемещение происходит мгновенно, однако мы можем сделать этот переход плавным с помощью CSS (возможно это для вас конечно не новость, но кому-то точно будет полезно). Для этого нам нужно использовать вот этот кусочек кода:
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
Теперь то же действие на страничке википедии происходит таким образом:
Кстати говоря, если вдруг кто-то не знал, в Chrome devtools можно также добавлять media queries. Вот здесь можно найти подробную инструкцию о том, как это сделать: https://daveceddia.com/inspector-stylesheet-chrome/. Кейс супер редкий, но мало ли, вдруг понадобится.
На мой взгляд более плавное перемещение при клике на ссылку-якорь выглядит куда приятнее и позволяет лучше ориентироваться на странице, да и поддерживается достаточно хорошо, не работает лишь в Internet Explorer и SAFARI.
Отмечу также важный момент: если вы добавляете scroll-behavior: smooth в html, то имейте ввиду, что это автоматически также аффектит поведение метода window.scrollTo (тоже становится плавным), который используется, например, для того чтобы переместить пользователя наверх страницы в ответ на submit. Если вдруг вы раньше не сталкивались с этим методом, то обязательно ознакомьтесь и также не забудьте про метод scrollIntoView (разница между ними в том, что первый оперирует пикселями, а второй перемещает к конкретному элементу).
Scroll margin
Как мы видели на примере выше, при клике на ссылку-якорь наша страничка прокручивается таким образом, что нужный нам текст оказывается в самом верху экрана. Однако, если у нас имеется header со свойством position в значении sticky или fixed, то часть нужного нам контента обязательно заползет под него и нам придется после этого скроллить страничку вверх, чтобы этот самый контент стало видно.
Здесь нам поможет использование свойства scroll-margin-top, которое определяет, где элемент должен находиться относительно верхней части окна при прокрутке. Со значением нужно поэкспериментировать и подобрать подходящее под вашу конкретную ситуацию.
Изменение цвета.
Иногда стандартный скролл очень сильно выбивается из общей картины сайта и портит все впечатление, поэтому, зачастую, встает вопрос о том, каким образом можно его хотя бы немного кастомизировать.
Возможно вы уже сталкивались со свойтвом scrollbar-color, которое позволяет изменить цвет скроллбара, только вот к сожалению поддерживается оно только браузером Firefox.
body {
scrollbar-color: color1 color2;
}
Это конечно здорово, но сразу возникает вопрос: 'Есть ли альтернатива для остальных браузеров? ' Альтернатива есть и имя ей: Vendor-prefixes.
::-webkit-scrollbar {
/* Цвет контейнера */
background-color: color2;
}
::-webkit-scrollbar-thumb {
/* Цвет ползунка */
background-color: color1;
}
Однако, стоит отметить, что если мы изменим цвет таким образом, то результат будет достаточно топорным для остальных браузеров, так как мы затираем стандартные свойства скроллбара. Вот вам для примера сравнение результата в браузерах Chrome и Firefox:
Чтобы сделать картинку получше и добиться примерно одинакового внешнего вида в разных браузерах, нам нужно накинуть дополнительных стилей (на наше счастье псевдоэлемент -webkit-scrollbar позволяет нам это сделать):
<style>
html {
--background: hsl(210deg, 15%, 6.25%);
--text: hsl(210deg, 10%, 90%);
--gray-300: hsl(210deg, 10%, 40%);
--gray-500: hsl(210deg, 8%, 50%);
/* Official styles (Firefox) */
scrollbar-color:
var(--gray-300)
var(--background);
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 10px;
background-color: var(--background);
}
::-webkit-scrollbar-thumb {
border-radius: 1000px;
background-color: var(--gray-300);
border: 2px solid var(--background);
}
/*
Little bonus: on non-Firefox browsers,
the thumb will light up on hover!
*/
::-webkit-scrollbar-thumb:hover {
background-color: var(--gray-500);
}
</style>
Ну вот и все, друзья мои, я рассказал вам обо всех моментах, которые раньше у меня вызывали определенные трудности. Данная заметка - это определенно всего лишь 'Капля в море' и у вас 100% найдется еще большое количество вопросов и разного рода уточнений, a также замечаний, надеюсь увидеть их в комментариях (я пока еще учусь, так что не судите строго).
В дальнейшем попробую еще написать набольшую заметку про 2 другие интересные темы, касающиеся скролла: Cumulative Layout Shifts и Scroll Snapping.
Очень надеюсь, что вам было хоть немного полезно и интересно!
Комментарии (8)
Holix
12.01.2022 19:35+6Насильно включенный плавный scroll -- зло. Ломается поведение ролика мышки, лагают переходы по странице. Если кому-то это нравится -- пусть сам включит в своём браузере.
Bigata
12.01.2022 21:29Если таблица большая, то подвинуть шапку вызовет значительный лаг - браузер перерисует всю таблицу, ширины всех столбцов перерендерит.
AverageFrontender Автор
12.01.2022 21:56Для огромных таблиц, да и в целом для рендеринга больших списков данных лучше пользоваться библиотеками, чтобы это дело максимально оптимизировать. React-virtualized прекрасно подойдет
pharrell
Зачем нарушать семантику и менять display у элементов таблицы, если всю таблицу можно поместить в div.scrollable, который уже и так block, и просто накинуть ему height и overflow?
Как это сделано в bootstrap, например
ionicman
Так иначе скролл будет у самого div, а не у таблицы со всеми вытекающими, а тут зафиксирован заголовок таблицы всеми этими танцами и скролл находится в самой таблице и скроллит только ее данные.
PaulZi
https://css-tricks.com/position-sticky-and-table-headers/
AverageFrontender Автор
position: 'sticky' - хорошая вещь, но все же это не совсем то поведение, которого я хотел добиться в статье. С данным решением суть будет такая же, как и предложение от @pharrell.