Хабрахабр

[Перевод] Гибкие таблицы на CSS Grid


Просмотр списка лидов («холодных» контактов)

Одна из интересных функций Teamwork CRM — просмотр списка (list view). Поскольку мы уже запустились, я, наконец, могу рассказать о секретном проекте, над которым работал последние два года.

По сути, таблица на стероидах. Это мощный компонент, который встречается в приложении семь раз. Сосредоточусь на том, как мы реализовали подобную гибкость с помощью всего нескольких строк CSS (Grid). Я мог бы много рассказать, но не хочу вас утомлять. А именно, как мы выкладываем тяжёлые таблицы данных, как поддерживаем изменение размера столбцов и многое другое.

Если это не интересует, не стесняйтесь перейти сразу к технической реализации.
В первую очередь, наше решение позволяет клиентам быстро просматривать списки, например, лиды или контакты, и замечать важные детали. Во-первых, нужно объяснить контекст, начиная с цели и задачи дизайна этих таблиц. Это не похоже на электронную таблицу Excel — мы лучше справляемся с выкладкой данных, если их очень много.

Мы начинаем с самого узкого варианта и настраиваем макет на основе контента, дизайна и вариантов использования (у нас нет брекпоинтов, ориентированных на устройство). Абсолютно всё работает в адаптивном гибком дизайне.

На минимальной ширине экрана столбцы штабелируются вертикально, занимая всю ширину экрана.

Как выглядит список на узком экране

Вы можете выбрать из нескольких существующих шаблонов. Гибкие таблицы сделать непросто. Подумайте, какую информацию ищут пользователи, и выбирайте с умом.

Затем в макете не происходит никаких серьёзных изменений, но мы по-прежнему хотим отображать колонки как можно удобнее для клиента. Как только у нас достаточно пикселей на экране, мы переключаемся на более типичный макет, так что колонки… ну, становятся колонками.

Во-первых, таблица должна как минимум заполнить ширину экрана. Предположим, у нас много столбцов (позже рассмотрим, как пользователь может их настроить). Например, короткий/длинный текст, дата, номер, URL и т. д. Во-вторых, ширина столбцов должна определяться их содержимым и типом значений. Колонки с датой должны занимать меньше места, чем колонки длинного текста.

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

Извините за дёрганую гифку, позже я приведу несколько интерактивных примеров
Как макет зависит от ширины окна.

Затем улучшаем его с помощью CSS Grid. Начнём с того, что мы по максимуму используем для таблицы обычный CSS старой школы. Потом я покажу, как средствами Grid пользователи изменяют размер столбцов, что было гораздо неудобнее с обычным CSS.

Я не эксперт по CSS Grid, но он мне нравится. Это чрезвычайно мощный и простой инструмент, который реализует сложные макеты с минимальным количеством кода. Здесь я пропущу введение в технологию. Можете прочесть «Новые макеты CSS» Рейчел Эндрю или «Полное руководство по Grid». Когда закончите размышления о том, где же был этот инструмент всю вашу жизнь, возвращайтесь ко мне.

