Привет, Хабр!

Начать данную статью хотелось бы с небольшого лирического отступления. Недавно, в очередной раз столкнувшись со сложным кейсом на работе и прошерстив добрую половину интернета в поисках истины, попутно приобретя с десяток седых волос, у меня начало болеть сердечко за начинающих специалистов, которые выбрали нелегкий путь разработчика 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 пикселей. В общем, что-то наподобие вот этого:

Нужного нам результата можно добиться несколькими способами:

  1. Использовать библиотеку по типу React-Table, React-Virtualized, либо какую-нибудь еще, чтобы вообще не заморачиваться. Настоятельно рекоммендую хотя бы с одной из них ознакомиться, поскольку они очень значительно облегчают жизнь разработчика при работе с таблицами.

  2. Немного подшаманить 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. По умолчанию высота вычисляется на основе содержимого таблицы.

Здесь есть несколько выжных моментов, о которых не стоит забывать:

  1. Меняя свойство display на 'block', мы как бы ломаем семантику, и наш браузер уже больше не считает нашу таблицу таблицей, что также оказывает влияние на скринридеры.

  2. Теперь наш тег tr уже не занимает все свободное пространство контейнера и теперь нам нужно все это дело выровнять.

Для наглядности добавил border.
Для наглядности добавил border.
Код
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
А вот так тот же код выглядит на windows

Да-да, получается что внешний вид скролла зависит также от операционной системы, а не только от браузера.

Глядя на результат для windows, мы плавно подходим к следующей теме.

Как быть, когда cодержимое таблицы съезжает относительно шапки при появлении скролла? Установка css-переменной scroll-width

Мы отчетливо видим, что на windows скролл при появлении съедает часть пространства, что в свою очередь приводит к тому, что контент нашей таблицы съезжает относительно шапки. Это лишь один из примеров, но есть и другие кейсы, когда скролл негативно влияет на качество верстки. Казалось бы, проблема очевидная, и наверняка должен быть способ как-то с этим бороться, однако, к сожалению, на данный момент есть только костыльные решения (во всяком случае я не нашел того, что бы меня устроило). Ниже я расскажу о самом распространенном варианте решения данной проблемы, плюс затрону относительно новое CSS свойство, которое должно потенциально избавить разработчиков от этой головной боли.

Ну что ж, приступим! Алгоритм прост и содержит всего лишь 2 шага:

  1. Задаем для overflow-y значение scroll, чтобы в любом состоянии скролл у нас присутствовал в таблице (отмечу, что часто дизайнеры не в восторге от такого поворота событий, не по фэншую это всё и всю красоту портит).

  2. Высчитываем ширину скролла в браузере и двигаем шапку на нужное расстояние.

Как вы уже догадались из заголовка, значение ширины скролла очень удобно хранить в 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?

Ответ: в данной ситуации можно, нужно всего лишь немного усовершенствовать наш костыль.

Алгоритм действий:

  1. Объявляем отдельный класс с нашим padding

  2. Меняем overflow на auto (чтобы скролл появлялся только по необходимости)

  3. При загрузке страницы определяем ширину шапки и body

  4. Используем 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>
Josh W Comeau просто лучший)
Josh W Comeau просто лучший)

Ну вот и все, друзья мои, я рассказал вам обо всех моментах, которые раньше у меня вызывали определенные трудности. Данная заметка - это определенно всего лишь 'Капля в море' и у вас 100% найдется еще большое количество вопросов и разного рода уточнений, a также замечаний, надеюсь увидеть их в комментариях (я пока еще учусь, так что не судите строго).

В дальнейшем попробую еще написать набольшую заметку про 2 другие интересные темы, касающиеся скролла: Cumulative Layout Shifts и Scroll Snapping.

Очень надеюсь, что вам было хоть немного полезно и интересно!

Комментарии (8)


  1. pharrell
    12.01.2022 18:54

    Зачем нарушать семантику и менять display у элементов таблицы, если всю таблицу можно поместить в div.scrollable, который уже и так block, и просто накинуть ему height и overflow?

    Как это сделано в bootstrap, например


    1. ionicman
      12.01.2022 18:59

      Так иначе скролл будет у самого div, а не у таблицы со всеми вытекающими, а тут зафиксирован заголовок таблицы всеми этими танцами и скролл находится в самой таблице и скроллит только ее данные.


      1. PaulZi
        12.01.2022 19:44
        +1

        1. AverageFrontender Автор
          12.01.2022 22:10
          +1

          position: 'sticky' - хорошая вещь, но все же это не совсем то поведение, которого я хотел добиться в статье. С данным решением суть будет такая же, как и предложение от @pharrell.


  1. Holix
    12.01.2022 19:35
    +6

    Насильно включенный плавный scroll -- зло. Ломается поведение ролика мышки, лагают переходы по странице. Если кому-то это нравится -- пусть сам включит в своём браузере.


    1. vyacheslavchulkin
      13.01.2022 11:22

      С тачпадом вообще иногда жесть происходит


  1. Bigata
    12.01.2022 21:29

    Если таблица большая, то подвинуть шапку вызовет значительный лаг - браузер перерисует всю таблицу, ширины всех столбцов перерендерит.


    1. AverageFrontender Автор
      12.01.2022 21:56

      Для огромных таблиц, да и в целом для рендеринга больших списков данных лучше пользоваться библиотеками, чтобы это дело максимально оптимизировать. React-virtualized прекрасно подойдет