CSS Grid понятно для всех
Grid представляет собой пересекающийся набор горизонтальных и вертикальных линий — один набор определяет столбцы, а другой строки. Элементы могут быть помещены в сетку, соответственно строкам и столбцам.
Поддержка браузерами
В 2020 году поддержка браузерами достигает 94 %
Grid контейнер
Мы создаем grid контейнер, объявляя display: grid или display: inline-grid на элементе. Как только мы это сделаем, все прямые дети этого элемента станут элементами сетки.
Header
Navbar
Article
Ads
grid-template-rows — это CSS свойство, которое определяет названия линий и путь размера функции grid rows.
CSS свойство grid-row определяет с какой строки в макете сетки будет начинаться элемент, сколько строк будет занимать элемент, или на какой строке завершится элемент в макете сетки. Является сокращенным свойством для свойств grid-row-start и grid-row-end.
Свойство CSS grid-gap является сокращенным свойством для grid-row-gap и grid-column-gap , определяющего желоба между строками и столбцами сетки.
Свойство grid-template-areas определяет шаблон сетки ссылаясь на имена областей, которые заданы с помощью свойства grid-area.
Повторение названия области приводит к тому, что содержимое охватывает эти ячейки. Точка означает пустую ячейку. Сам синтаксис предоставляет визуализацию структуры сетки.
С помощью свойства grid-area мы можем назначить каждой из этих областей свое собственное имя. Именование областей еще не создает никакого макета, однако теперь у нас есть именованные области, которые мы можем в нем использовать.
.header < grid-area: header; >.nav < grid-area: nav; >.article < grid-area: article; >.ads
Создаем шаблон сайта с CSS Grid:
Изменяем шаблон
Вы можете изменить шаблон просто перераспределив грид-области в grid-template-areas .
Таким образом, если мы сменим на это:
grid-template-areas: "nav header header" "nav article ads"; >
То в результате получим такой шаблон:
Гриды с медиа запросами
Одной из сильных сторон гридов является то, что вы можете создать уже совершенно другой шаблон за секунды.
Это делает CSS Grid идеальным для медиа запросов. Мы можем просто переназначить значения в ASCII-графике и обернуть результат в конечный медиа запрос.
@media all and (max-width: 575px) < .row < grid-template-areas: "header" "article" "ads" "nav"; grid-template-rows: 80px 1fr 70px 1fr ; grid-template-columns: 1fr; >>
В результате получим:
Таким образом, все дело состоит в переназначении значений в свойстве grid-template-areas .
Заключение
В данной статье мы рассмотрели всего лишь верхушку CSS Grid Layout айсберга. Иногда сложно поверить своим глазам какие штуки удается сделать при помощи CSS Grid. Это разрыв всех шаблонов. И мне это нравится.
Я вам советую обратить внимание на данную спецификацию и потратить немного своего времени на ее изучение. Поверьте, в будущем вам это точно пригодится и не важно, пишете вы на React, Angular, Vue (вставьте свое). Grid’ы пришли надолго.
Элемент управления Grid
Табличные элементы управления (обычно в их названии присутствуют слова Table или Grid) широко используются при разработке GUI. Так получилось, что на работе мы используем С++ и MFC для разработки пользовательского интерфейса. В начале мы использовали CGridCtrl — общедоступную и довольно известную реализацию грида. Но с некоторого времени он перестал нас устраивать и появилась на свет собственная разработка. Идеями, лежащими в основе нашей реализации, я хочу с вами здесь поделиться. Есть задумка сделать open source проект (скорее всего под Qt). Поэтому данную заметку можно рассматривать как «Proof Of Concept». Конструктивная критика и замечания приветствуются.
Причины, по которым меня не устраивают существующие реализации я опущу (это тема для отдельной заметки).
Проекты у нас инженерно-научные, с богатой графикой, и списки и таблицы используются повсеместно. Поэтому новый грид должен был обеспечивать гибкую кастомизацию, хорошее быстродействие и минимальное потребление памяти при показе больших объемов информации. При разработке я старался придерживаться следующим правилом: реализуй функциональность максимально обобщенно и абстрактно, но не во вред удобству использования и оптимальности работы. Конечно, это правило противоречиво, но насколько мне удалось соблюсти баланс — судить вам.
Чтобы с чего-то начать, давайте попробуем дать определение элементу управления grid. Для сохранения общности можно сказать, что grid — это визуальный элемент, который разбивает пространство на строки и столбцы. В результате получается сетка ячеек (место пересечения строк и столбцов), внутри которых отображается некоторая информация. Таким образом у грида можно различить два компонента: структуру и данные. Структура грида определяет как мы будем разбивать пространство на строки и столбцы, а данные описывают, собственно, то, что мы хотим видеть в получившихся ячейках.
- Главное свойство Count — количество линий, из которых состоит Lines
- Каждая линия может менять свой размер (строка высоту, а столбец — ширину)
- Линии можно переупорядочивать (строки сортировать, столбцам менять порядок)
- Линии можно скрывать (делать невидимыми для пользователя)
class Lines < public: Lines(UINT_t count = 0); UINT_t GetCount() const < return m_count; >void SetCount(UINT_t count); UINT_t GetLineSize(UINT_t line) const; void SetLineSize(UINT_t line, UINT_t size); bool IsLineVisible(UINT_t line) const; void SetLineVisible(UINT_t line, bool visible); template void Sort(const Pred& pred); const vector& GetPermutation() const; void SetPermutation(const vector& permutation); UINT_t GetAbsoluteLineID(UINT_t visibleLine) const; UINT_t GetVisibleLineID(UINT_t absoluteLine) const; Event_t changed; private: UINT_t m_count; vector m_linesSize; vector m_linesVisible; >;
Комментарии и некоторые служебные функции и поля опущены для наглядности.
Вы можете заметить, что в классе есть функции GetAbsoluteLineID и GetVisibleLineID . Так как мы позволяем перемешивать и скрывать линии, то абсолютный и видимый индекс линии различаются. Надеюсь картинка наглядно показывает эту ситуацию.
Также нужно сделать пояснение по поводу строки
Event_t changed;
Здесь определён сигнал (так он называется в Qt или boost). С появлением С++11 и std::function, можно легко написать простую реализацию signals/slots, чтобы не зависеть от внешних библиотек. В данном случае мы определили эвент в классе Lines, и к нему можно подключать любую функцию или функтор. Например грид подключается к этому эвенту и получает оповещение, когда экземпляр Lines меняется.
Таким образом структура грида у нас представлена двумя экземплярами Lines:
private: Lines m_rows; Lines m_columns;
Переходим к данным. Каким образом давать гриду информацию о том, какие данные он будет отображать и как их отображать? Здесь уже всё изобретено до нас — я воспользовался триадой MVC (Model-View-Controller). Начнем с элемента View. Так же как класс Lines определяет не одну линию, а целый набор, определим класс View как нечто, что отображает какие-то однородные данные в некотором подмножестве ячеек грида. Например, у нас в первом столбце будет отображаться текст. Это означает, что мы должны создать объект, который умеет отображать текстовые данные и который умеет говорить, что отображаться эти данные должны в первой колонке. Так как данные у нас могут отображаться разные и в разных местах, то лучше реализовать эти функции в разных классах. Назовем класс, который умеет отображать данные, собственно View, а класс, который умеет говорить где данные отображать Range (набор ячеек). Передавая в грид два экземпляра этих классов, мы как раз указываем что и где отображать.
Давайте подробнее остановимся на классе Range. Это удивительно маленький и мощный класс. Его главная задача — быстро отвечать на вопрос, входит ли определенная ячейка в него или нет. По сути это интерфейс с одной функцией:
class Range < public: virtual bool HasCell(CellID cell) const = 0; >;
Таким образом можно определять любой набор ячеек. Самыми полезными конечно же будут следующие два:
class RangeAll < public: bool HasCell(CellID cell) const override < return true; >>; class RangeColumn < public: RangeColumn(UINT_t column): m_column(column) <>bool HasCell(CellID cell) const override < return cell.column == m_column; >private: UINT_t m_column; >;
Первый класс определяет набор из всех ячеек, а второй — набор из одного конкретного столбца.
- Сколько надо места, что бы отобразить данные (например чтобы колонкам установить ширину, достаточную для отображения текста — режим Fit)
- Дай текстовое представление данных (чтобы скопировать в буфер обмена как текст или отобразить в tooltip)
class View < public: virtual void Draw(DrawContext& dc, Rect rect, CellID cell) const = 0; virtual Size GetSize(DrawContext& dc, CellID cell) const = 0; virtual bool GetText(CellID cell, INTENT intent, String& text) const = 0; >;
А что, если мы хотим отрисовать разные типы данных в одной и той же ячейке? Например нарисовать иконку и рядом текст или нарисовать чекбокс и рядом текст. Не хотелось бы для этих комбинаций реализовывать отдельный тип View. Давайте разрешим в одной ячейке показывать несколько View, только нужен класс, который говорит как разместить конкретный View в ячейке.
class Layout < public: virtual void LayoutView(DrawContext& dc, const View* view, Rect& cellRect, Rect& viewRect) const = 0; >;
Для наглядности рассмотрим пример в котором в первом столбце отображаются чекбоксы и текст. Во втором столбце представлены радио-кнопки, квадратики с цветом и текстовое представление цвета. И еще в одной ячейке есть звёздочка.
Например для чекбокса мы будем использовать LayoutLeft, который спросит у View его размер и «откусит» прямоугольник нужного размера от прямоугольника ячейки. А для текста мы будем использовать LayoutAll, к которому в параметре cellRect перейдет уже усеченный прямоугольник ячейки. LayoutAll не будет спрашивать размер у своего View, а просто «заберет» все доступное пространство ячейки. Можно напридумывать много разных полезных Layouts, которые будут комбинироваться с любыми View.
Возвратимся к классу Grid, для которого мы хотели задавать данные. Получается, что хранить мы можем тройки , которые определяют в каких ячейках, каким образом отображать данные, плюс как эти данные должны быть расположены внутри ячейки. Итак класс Grid у нас выглядит примерно так:
class Grid < private: Lines m_rows; Lines m_columns; vector> m_data; >;
Вот как выглядит m_data для нашего примера
В сущности, этого достаточно для отрисовки грида. Но информация организована не оптимальным образом — просто список записей, определяющих отображение данных.
Давайте подумаем, как с помощью нашего класса Grid можно отрисовать какую-то ячейку.
-
Нужно отфильтровать m_data и оставить только те тройки, для которых наша ячейка попадает в Range
for (auto d: grid.m_data) if (d.range->HasCell(cell)) cell_data.push_back(d);
Rect cellRect = CalculateCellRect(grid.m_rows, grid.m_columns, cell);
vector view_rects(cell_data.size()); auto view_rect_it = view_rects.begin(); for (auto d: cell_data) d.layout->LayoutView(grid.GetDC(), d.view, cellRect, *view_rect_it++);
auto view_rect_it = view_rects.begin(); for (auto d: cell_data) d.view->Draw(grid.GetDC(), *view_rect_it++, cell);
class CellCache < public: CellCache(Grid grid, CellID cell); void Draw(DrawContext& dc); private: CellID m_cell; Rect m_cellRect; vector> m_cache; >;
Этот класс в конструкторе выполняет первые три пункта и сохраняет результат в m_cache. При этом функция Draw получилась достаточно легковесной. За эту легковесность пришлось заплатить в виде m_cache. Поэтому создавать экземпляры такого класса на каждую ячейку будет накладно (мы ведь договорились не иметь данных, зависящих от общего количества ячеек). Но нам и не надо иметь экземпляры CellCache для всех ячеек, достаточно только для видимых. Как правило в гриде видна небольшая часть всех ячеек и их количество не зависит от общего числа ячеек.
Таким образом у нас появился еще один класс, который управляет видимой областью грида, хранит CellCache для каждой видимой ячейки и умеет быстро рисовать их.
class GridCache < public: GridCache(Grid grid); void SetVisibleRect(Rect visibleRect); void Draw(DrawContext& dc); private: Grid m_grid; Rect m_visibleRect; vectorm_cells; >;
Когда пользователь меняет размер грида или скроллирует содержимое, мы просто выставляем новый visibleRect в этом объекте. При этом переформируется m_cells, так чтобы содержать только видимые ячейки. Функциональности GridCache достаточно, что бы реализовать read-only грид.
class GridWindow < public: Grid GetGrid() < return m_gridCache.GetGrid(); >void OnPaint() < m_gridCache.Draw(GetDrawContext()); >void OnScroll() < m_gridCache.SetVisibleRect(GetVisibleRect()); >void OnSize() < m_gridCache.SetVisibleRect(GetVisibleRect()); >private: GridCache m_gridCache; >;
Разделение классов Grid и GridCache очень полезно. Оно позволяет, например, создавать несколько GridCache для одного экземпляра Grid. Это может использоваться для реализации постраничной печати содержимого грида или экспорта грида в файл в виде изображения. При этом объект GridWindow никаким образом не модифицируется — просто в стороне создается GridCache, ссылающийся на тот же экземпляр Grid, в цикле новому GridCache выставляется visibleRect для текущей страницы и распечатывается.
Как же добавить интерактивности? Здесь на первый план выходит Controller. В отличие от остальных классов, этот класс определяет интерфейс со многими функциями. Но лишь потому, что самих мышиных событий достаточно много.
class Controller < public: virtual bool OnLBttnDown(CellID cell, Point p) = 0; virtual bool OnLBttnUp(CellID cell, Point p) = 0; . >;
Так же как и для отрисовки, для работы с мышью нам нужны только видимые ячейки. Добавим в класс GridCache функции обработки мыши. По положению курсора мыши определим какая ячейка (CacheCell) находится под ней. Далее в ячейке для всех View, в чей прямоугольник попала мышь, забираем Controller и вызываем у него соответствующий метод. Если метод возвратил true — прекращаем обход Views. Данная схема работает достаточно быстро. При этом нам пришлось в класс View добавить ссылку на Controller.
Осталось разобраться с классом Model. Он нужен как шаблон адаптер. Его основная цель — предоставить данные для View в «удобном» виде. Давайте рассмотрим пример. У нас есть ViewText который умеет рисовать текст. Что бы его нарисовать в конкретной ячейке, этот текст надо для ячейки запросить у объекта ModelText, который, в свою очередь, лишь интерфейс, а его конкретная реализация знает откуда текст взять. Вот примерная реализация класса ViewText:
class ViewText: public View < public: ViewText(ModelText model): m_model(model) <>void Draw(DrawContext& dc, Rect rect, CellID cell) const override < const String& text = model->GetText(cell); dc.DrawText(text, rect); > private: ModelText m_model; >;
Таким образом несложно угадать какой интерфейс должен быть у ModelText:
class ModelText: public Model < public: virtual const String& GetText(CellID cell) const = 0; virtual void SetText(CellID cell, const String& text) = 0; >;
Обратите внимание, мы добавили сеттер для того, что бы им мог воспользоваться контроллер. На практике наиболее часто используется реализация ModelTextCallback
class ModelTextCallback: public ModelText < public: functiongetCallback; function setCallback; const String& GetText(CellID cell) const override < return getCallback(cell); >void SetText(CellID cell, const String& text) override < if (setCallback) setCallback(cell, text); >>;
Эта модель позволяет при инициализации грида назначить лямбда функции доступа к настоящим данным.
Ну а что же общего у моделей для разных данных: ModelText, ModelInt, ModelBool . В общем-то ничего, единственное, что про них всех можно сказать, что они должны информировать все заинтересованные объекте о том, что данные изменились. Таким образом базовый класс Model у нас примет следующий вид:
class Model < public: virtual ~Model() <>Event_t changed; >;
В итоге наш грид разбился на множество небольших классов, каждый из которых выполняет четко определенную небольшую задачу. С одной стороны может показаться, что для реализации грида представлено слишком много классов. Но, с другой стороны, классы получились маленькими и простыми, с четкими взаимосвязями, что упрощает понимание кода и уменьшает его сложность. При этом всевозможные комбинации наследников классов Range, Layout, View, Controller и Model дают очень большую вариативность. Использование лямбда функций для ModelCallback позволяют легко и быстро связывать грид с данными.
В следующей заметке я опишу как реализовать стандартную функциональность грида: selection, sorting, column/row resize, printing, как добавить заголовок (фиксированные верхние строки и левые столбцы).
Раскрою небольшой секрет — все что описано в данной статье уже достаточно для реализации вышеперечисленного. Если какую-то функциональность я пропустил, пожалуйста, пишите в комментариях и я опишу их реализацию в следующей статье.
Метод grid
Grid является одним из трех менеджеров геометрии в Tkinter (другими являются уже рассмотренный ранее Pack, а также Place). У всех виджетов есть соответствующий данному менеджеру метод grid . «Grid» с английского переводится как «сетка», однако по смыслу правильнее говорить о таблице.
Табличный способ размещения предпочтителен из-за его гибкости и удобства, когда дело доходит до разработки относительно сложных интерфейсов. Grid позволяет избежать использования множества фреймов, что неизбежно в случае упаковщика Pack.
При размещении виджетов методом grid родительский контейнер (обычно это окно) условно разделяется на ячейки подобно таблице. Адрес каждой ячейки состоит из номера строки и номера столбца. Нумерация начинается с нуля. Ячейки можно объединять как по вертикали, так и по горизонтали.
На рисунке пунктир обозначает объединение ячеек. Общая ячейка в таком случае обозначается адресом первой.
Никаких предварительных команд по разбиению родительского виджета на ячейки не выполняется. Tkinter делает это сам, исходя из указанных позиций виджетов.
Размещение виджета в той или иной ячейке задается через аргументы row и column , которым присваиваются соответственно номера строки и столбца. Чтобы объединить ячейки по горизонтали, используется атрибут columnspan , которому присваивается количество объединяемых ячеек. Опция rowspan объединяет ячейки по вертикали.
Пусть надо запрограммировать такой GUI:
Представим данный интерфейс в виде таблицы и пронумеруем ячейки, в которых будут располагаться виджеты:
Теперь пишем код:
from tkinter import * root = Tk() Label(text="Имя:").grid(row=0, column=0) Entry(width=30).grid(row=0, column=1, columnspan=3) Label(text="Столбцов:").grid(row=1, column=0) Spinbox(width=7, from_=1, to=50).grid(row=1, column=1) Label(text="Строк:").grid(row=1, column=2) Spinbox(width=7, from_=1, to=100).grid(row=1, column=3) Button(text="Справка").grid(row=2, column=0) Button(text="Вставить").grid(row=2, column=2) Button(text="Отменить").grid(row=2, column=3) root.mainloop()
Примечание. В примере используются виджеты класса Spinbox , которые не рассматривались в курсе. Spinbox похож на Entry , но для него задается список принимаемых значений, и имеется подобие скроллера.
Выполнив приведенный выше программный код, получим:
Похоже, но не совсем то, что хотелось. Теперь на помощь должны прийти другие свойства метода grid . У него, также как у pack , имеются атрибуты для задания внешних и внутренних отступов ( padx , pady , ipadx , ipady ).
Кроме этого есть атрибут sticky (липкий), который принимает значения направлений сторон света (N, S, W, E, NW, NE, SW, SE). Если, например, указать NW, то виджет прибьет к верхнему левому углу ячейки. Виджеты можно растягивать на весь объем ячейки ( sticky=N+S+W+E ) или только по одной из осей ( N+S или W+E ). Эффект от «липучки» заметен, только если виджет меньше ячейки.
from tkinter import * root = Tk() Label(text="Имя:")\ .grid(row=0, column=0, sticky=W, pady=10, padx=10) Entry()\ .grid(row=0, column=1, columnspan=3, sticky=W+E, padx=10) Label(text="Столбцов:")\ .grid(row=1, column=0, sticky=W, padx=10, pady=10) Spinbox(width=7, from_=1, to=50)\ .grid(row=1, column=1, padx=10) Label(text="Строк:")\ .grid(row=1, column=2, sticky=E) Spinbox(width=7, from_=1, to=100)\ .grid(row=1, column=3, sticky=E, padx=10) Button(text="Справка").grid(row=2, column=0, pady=10, padx=10) Button(text="Вставить").grid(row=2, column=2) Button(text="Отменить").grid(row=2, column=3, padx=10) root.mainloop()
С помощью методов grid_remove и grid_forget можно сделать виджет невидимым. Отличие между этими методами лишь в том, что grid_remove запоминает прежнее положение виджета. Поэтому для его отображения в прежней ячейки достаточно применить grid без аргументов. После grid_forget потребуется заново конфигурировать положение виджета.
from tkinter import * def rem(): global l1_flag if l1_flag == 1: l1.grid_remove() l1_flag = 0 else: l1.grid() l1_flag = 1 def forg(): global l2_flag if l2_flag == 1: l2.grid_forget() l2_flag = 0 else: l2.grid(row=1) l2_flag = 1 root = Tk() l1_flag = 1 l2_flag = 1 l1 = Label(width=5, height=3, bg='blue') l2 = Label(width=5, height=3, bg='green') b1 = Button(bg='lightblue', command=rem) b2 = Button(bg='lightgreen', command=forg) l1.grid(row=0) l2.grid(row=1) b1.grid(row=2) b2.grid(row=3) root.mainloop()
В данной программе голубая метка после удаления будет появляться в том же месте, где была до этого, несмотря на то, что в функции rem к ней применяется метод grid без настроек. В отличие от нее зеленая метка, если ей не указать место размещения после удаления, появлялась бы под кнопками.
Скрытие виджетов бывает необходимо в тех случаях, когда, например, от выбора пользователя в одной части интерфейса зависит, какие виджеты появятся в другой.
Практическая работа
Перепрограммируйте второе окно из практической работы предыдущего урока, используя метод grid .
Курс с примерами решений практических работ: pdf-версия
X Скрыть Наверх
Tkinter. Программирование GUI на Python
Как создать сетки на CSS Grid Layout
CSS Grid Layout — это способ разметки, который позволяет создавать на странице простые и сложные двумерные сетки. Вы можете добавить любое количество столбцов или строк и указать, сколько ячеек должен занимать каждый элемент.
Например, с помощью гридов легко создать такой раздел:
Если пока не знаете, в чём разница, прочитайте, когда использовать флексы, а когда гриды.
Как создать сетку на гридах
Чтобы добавить сетку, нужно в стилях родительского элемента написать display: grid . Далее указывается количество столбцов и строк в сетке, задаются их размеры.
Описать количество и размеры столбцов — grid-template-columns .
Описать количество и размеры строк — grid-template-rows .
Например, так создаётся сетка из двух столбцов шириной 200px и двух строк высотой 150px :
.cats
Как задать размеры столбцов и строк
В процентах. Ширина колонки и высота ряда считаются относительно родительского контейнера. Например, grid-template-columns: 50% 50% создаст две одинаковые колонки шириной в половину родительского элемента.
В пикселях. Устанавливает точную ширину столбца или высоту строки — мы так делали в примере с котами: grid-template-columns: 200px 200px .
С помощью ключевого слова auto. Размеры вычисляются автоматически, в зависимости от свободного пространства.
C помощью ключевого слова fr (fraction). Всё пространство в сетке делится на равные доли fr . Например, колонка grid-template-columns: 1fr растянется на всю область родительского контейнера. А если написать grid-template-columns: 1fr 2fr , вы получите две колонки, при этом вторая будет в два раза шире.
Использовать разные значения. Можно смешивать значения, чтобы добиться большей точности или гибкости:
В этом примере мы создаём четыре колонки. У первой фиксированная ширина, последняя занимает 25% пространства, а вторая и третья делят оставшееся пространство на две равные доли.
Как разместить элементы в гриде
По умолчанию все элементы располагаются в гриде слева направо сверху вниз и занимают одну ячейку сетки. Нумерация считается по линиям, как на картинке:
Чтобы расположить элемент в сетке, нужно задать ему координаты. Например, первый кот начинается на линии 1 столбца и линии 1 ряда, а заканчивается на линии 2 столбца и линии 2 ряда. Кот в очках начинается на 3 линии столбца и 1 линии строки, а заканчивается на 4 линии столбца и 2 линии строки.
Также можно использовать обратную нумерацию. Это полезно, например, когда нужно работать с элементами в крайнем правом столбце или нижнем ряду.
Здесь кот в очках находится между линиями столбцов -1 и -2:
Стили для расположения элементов в сетке: grid-column и grid-row
grid-column-start и grid-column-end указывают, в каком столбце начинается элемент и в каком заканчивается. Например:
.keks < grid-column-start: 1; grid-column-end: 3; /*Элемент растянется на две колонки с первой по третью линию*/ >
grid-row-srart и grid-row-end указывают, какие строки занимает элемент. Здесь всё, как со столбцами:
.keks < grid-row-srart: 1; grid-row-end: 3; /*Элемент растянется на две строки, с первой по третью линию*/ >
Для этих свойств есть сокращённые записи: grid-column и grid-row . В них можно сразу прописать, на какой линии начинается и заканчивается элемент — это делается через черту, вот так: grid-row: 1/3.
.keks < /*Элемент займёт две строки.*/ grid-row: 1/3 >
Свойство grid-area и repeat
Свойствоgrid-area
Писать каждый раз начальные и конечные координаты для столбцов и рядов — долго и не всегда удобно. Но можно сократить запись с помощью свойства grid-area , оно объединяет и заменяет grid-column и grid-row .
Свойство grid-area принимает четыре значения, которые разделяются косой чертой: grid-row-start , grid-column-start , grid-row-end и grid-column-end :
Эта же запись в полном варианте:
Ключевое словоrepeat
Второй способ сократить код — использовать ключевое слово repeat . Оно помогает, когда в grid-template-columns или grid-template-columns нужно создать множество одинаковых колонок или рядов:
С ключевым словом repeat код выглядит так:
В скобках после repeat сначала указывается количество строк или столбцов, а затем через запятую пишется их размер.
Границы и отступы
Границы между элементами задаются с помощью свойства gap . Например, gap: 10px добавит отступы между элементами по бокам и сбоку:
Чтобы задать только боковые отступы, используйте grid-column-gap , а для отступов между рядами — grid-row-gap .
Как изменить порядок элементов
Есть два способа.
Первый — указать расположение элемента с помощью grid-column , grid-row или grid-area . К примеру, у нас есть такой список:
Кот Кекс Кот Барсик Кошка Василиса Кот Борис
.grid
На странице он выглядит так:
Поменяем порядок. Сделаем так, чтобы Кекс стал третьим и перешёл на вторую строку. Разметку не меняем, только стили:
.keks < grid-row: 2/3; >
С помощью стилей вы можете указать координаты для каждого элемента и даже наложить один элемент на другой, если это нужно.
Второй способ — использовать свойство order . По умолчанию элементы грида имеют order , равный 0 , но это значение можно менять. Например, вот что произойдёт, если мы добавим «Кексу» order: 1 :
.keks
Элемент переместится в конец:
Где отточить навык работы с гридами
«Старт в программировании» — курс для новичков. Он научит вас создавать страницы, в том числе строить сетки на флексах и гридах. Курс основан на тренажёрах и мастер-классах.
Grid Garden — интерактивная игра, которая поможет понять основы CSS Grid Layout. Вам предстоит поливать грядки с морковью и уничтожать сорняки с помощью гридов.
Раздело CSS Grid Layout — страница на официальной документации MDN. Поможет расширить знания о гридах и не только.
Grid by Example — на этом сайте вы найдёте множество примеров использования CSS Grid. Все статьи здесь на английском языке, но их можно перевести через переводчик, встроенный в браузер.
Материалы по теме
- Как создавать адаптивные сетки
- Флексы для начинающих
- Как сверстать макет. Пошаговый план
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
Читать дальше
Новое в 2023 — text-wrap: balance
В 2023 в CSS появилось любопытное свойство text-wrap со значением balance . Оно «уравновешивает» текстовые элементы, чтобы они приятнее выравнивались внутри блока.
Ограничение — текст не длиннее 6 строк, иначе браузеру придётся непросто, и лучше не применять это свойство к body .
Вот пример заголовка c text-wrap: balance и без него.
На момент написания заметки свойство поддерживается во всех больших браузерах, кроме Safari, а на мобильных — только в Chrome, но то ли ещё будет.
- 13 ноября 2023
Знакомство с CSS
После того как мы разобрались с базовой структурой веб-страницы с помощью HTML, пришло время привнести в неё стиль и красоту. В этом нам поможет CSS, что означает Cascading Style Sheets, или «каскадные таблицы стилей».
CSS используется для оформления HTML-страниц. Если HTML — это скелет сайта, то CSS — его одежда. С помощью CSS мы можем задавать цвета, шрифты, отступы, добавлять анимации и многое другое.
- 1 ноября 2023
Увеличение ссылки при наведении
Задача: плавно увеличить ссылку при наведении.
Решение:
a < display: inline-block; transition: transform 0.3s ease; >a:hover
Первые два свойства просто немного меняют вид ссылки. Свойство color: maroon; меняет цвет текста в тегах на темно-красный, а свойство text-decoration : none; убирает подчеркивание.
Но наша задача — плавно увеличить размер ссылки, а не просто её перекрасить. Поэтому используем свойство transform: scale(1.2) , которое срабатывает при наведении курсора и увеличивает размер ссылки в 1.2 раза по сравнению с её начальным размером.
- 13 октября 2023
WOFF больше не нужен
Я купил и скачал шрифты для недавнего проекта, распаковал папку, где были только WOFF2-файлы, и сначала не поверил, что такое бывает.
Потом мне стало интересно: они что, забыли WOFF? А он вообще ещё нужен? Ну, всё-таки, веб — это место, где постоянно всё меняется и улучшается, поэтому я пошёл и спросил людей в Mastodon. Ответ был единодушным: нужен только WOFF2!
Я хорошо помню пост от Зака в конце 2016, после которого я отказался от исчерпывающего синтаксиса @font-face , включавшего, вдобавок, TTF, EOT и SVG-шрифты, и перешёл только на WOFF2 и WOFF.
Похоже, с тех пор мир веб-шрифтов изменился ещё разок, и вот актуальная версия @font-face :
@font-face
Остался всего один формат. Просто, скажите?
Как писал Зак, «так как в вебе, когда шрифт не найден, всё равно подгружаются системные шрифты, мы можем идти в ногу со временем». Итак, какие браузеры отправятся в тёмные века системных шрифтов с этим синтаксисом?
- IE 11, 10, 9, 8, 7, …
- Chrome 4–35
- Edge 12 и 13
- Safari 3–9.1
- Firefox 2–38
- Opera 22 и ниже
- Android 4.4.4 KitKat и ниже (а это
- Safari на iOS 3.2–9.3
Caniuse.com показывает, что почти у 95% пользователей есть браузер с поддержкой WOFF2. А в относительной статистике (Date Relative — прим. перев.) заметно, что массовый переход на WOFF2 случился в 2015 и 2016. К концу 2016 во всех последних версиях больших браузеров появилась поддержка WOFF2.3
А спустя 7 лет поддержка расширилась настолько, что можно уже не добавлять в проект WOFF-файлы — ну, кроме случая, когда вы точно знаете, что много ваших пользователей используют старые устройства и браузеры.
С другой стороны, нет смысла и удалять WOFF из старых проектов. Если вы подключали WOFF2 раньше WOFF внутри @font-face — и порядок здесь важен — то браузер просто скачает и подключит WOFF2-версию.
И если однажды вы, как и я, обнаружите себя перед папкой, полной файлов WOFF2, знайте, что WOFF — уже всё.
- 23 сентября 2023
Трясём пароль с помощью CSS
Знаете момент, когда всё на сайте уже сделано, и хочется добавить какую-нибудь маленькую незаметную фишку? Мы тоже знаем, поэтому давайте просто потрясём поле пароля, когда пользователь ввёл неверный пароль. Как на Маке.
Вот что получится в итоге:
- 7 сентября 2023
Как сделать тёмную тему на сайте
Без лишних слов создадим простой переключатель для светлой и темной темы с использованием HTML, CSS и JavaScript. Нам понадобятся три файла — index.html , styles.css и script.js .
HTML
Основная разметка страницы — заголовок, абзац текста, список и текст в рамке.
CSS (styles.css):
Здесь задаём цвета для светлой и тёмной темы, а ещё минимальную стилизацию текста и блока с рамкой.
body < font-family: Arial, sans-serif; transition: background-color 0.3s ease; >body.light-theme < background-color: #ffffff; color: #000000; >body.dark-theme < background-color: #121212; color: #ffffff; >.boxed-text
JavaScript (script.js)
Этот код нужен, чтобы переключать тему при нажатии на кнопку:
document.getElementById('themeToggle').addEventListener('click', function() < const currentTheme = document.body.className; if (currentTheme === 'light-theme') < document.body.className = 'dark-theme'; >else < document.body.className = 'light-theme'; >>);
При загрузке страницы по умолчанию будет установлена светлая тема. При нажатии на кнопку «Переключить тему» будет происходить переключение между светлой и темной темой.
- 29 августа 2023
4 способа центрировать текст в CSS
Центрирование элементов на веб-странице — это одна из наиболее распространенных задач, с которой мы сталкиваемся при работе с макетами. И хотя центрирование текста по горизонтали довольно простое ( text-align: center; и делов-то), вертикальное центрирование может быть немного сложнее. Давайте рассмотрим несколько методов.
Метод 1: Flexbox
Flexbox — это один из самых простых и эффективных способов центрирования.
Заворачиваем текст в с классом center-both :
Центрированный текст
.center-both
Метод 2: CSS Grid
HTML такой же, как в предыдущем примере. В CSS включаем гриды и используем свойство place-items со значением center :
.center-both
Метод 3: позиционирование и Transform
Этот метод немного старомодный и работает не идеально. Здесь у div устанавливается relative позиция. А
внутри дива мы сдвигаем с помощью абсолютного позиционирования. Не слишком элегантно:
.center-both < position: relative; >.center-both p
HTML остается таким же. Вот что получается:
Плохой метод: использование line-height
Если у вас однострочный текст, вы можете установить line-height , равный высоте родительского элемента.
.center-both < line-height: 200px; /* Пример высоты */ text-align: center; >
Этот метод не подойдет для многострочного текста. Да и вообще мы очень не рекомендуем так делать, это прям совсем для любителей острых ощущений. Потому что вот:
Если вам интересно узнать больше о каждом из этих методов, рекомендуем посмотреть документацию по Flexbox на MDN или документацию по CSS Grid на MDN, а ещё пройти курсы в HTML Academy.
- 28 августа 2023
Как скруглить рамку. CSS-свойство border-radius
CSS-свойство border-radius помогает скруглить углы элемента. Оно особенно полезно для стилизации кнопок, форм, карточек товаров и других элементов сайта.
- 28 июля 2023
CSS-свойство contain
Представьте, что у вас есть контейнер. Внутри него находятся разные элементы: текст, изображения или что-то другое. Свойство contain говорит браузеру, как именно элементы должны взаимодействовать. Например, они могут быть ограничены, влиять на расположение друг друга или менять свои размеры.
Также свойство помогает повысить производительность страницы. Например, браузер понимает, когда при изменении свойств элемента нужно перерисовать страницу, а когда нет.
⭐ CSS-свойство contain определяет, как элемент должен взаимодействовать с другими элементами внутри контейнера.
Синтаксис
.container
- 14 июля 2023
Как задать позицию и размер элемента. CSS-свойство inset
CSS-свойство inset задаёт позицию и размер элемента на странице. Это комбинация четырёх отдельных свойств: top , right , bottom и left , которые определяют отступы от верхнего, правого, нижнего и левого края элемента.
Синтаксис
.element
- 13 июля 2023