Это ничего не сломает: если браузер не поддерживает Grid, то применит display:table. Первым делом применяем display:grid к <table>, чтобы превратить таблицу в сетку. Нам не нужно думать о <thead>, <tbody> или даже <tr>. Дочерние элементы <thead> и <tbody> становятся элементами сетки. сетки внутри сеток), но это не идеально. Мы хотим выложить на этой сетке наши <th> и <td>, применив display:grid к каждому из них (т. е. Каждая сетка <tr> будет независима от других, и это не хорошо (позже вы увидите, что такая же проблема с Flexbox).

Это в основном удаляет их из макета сетки, выдвигая вперёд дочерние элементы (<th> и <td>). Обходной путь — использовать display:contents на <thead>, <tbody> и <tr>.

Да, всего одна строка CSS. Затем мы используем волшебное правило grid-template-columns для управления элементами сетки. Например, если у нас колонка даты и колонка URL, получится примерно так:

grid-template-columns: minmax(150px, 1.33fr) minmax(150px, 2.33fr);

Используем одинаковый минимальный размер для всех колонок, но максимальное значение (fr) определяется типом данных столбца. Я пробовал auto и max-content, но мы придумали лучший вариант. Вот упрощенный пример: интерактивная таблица с кодом. Попробуйте изменить размер окна.
Кроме того, в наших таблицах можно поменять местами, изменить ширину и скрыть столбцы. Последнее важно, потому что поддерживается очень много колонок с различными типами данных: это свойства самого элемента (например, лидов), свойства связанных элементов (например, компании, связанной с лидом) и кастомные поля.

Например, пользователь может создать для контактов настраиваемое поле (дата) под названием «Дата рождения», которое будет отслеживаться в системе для каждого контакта.

Сначала объясню, как происходит изменение ширины. Поскольку при создании настраиваемого поля выбирается тип «Дата», система будет обрабатывать это поле с учётом такого типа.

  1. Когда пользователь проводит курсором над заголовком столбца, справа отображается маркер изменения размера. Мы прослушиваем событие mousedown на ползунке изменения размера.
  2. Когда пользователь нажимает на ползунок, мы привязываем ещё несколько методов для прослушивания событий mousemove и mousedownwindow). На этом этапе также добавляем некоторые классы для оформления.
  3. Когда пользователь перемещает мышь, мы вычисляем новую ширину столбца с учётом положения курсора, положения прокрутки таблицы и установленного минимума. Затем повторно устанавливаем правило grid-template-columns для <table> (через атрибут style), на этот раз заменив максимальное значение (fr) пиксельным. Например, grid-template-columns: minmax(150px, 1.33fr) 296px;. Это делается с помощью requestAnimationFrame, чтобы обеспечить как можно более плавную анимацию.
  4. Когда поступает mouseup, мы отменяем прослушиватели событий и удаляем классы.

Попробуйте на этом упрощённом примере.

Замечательно, что достаточно обновить только один элемент в DOM, а не каждую ячейку.

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

Вы могли заметить, что кое о чём умолчал. На самом деле изменяется ширина не одного, а всех столбцов. Может, вы даже этого не заметили, потому что именно так оно и должно работать.

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

Пользователь чувствует некую потерю контроля, когда его действия вызывают непредсказуемые побочные эффекты. Но после небольшого пользовательского тестирования выяснилось, что для людей это неожиданное поведение.

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

Если изменить размер экрана, колонки тоже изменятся по тому же принципу. Поэтому, если вы открываете приложение в первый раз, столбцы выкладываются оптимальным образом. Как только вы касаетесь ползунка для изменения размера любого столбца, ширина всех видимых колонок фиксируется.

Опять прошу прощения, что гифка немного дёргается
До, во время и после изменения ширины столбца.

Каждый раз, когда размер столбца изменяется или фиксируется, мы создаём независимую запись localStorage, сопоставляющую идентификатор столбца с пиксельным значением, чтобы сохранить пользовательские настройки.

Может, для простоты. Не могу точно вспомнить, почему мы решили установить фиксированное значение в пикселях, а не адаптивный вариант. Или потому что в отсутствие поддержки Grid и display:contents происходит откат на более архаичный подхода к настройке ширины столбцов.

Мы не можем предполагать, что для него самое главное — сделать все столбцы меньше, чтобы все они остались на экране. Вероятно, адаптивный вариант в любом случае не будет соответствовать намерениям пользователя. Если у нас адаптивный блок, а затем он сужается в окне меньшего размера, то мы игнорируем выбор человека. Если человек изменил ширину колонки, он хочет увидеть определённое количество содержимого в этом столбце. Вряд ли пользователь думает: «Хм, я хочу, чтобы этот столбец занимал 20% окна, даже если я его изменю». Ему придётся снова изменить ширину столбца, чтобы увидеть тот же контент. Впрочем, я слишком углубляюсь в пограничную ситуацию: на самом деле, пользователи редко изменяют размер окон.


Интерфейс для настройки отображаемых столбцов

Если ни один из выбранных столбцов ранее не изменялся, то они отобразятся с использованием значений по умолчанию grid-template-column в зависимости от типа данных. Представьте, что пользователь изменил набор столбцов через этот интерфейс. 33fr). Например, minmax(150px, 3.

Если ширина какой-то колонки зафиксирована в localStorage, мы фиксируем ширину всех выбранных столбцов и тоже сохраняем эти значения в localStorage.

Для пользователей единственный способ вернуться к адаптивному дизайну — сбросить колонки. Со временем всё больше и больше столбцов сохраняют фиксированную ширину.

Мы также храним в localStorage массив идентификаторов столбцов, отдельно от записей о ширине.

С библиотекой JavaScript решение будет тяжёлым, дёрганым, не обеспечит интерактивность и может даже вообще не поддерживать . Мне также не хотелось писать что-то подобное. Я подумал: «Должен быть способ получше».
Каждая строка будет оцениваться/выводиться независимо друг от друга. Столбец может быть не выровнен относительно столбца выше из-за разного объёма содержимого.

Но не хотел этого делать. Я мог бы переключиться на <div>'ы для столбцов с вертикальной группировкой ячеек внутри. Кроме того, мы легко могли столкнуться с другими проблемами: например, с несовпадением ячеек по высоте между столбцами. Я хотел использовать <table>.

Действительно, <colgroup> — удобный старый элемент. После определения столбцов с помощью стили, применённые к одному, будут эффективно применяться ко всем ячейкам в этом столбце.

Мы попробовали, но очень быстро от него отказались. Но это оказалось слишком ограниченное решение. Почти уверен, что невозможно было достичь желаемого уровня адаптивности и оно не сработало с Flexbox и Grid. Настолько быстро, что я уже не могу точно вспомнить, в чём были проблемы.

Я мог бы применить на <table> правило table-layout: fixed и установить ширину столбцов в процентах. Но глядя на примеры и поиграв с этим правилом, создалось впечатление, что оно работает только на таблицах шириной 100%. Кроме того, изменение размера одного столбца приводит к изменению размера других столбцов для выхода на общие 100% ширины.
Да, таблицы из коробки способны на много умных вещей, но не могут эффективно поддерживать всё, что я хотел реализовать. Не согласны? Хорошо, волшебник, научи меня.
Значение display: contents позволило сохранить разметку таблицы. Используйте его только тогда, когда это действительно нужно. В некоторых браузерах есть или, по крайней мере, были проблемы с доступностью и скрин-ридерами.

Мы обнаружили странный баг display: contents с нативным драг-н-дропом в Firefox.

В нашем приложении мы хотим лишь упростить разметку, но подсетки откроюет двери в дикие многомерные сеточные оргии. К счастью, скоро выйдет функция подсеток (subgrid), которая позволит дочерним элементам корректно внедряться в сетки. «Почему display: contents не является подсеткой CSS Grid Layout». См.

Кажется, была ещё проблема с переполнением текста при изменении размера столбцов, но я уже точно не помню.

Это прекрасное усовершенствование, и оно отлично деградирует в старых браузерах. Для сохранения заголовков таблиц при прокрутке вниз мы используем position: sticky. На самом деле я бы не рекомендовал position: sticky из-за трудностей с горизонтальной прокруткой. Тем не менее, для пользователей IE11 у нас есть резервный JavaScript.

Например, пользователи могут применять, сохранять и обмениваться кастомными фильтрами (например, показать лиды выше $500 с потенциальными клиентами в Европе). Я даже не упомянул некоторые функции наших представлений списка. В этих фильтрах можно запоминать набор столбцов, чтобы всегда отобразить определённые столбцы для конкретного рабочего процесса.

Вскоре мы собираемся реализовать массовое редактирование в представлении списка, а также экспорт кастомного представления в CSV.

В любом случае, спасибо за чтение.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть