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

Как то так получилось, что несмотря на некоторый интерес к "фронтовой" теме, основные мои задачи долгое время были только в плоскости брутального windows-десктопа. Но, тем не менее изредка приходилось что-то делать с помощью html/css/React, и каждый раз удивляться извилистым путям, ведущим к решению простейших задач.

Html/css и его вариации, предлагают могучий инструментарий для воплощения самых смелых фантазий дизайнеров. И, наверное, при регулярном и глубоком погружении в тему верстки, весь этот инструментарий "прокэширован в подкорке" и "вертится на кончиках пальцев"...

А как быть тем, кто заходит в сияющий мир CSS лишь изредка — по необходимости?

Либо нужно реализовать что-то небольшое по объему работ и усилия по изучению тонкостей просто не окупятся?

Более того, даже на восьмидесятом этаже наслоений абстракций под названием React, блуждая по кабинетам React библиотекам "MUI", "AntD" и т.д. как то не обнаруживается ожидаемая интуитивная простота в решении всех простых задач верстки!

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

Эталонная разметка (Chrome 113)
Эталонная разметка (Chrome 113)

Так как речь о приложении, а не о неком html документе, то очевидно, что разметка должна занимать ровно 100% ширины и высоты области просмотра браузера.

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

Эпизод первый, разминочный: HTML

Подобную разметку можно сделать с помощью flex или grid блоков.

Попробуем пойти по "гибкому" пути в первой попытке:

<div style="display: flex; flex-direction: column; height: 100%;">
  <div style="background-color: lightblue;">Header</div>

  <div style="display: flex; flex-grow: 1;">
	<div style="background-color: bisque; flex-basis: content;">Left panel</div>
	
	<div style="overflow-y: auto; flex-grow: 1;">
      Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
      Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
	</div>
	
	<div style="background-color: bisque; flex-basis: content;">Right panel</div>
  </div>
  
  <div style="background-color: lightblue;">Footer</div>
</div>

Пройдемся по коду:

  • Разметку начинает flex блок(1), ориентирующий контент в виде вертикальной колонки и занимающий 100% высоты браузера. Колонку составляет Header(2), блок(4) с неким контентом требующем прокрутку и Footer(15)

  • Flex блок(4) в свою очередь занимает все свободное пространство в родительском блоке (flex‑grow: 1) и ориентирует контент в виде строки. В эту строку входят снова три блока: левая(5) и правая(12) панели и собственно контент со скроллом;

  • Left panel(5) и Right panel(12) нужно пометить flex-basis: content, чтобы они были не меньше контента.

Вроде все складно и должно заработать как надо? Мечты, все мечты...

Это не то, чем кажется!
Это не то, чем кажется!

Что здесь не так?

У блока с контентом(7) задано overflow-y: auto, что вроде бы должно создать область прокрутки для этого блока и ограничить его же видимый размер. А теперь еще раз смотрим на скриншот — выглядит так, будто overflow-y: auto применен к корневому блоку в первой строке.

Я когда увидел это чудо, уверовал в теорию заговора, будто разработчики html решили объявить войну очевидным и как следствие простым решениям. Только хардкор.

Благо, на связи был матерый "фронтовик", он посоветовал решение костыль:

<div style="display: flex; flex-direction: column; height: 100%;">
  <div style="background-color: lightblue;">Header</div>

  <div style="display: flex; flex-grow: 1;">
	<div style="background-color: bisque; flex-basis: content;">Left panel</div>
	
	<div style="overflow-y: auto; flex-grow: 1;">	
      <div style="height: 0">		
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
      </div>
	</div>
	
	<div style="background-color: bisque; flex-basis: content;">Right panel</div>
  </div>
  
  <div style="background-color: lightblue;">Footer</div>
</div>

Смотрим на строки 8 и 11...

Еще раз смотрим...

Нет, не костыль. Магия! Зато работает. Скриншот в начале как раз снят с этого кода :)

Итого: нас убеждают, что:

  • явно заданная нулевая высота блока не должна сделать его невидимым;

  • overflow-y как старое ружье — стреляет куда попало, а для точной привязки к нужному блоку, нужно примотать его скотчем обернуть контент блоком с нулевой высотой;

Про то, что ширина и высота в html живут по разным правилам, даже и не заикаюсь: заметили, что высоту 100% для flex контейнера(1) нужно было указывать, а ширину — нет? Откуда здесь ноги растут понятно (ориентация на прокрутку высоченных страниц и т.д.), но так ли нужно было ломать законы логики?

Вообще тема с прокруткой, весьма широка.
  • Проблема с overflow-y не решена и в tailwindcss с тегом overflow-y-auto;

  • На многих совершенно разных сайтах, заголовок вместе с меню и прочим функционалом, находится в области прокрутки и движется вместе с контентом. В результате, чтобы перейти на другую страницу, пользователю приходится скроллить к началу. Но говорят, это "модно", так же как и сопутствующая кнопка "в начало". Юзабилити? Нет, не слышали;

  • Также популярно решение, когда заголовок, хотя и находится визуально в области действия скроллбара, на самом деле не управляется им #рукалицо;

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

Помимо вышеописанных "шероховатостей", о которых нужно помнить (а вы о них забудете, если подобные вещи не делать каждый день), код выше несколько "рыхловат", много букв на "квадратный блок".

Эпизод второй, позитивный: CSS

Но что это за придирки к "старичку" html? Попробуем вынуть палки из html, создав вспомогательный файл layout.css:

html, body {
   margin: 0;
}

[col], [row] {
	display: flex;
	flex-basis: content;

}

[col] {
	flex-direction: column;
	height: 100%;
}

[row] {
	flex-direction: row;
}

[expand] {
  flex-grow: 1;
}

[scroll] {
	overflow-y: auto;
	background: white;	
}

[scroll] > * {
	height: 0;
}

[bar] {
    background-color: lightblue;
}

[panel] {
    background-color: bisque;
}

[action] {
	background-color: aqua;
}

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

  • col - блок размещающий контент в виде вертикальной колонки;

  • row - блок размещающий контент в виде горизонтальной строки;

  • expand - расширение дочернего блока у row или col до максимально возможного;

  • scroll - добавление области прокрутки (включая необходимую магию(25), задающую свойства дочернего блока);

  • bar, panel, action - просто примеры неких прочих пользовательских аттрибутов стилизующих блоки.

Диалог за кадром

- "Такой способ использования аттрибутов не является стандартным. Также кастомные аттрибуты поддерживаются не во всех браузерах! А вдруг IE дремучей версии случится?!";

- "Ну да, 100501-й способ усложнить жизнь разработчику... А что предлагаешь?";

- "Классы, конечно!"

Теперь пишем новый html, не забывая подключать наш css:

<link rel="stylesheet" href="layout.css" />

<div col>
  <div bar>Header</div>

  <div row expand>
	<div panel>Left panel</div>

	<div scroll expand>	
	  <div>		
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
  	  </div>
	</div>

	<div panel>Right panel</div>
  </div>

  <div bar>Footer</div>
</div>

Да, это рабочий html, генерирующий картинку как на первом скриншоте. Код короче, и вся магия ушла в css, помнить о ней не нужно (почти).

Проясним использование атрибутов:

  • Корневой блок(3) помечаем атрибутом col, ибо он колонка;

  • Дочерний блок(4) помечаем bar - просто оформление цветом;

  • Дочерний блок(6) - контейнер, помечаем row, чтобы он был горизонтальным и expand, чтобы занял все свободное место между панелями Left panel и Right panel;

  • Блок с контентом(9) помечаем как scroll и он становится областью прокрутки, а добавление expand, позволяет занять все доступное пространство;

  • и т.д...

Единственное, что не удалось решить в рамках css, это требование наличия блока вокруг контента scroll. Просто текст не получится. Но это не беда, далее все будет!

И хотя поставленная задача решена, простота полученного кода так и призывает "навернуть" что-нибудь еще:

<link rel="stylesheet" href="layout.css" />

<div col>
  <div bar>Header</div>

  <div row expand>
	<div col panel>Left panel</div>

	<div scroll expand>	
	  <div>		
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
  	  </div>
	</div>

	<div panel col>
      <div>
        Right panel
      </div>

      <div scroll expand>
        <div>
          Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
          Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        </div>
      </div>
      
      <div>
        Right footer
      </div>
	</div>
  </div>
  
  <div bar row>
    <div expand>
      Footer
    </div>
	
    <div action>
      [Icon]
    </div>
	
    <div action>
      [Action]
    </div>
  </div>
</div>

Здесь мы доработали следующее:

  • Right panel(16) добавлением атрибута col, легко превращается в вертикальный контейнер, также добавляем область прокрутки(21) с контентом по образцу в строке 9. Обратите внимание: эти два блока с контентом в вертикальном и горизонтальном контейнере настраиваются абсолютно одинаково;

  • Footer(34) также просто становится горизонтальным контейнером с помощью атрибута row. На десерт, в этом блоке пробуем максимально наглядный способ размещение блоков в контейнере: если некий блок(35) с помощью expand занимает все доступное пространство, остальные(39, 43) - сиротливо теснятся в стороне (справа в данном случае). Куда еще "материальнее" ?

Вот окончательный рендер:

Усложненный пример
Усложненный пример

Понятно, что такой "дизайн" нужен далеко не всем. И вообще, творить визуальную нетленку, дело утонченных дизайнеров, а не суровых разработчиков. Хотя, местами, они жертвуют юзабилити какому-то своему божеству. Но, имея простой инструмент, логику которого не нужно вспоминать (привет блоку с нулевой высотой), будет гораздо легче воспринимать вашего дизайнера.

Эпизод третий, фееричный: MUI и Ant design

Разумеется, на чистом html + css мало кто сейчас работает. Поэтому посмотрим, что предлагают в обсуждаемой области две популярнейших библиотеки MUI и Ant design:

Что ожидаешь, обращаясь к популярным продуктам с "именем"?

Ну вероятно, что они то умеют в разметку. Сделают все удобно и лаконично. Продумают все варианты. Это были мечты. Далее суровая реальность.

Компоненты разметки MUI:

  • Container - просто центрирование по горизонтали. Это тег, не атрибут. Странно, что нет тегов Right и Left;

  • Grid - похоже мимо. Больше подходит для контента с массивом элементов;

  • Stack - горизонтальный\вертикальный контейнер, это что-то близкое к задаче. Функционал компонента Stack ограничен только выстраиванием дочерних блоков в столбик или линию. Все. Внезапно. Занавес.

Компоненты разметки AntD:

  • Space - более продвинуты аналог Container из MUI;

  • Layout - глядя на примеры, кажется, что это 100% попадание. Кажется. Механизм заполнения блоком клиентской области не продуман, т.е. без инжектирования css повторить разметку подобную целевой, не получится;

  • Grid - более продвинутый (вероятно) аналога Grid в MUI, тоже мимо;

Другими словами повторить целевую разметку без использования css не получается!

Где аналог expand на основе flex-grow или вроде того?

И\или выравнивание элементов контейнеров лево-центр-право?

До последнего момента не ожидал такого фиаско.

Итого:

Готовой функциональности по разметке для приложений, сложнее "пролайкать котиков" нам не дают, оставляя лазейку в виде низкоуровневого html + css.

Где-то это уже было? Может в html + css?

А для чего тогда эти библиотеки? Ах, да — контролы и документо-ориентированная разметка...

#совсем-рука-лицо

Я очень хочу заблуждаться, в надежде, что уважаемое комьюнити ткнет меня, как слепого котенка, в нужный компонент из состава MUI или Ant Design.

В противном случае, базовая вещь - разметка, остается не простой. И дело не в особой сложности flex контейнеров, а в том, что css содержит множество фич, объединение которых в одной задаче, может быть весьма нетривиальным делом. Поэтому наличие простой согласованной высокоуровневой компонентной обертки над html+css считаю залогом качественной React библиотеки. Такой библиотеки, которая не будет заставлять меня гадать, какие из фич css конфликтуют друг с другом вместо предоставления набора "рычагов" каждый из которых будет работать ожидаемо вне зависимости от других.

Эпизод последний: Box, just Box

В начале разработки UI в проекте GeekLoad, сразу была заложена возможность смены библиотеки компонентов (вдруг не подойдет). А так как теги разметки, самые частые в UI, то хотелось избежать массовой переделки всего и вся в этом случае.

Это, а также неудовлетворительная проработка компонентов-контейнеров в рассматриваемых библиотеках побудили к реализации очередного "велосипеда" - React компонента <Box/>.

Главное требование к компоненту было: не вставлять палки в колеса разметку, а конструировать ее понятным и предсказуемым образом.

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

Для начала - аналог компонента MUI:Container:

<Box class='bar'>Content</Box>
Использование классов здесь - только для подсветки блоков, а для разметки совсем не обязательно!
Использование классов здесь - только для подсветки блоков, а для разметки совсем не обязательно!

Добавлением атрибута expand - занимаем всю клиентскую область браузера:

<Box expand class='bar'>Content</Box>
Контент в центре видимой области браузера
Контент в центре видимой области браузера

Чтобы получить прямой аналог AntD:Space, нужно просто воспользоваться свойствами hAlign и vAlign, не думая о том какая ориентация контейнера использована: горизонтальная или вертикальная (по умолчанию - горизонтальная, как в примере выше):

<Box hAlign = {Align.End}
     vAlign = {Align.End}
     class = 'bar'
>Content</Box>

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

Пример позиционирования дочерних элементов. Да, их может быть несколько.
Пример позиционирования дочерних элементов. Да, их может быть несколько.

Теперь кратко поясним логику компонента Box:

  • Aтрибут col распределяет контент и растягивает сам блок по вертикали, если его нет, то контент распределяется и сам блок растягивается по горизонтали;

  • Body html документа "за кадром" ведет себя как <Box col> дополнительно растянутый по горизонтали;

  • Атрибут expand растягивает Box по направлению его контейнера. Если таких блоков более одного, они делят доступное пространство поровну;

  • Атрибут scroll автоматически применяет col блоку и превращает его в область прокрутки, без дополнительных требований к контенту;

  • Атрибут gap включает отступы между блоками контента, может быть значением отступа в единицах 'rem'. По умолчанию = 1;

  • Для выравнивания контента служат атрибуты hAlign и vAlign:

    • Align.Start - выравнивание по верхнему или левому краю;

    • Align.Center (по умолчанию) - выравнивание по центру;

    • Align.End - выравнивание по нижнему или правому краю;

    • Align.Fill - растягивание блоков отличных от Box перпендикулярно направлению блока.

  • Атрибут class - задает необходимые css классы блоку.

И затем, реализуем "целевую разметку":

<Box expand>
  <Box col expand>
    <Box class = 'bar'>Header</Box>

    <Box expand>
      <Box col class = 'panel'>Left panel</Box>

      <Box scroll expand>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
      </Box>

      <Box col class = 'panel'>Right panel</Box>
    </Box>

    <Box class = 'bar'>Footer</Box>
  </Box>
</Box>

В этом примере следующая логика:

  • Корневой Box(1) служит для растягивания используемого пространства по горизонтали. Это понадобилось сделать, чтобы не нарушать логику компонента в остальных случаях.

  • Box(2) col, распределяет блоки Header(3), Content(5) и Footer(13) вертикально, а expand захватывает пространство по направлению контейнера, т.е. горизонтально;

  • Box(3) - горизонтальный блок "Header";

  • Box(5), распределяет блоки Left panel(6), Content(8) и Right panel(13) горизонтально

  • Left panel(6) и Right panel(13) помечены атрибутом col, чтобы быть вертикально растянутыми;

Отличие от целевой разметки - только в выравнивании по умолчанию по центру.
Отличие от целевой разметки - только в выравнивании по умолчанию по центру.

И, на последок, усложняем немного задачу:

<Box expand>
  <Box col expand>
    <Box gap = {2} class = 'bar'>
      <Box>Header</Box>
      <Box expand = {2} class = 'action'>[Menu]</Box>
      <Box expand class = 'action'>[Login]</Box>
    </Box>

    <Box expand>
      <Box col class = 'panel'>Left panel</Box>

      <Box scroll expand>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
        Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
      </Box>

      <Box col>
          <Box class = 'panel'>Right header</Box>

          <Box scroll expand>
            Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
            Content<br/>Content<br/>Content<br/>Content<br/>Content<br/>
          </Box>

          <Box class = 'panel'>Right footer</Box>
      </Box>
    </Box>

    <Box gap class = 'bar'>
        <Box expand class = 'bar'>Footer</Box>

        <Box class = 'action'>[Icon]</Box>
        <Box class = 'action'>[Action]</Box>
    </Box>
  </Box>
</Box>

Надеюсь, этот пример после предыдущих уже понятен. Если не особо - пишите в комментариях - поправлю статью.

Дизайнеры за такое убьют. Но мы им не покажем ;)
Дизайнеры за такое убьют. Но мы им не покажем ;)

Ниже, под катом, исходники упрощенной версии описываемого компонента. Разумеется какие-то решения покажутся спорными, но, уверен, кому то идея зайдет, как минимум в качестве прототипа.

Исходники <Box/>

Box.css:

#root,
html,
body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

#root {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.box {
    flex-basis: content;
    display: flex;
}

.row {
    flex-direction: row;
    width: 100%;
}

.col {
    flex-direction: column;
    height: 100%;
}

.scroll {
    overflow-y: auto;
    overflow-x: hidden;
    width: 100%;
    height: 100%;
}

.bar {
    background-color: lightblue;
}

.panel {
    background-color: bisque;
}

.action {
    background-color: aqua;
}

Box.tsx:

import {ReactNode} from 'react';
import './Box.css'

export enum Align {
    Start = 'start',
    End = 'end',
    Center = 'center',
    Fill = 'stretch'
}

export default (props: {
    class?: string
    scroll?: boolean
    hAlign?: Align
    vAlign?: Align
    col?: boolean
    expand?: boolean | number
    children?: ReactNode
    gap?: boolean | number
}) => {
    const hAlign = props.scroll ? Align.Fill : props.hAlign || Align.Center
    const vAlign = props.scroll ? Align.Fill : props.vAlign || Align.Center
    const gap = (props.gap || false) ? (((typeof props.gap) == 'boolean' ? 1 : props.gap) + 'rem') : undefined
    const expand = Number((props.expand || false) ? (((typeof props.expand) == 'boolean' ? 1 : props.expand)) : undefined)

    return <div className = {
        ('box ' +
            (props.col ? 'col ' : 'row ') +
            (props.scroll ? 'scroll ' : '') +
            (props.class ? props.class : '')
        ).trim()
    }
                style = {{
                    flexGrow: expand,
                    justifyContent: props.col ? vAlign : hAlign,
                    alignItems: props.col ? hAlign : vAlign,

                    rowGap: props.col ? gap : undefined,
                    columnGap: props.col ? undefined : gap
                }}
    >
        {props.scroll
            ? <div style = {{height: 0}}>{props.children}</div>
            : props.children}
    </div>
}

export function AppScreen(props: { children: ReactNode }) {
    return <div style = {{height: '100%'}}>{props.children}</div>
}

- "А в чем была суть вообще?"

На самом деле, различных усложнений в html + css разметке можно насобирать еще порядочно, но хотелось взять какой то кейс и довести его до решения, которое показалось бы достаточным.

Так как считаю разметку в большинстве случаев простой задачей, ожидаю от инструментов для ее реализации - простоты. И в ситуациях, когда Box перестает "дружить" с каким то вариантом разметки, он дорабатывается так, чтобы и предыдущие и новые кейсы лаконично поддерживались.

Здесь и кроется ответ на вопрос в начале статьи: Если в некой предметной области вы не частый гость, то хорошим подходом будет консолидация "сложности&магии" в неких точках. Box - как раз один из таких. Вместо того, чтобы по всему коду приложения раскидывать некие решения задач разметки, лучше собрать их в одном месте, а далее везде использовать краткие и понятные атрибуты. Даже если вы забудете суть найденных решений, логичный фасад вокруг них, избавит вас от мучительных попыток "вспомнить все".

Очень жаль, что мэтры компонентостроения множат сущности там, где это не нужно, но не прорабатывают некоторые очевидные кейсы.

P.S.

В комментариях к статье, вместо Flex был предложен Grid. Но его классическое использование заключается в "рисовании" некой "карты разметки", например:

    display: grid;
    grid-template-columns: 1fr 4fr 1fr;
    grid-template-rows: 20px minmax(0, 1fr) 40px;
    grid-template-areas:
      "h h h"
      "l m r"
      "f f f";

Такой подход подразумевает следующее:

  1. С ростом сложности разметки, "карта" тоже будет увеличиваться;

  2. Затем, вы захотите перенести какую то часть разметки в отдельный компонент (подразумевается, что большие приложения html\css вне какого либо компонентного React не пишутся);

  3. В случае предлагаемого выше в статье подхода "взаимоположение блока определяется самим блоком и его контейнером", после п.2 все продолжает работать как и ранее, а при наличии "карты" придется вспомнить\разобраться, где в ней что и откорректировать ее. Также понадобятся корректировки при обратном переносе.

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

Поэтому grid, как его предлагал, например kotan-11, мне не кажется удобным. Мое право, а также тех, кому это кажется удобным.

Разумеется, привычка вторая натура, и не вызывает никакого удивления то, что кто-то (может и многие) прикипели к "сетке". Их право.

В этой статье автор помимо прочего предлагает объединять grid и flex в одной разметке. Но помимо вышеописанного, в этом случае мы имеем увеличение совокупной сложности решения, т.е. получается "простое не просто".

Также, было проверено использование grid "по месту" также как и flex:

<div style="display: grid; grid-template-rows: auto 1fr auto; height: 100%;">
  <div style="background-color: lightblue;">Header</div>

  <div style="display: grid; grid-template-columns: auto 1fr auto;">
    <div style="background-color: bisque;">Left panel</div>

    <div style="overflow-y: auto;">
        Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>
        Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>
    </div>

    <div style="background-color: bisque;">Right panel</div>
  </div>

  <div style="background-color: lightblue;">Footer</div>
</div>

Результат:

Проблема overflow-y: auto существует вне выбора Grid\Flex
Проблема overflow-y: auto существует вне выбора Grid\Flex

Т.е. обсуждать здесь можно только выбор концепции разметки "карта" или "по месту" и только после выбора оной, искать подходящий низкоуровневый инструмент.

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


  1. mclander
    31.05.2023 19:30

    Мне кажется, что это можно сделать с помощью банальных скучных классов css, более банально скучно и понятно.


    1. old-school-geek Автор
      31.05.2023 19:30
      -1

      1) Как предлагаете полностью "спрятать" странное поведение overflow-y: auto в классе?


      1. old-school-geek Автор
        31.05.2023 19:30

        2) Промежуточное решение как раз и было основано на атрибутах css, которые близки классам, но компактнее в использовании. Если кому лучше подходят классы - не вопрос. Речь про то, что пока не попадалось элегантных решений.


      1. devlev
        31.05.2023 19:30
        +1

        Я думаю что предыдущий комментарий был отсылкой к тому, что в HTML часто для разметки стилей используют просто классы, а не атрибуты, как у вас. Исторически было так что стили на атрибутах работают медленее чем на классах, но как сейчас обстоят дела с производительностью стилей на атрибутах я не знаю. К теме странного поведения overflow-y: auto это не имеет никакого отношения.

        Grid как раз и был создан для того чтобы решать подобные проблемы и отлично решает их. Flex - это как бы 1D (либо по горизонтали/либо по вертикали), а Grid - это как бы 2D (и по горизонтали и по вертикали).


        1. old-school-geek Автор
          31.05.2023 19:30

          Отвечу вам и kotan-11 в самой статье в виде дополнения. Вижу некое недопонимание.


        1. old-school-geek Автор
          31.05.2023 19:30

          Про 2D тоже понятно. Но я исхожу из того, что 2D = 1D + 1D. И каждое измерение\уровень блоков живет своей жизнью и может быть перемещено без изменения остального кода.


      1. kotan-11
        31.05.2023 19:30

        Попробуйте решение: https://latis.cc/comment/300. Это grid, и в нем нет никаких костылей.


        1. old-school-geek Автор
          31.05.2023 19:30

          Спасибо за идею. Ответил дополнением в конце статьи.


          1. Nikeware
            31.05.2023 19:30

            Тот лайоут, что Вы повторили с помощью grid, можно упростить до использования одного единственного <div style="display: grid; и не "городить" ещё один вложенный grid.

            <div style="display: grid; grid-template-columns: auto 1fr auto; grid-template-rows: min-content auto min-content; overflow-y: auto; height: 100%;">
            
              <div style="background-color: lightblue; grid-column: 1 / span 3">Header</div>
            
            	<div style="background-color: bisque;">Left panel</div>
            
              <div style="overflow-y: auto;">
                  Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>
                  Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>Grid<br/>
              </div>
            
            	<div style="background-color: bisque;">Right panel</div>
            
              <div style="background-color: lightblue;  grid-column: 1 / span 3">Footer</div>
            
            </div>


            1. old-school-geek Автор
              31.05.2023 19:30

              Заметьте, вы сводите решение к частному случаю, в котором возможно "упростить grid", но беда частных случаев в том, что их нужно помнить. В статье же, я пытался показать универсальный способ построения разметки. В котором для достижения результата делается одно и то же, независимо от уровня вложенности блоков и т.д.

              Т.е. например Карандаш - он всегда рисует, независимо от того круг или квадрат. А когда нужно нарисовать треугольник, вы смело берете карандаш и рисуете, не вспоминая, в каком окружении его нужно нарисовать и т.д.

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

              Но, разумеется, удобнее использовать фасад (вроде показанного Box), который "сделает все красиво" и будет столь же простым, как и оптимизированная частная реализация, вроде показанной вами.

              И первая строка слегка выбивается из понятия "просто" :)


              1. Nikeware
                31.05.2023 19:30

                Ваша "универсальность" сводится к слишком узкому пониманию того, как реализованы и работают очень богатые возможности grid и flexbox. По сути вы свели не "нет" всё то, что вам предлагает такая богатая технология.
                А по поводу карандаша ... У нас в команде таких "художников" рисовать карандашом столько, что голова кругом идет. Постоянно приходиться бить по рукам. На форме от силы 5 - 6 элементов ввода, а её layout содержит столько же, а иногда даже больше div контейнеров с о стилем display:grid; вложенных друг в друга чуть линао 4-ре уровня в глубину (!) Это же тихий ужас!

                p.s. Если для Вас первая строка в приведённом мной примере вызывает сложность, я бы советовал подучить сначала мат. часть :-) Там вообще-то базовые вещи. Без них Вам будет в дальнейшем сложно.


                1. old-school-geek Автор
                  31.05.2023 19:30

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

                  Непонятен тезис про количество div+grid. Думаю, привязываться нужно не к количеству элементов ввода, а к количеству функциональных зон. При этом не вижу никакого криминала в том, чтобы обернуть каждую зону одним div (если мы говорим о чистом html\css). В рамка одной функциональной зоны, лишних div не будет.

                  Также, вы слишком буквально понимаете слово "сложность". Речь о избыточном синтаксисе и т.д. Проблем осознать приведенные синтаксические конструкции нет.


      1. Nikeware
        31.05.2023 19:30

        Вам для блока (4) нужно тоже добавить overflow-y: auto и всё у Вас заработает. Общий концепт таких разметок в том, что если Вам нужно некая прокручиваемая область, то overflow нужно "прослеживать" вплоть до родительского элемента на несколько "поколений" вверх по иерархии child-parent. Если в этой цепочке у некоего div где-то отсутствует overflow, то всё "рвётся", т.к. высота такого блока расчитывается по сумме высот всех блоков в него входящих и сколлируемость как бы переходит на уровень вверх по иерархии.

        p.s. для Left и Right тоже нужно добавить overflow.


        1. old-school-geek Автор
          31.05.2023 19:30
          -1

          Спасибо за пояснение логики, которая от ясности не становится простой.

          Мне как пользователю не понятно, почему просто указания "сделай это прокручиваемой областью" недостаточно.

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


          1. Nikeware
            31.05.2023 19:30
            +1

            Логика "проста". Overflow параметр всего лишь необходимое условие, но никак недостаточное. Вы поймите, что если Вы в некий блок натолкали каких-элементов и не зафиксировали явно или неявно размер этого блока, то его результирующий размер - это сумма всех размеров его елементов (children).

            Если у блока с overflow его родитель сам не имеет overflow, то результирующий размер родителя - это опять же сумма всех размеров его елементов (children). Естественно ваш блок не получит скроллбар, т.к. в родителе он получил достаточное пространство для отображения всего своего контента. Поэтому скроллбар возможно появится у другого parent или grand parent вверх по иерархии, пока для этого не появятся соответствующие уловия.

            И так будет всегда, пока всё это дело не "упрётся" в body, который конечно же физически ограничен размерами клиентской области окна и как результат - вы видите скроллбар у всего докуметна.


            1. old-school-geek Автор
              31.05.2023 19:30
              -1

              Логика проста для человека, который регулярно занимается разметкой.

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

              И я не спорю, с вашим объяснением особенностей overflow :)


              1. Nikeware
                31.05.2023 19:30

                c вашим объяснением особенностей overflow : )

                Это не особенности, это стандартно реализованное поведение.


  1. old-school-geek Автор
    31.05.2023 19:30
    -1

    Один из читателей предложил Grid вместо Flex.


    1. old-school-geek Автор
      31.05.2023 19:30
      -1

      Но ни тот, не другой как высокоуровневое средство разметки мне не кажется удобным.

      Принципиальной разницы в случае "упаковки" в некий компонент\набор css классов не вижу.


    1. Nikeware
      31.05.2023 19:30

      Если мы рассматриваем только css-стиль overflow, то Grid и Flex в этом контексте совершенно "одинаковы".


      1. old-school-geek Автор
        31.05.2023 19:30

        Да, я об этом тоже дописал в конце статьи.

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


        1. Nikeware
          31.05.2023 19:30

          "Простое должно быть простым"

          Просто было в 90-е с Netscape Navigator :-) Такие богатые вызальными эффектами страницы, которые сейчас существуют, простыми инструментами не получить.


          1. old-school-geek Автор
            31.05.2023 19:30

            Напишу здесь мой любимый принцип полностью, он и будет ответом: "Простое должно быть простым, а сложное - возможным".


  1. ErshoffPeter
    31.05.2023 19:30

    Спасибо автору от труженников back-end-а, которым раз в год надо что-нибудь на фронте замастрячить! ????


  1. dom1n1k
    31.05.2023 19:30
    +1

    Судя по постскриптуму, вы плохо понимаете, как нужно использовать вариант @kotan-11, но поспешили отвергнуть его. Его сила как раз в уходе от порочного принципа "взаимоположение блока определяется самим блоком и его контейнером" в пользу правильного - "компонент знает только своё собственное устройство и ничего не знает о родителе и соседях".

    Взаимное расположение блоков определяется централизованно, той самой "картой". Особенно удобно делать отзывчивость: по медиа-запросу меняются несколько свойств grid-template-* у лейаута - и все компоненты-потомки перестраиваются. Ничего не зная друг о друге. В произвольное количество рядов и столбцов, потому что промежуточные обертки не путаются под ногами. Нативно, без лишних перерендеров DOM-а.


    1. old-school-geek Автор
      31.05.2023 19:30

      Вариант @kotan-11 понят мной именно так, как вы его описываете. Только как раз ваша интерпретация предложенного мной подхода неверна (возможно из-за моих недостаточных литературных способностей).

      Я предлагаю централизованную карту разметки распределить по функциональным блокам, расположенным по месту. Компоненты, размещаемые в этих блоках действительно не знают о соседях. Но блоки - знают. И только они определяют положение дочерних компонентов. В примерах из статьи, "компоненты" вырождены в простой текст и стиль блока. Также, при необходимости, можно в одном блоке на любом уровне вложенности разместить любое количество любых компонентов\блоков, которые не будут знать о своем местоположении ничего, но тем не менее выстроятся нужным образом. И, меняя свойства контейнера, можно перестраивать расположение дочерних компонентов, которые ничего по прежнему не знают друг о друге.

      Во пример "компонентов" не знающих о соседях и не определяющих своего положения, но выстраивающихся так, как это задает их контейнер. Здесь, меняя свойства контейнера (col, gap, xAlign), можно изменять положение дочерних блоков.

      <Box col gap expand>
          <Box gap hAlign = {Align.Start}>
              <div className = 'bar'>Component</div>
              <div className = 'bar'>Component</div>
              <div className = 'bar'>Component</div>
          </Box>
      
          <Box gap>
              <div className = 'action'>Component</div>
              <div className = 'action'>Component</div>
              <div className = 'action'>Component</div>
              <div className = 'action'>Component</div>
          </Box>
      
          <Box gap hAlign = {Align.End}>
              <div className = 'panel'>Component</div>
              <div className = 'panel'>Component</div>
          </Box>
      </Box>

      Вот пример динамически перестраиваемой разметки с компонентом Box (попробуйте изменять ширину окна браузера) :

      https://geekload.io/downloads

      А вообще мы, как это обычно бывает, "соскользнули" с главной темы.

      Суть вообще не в противостоянии Grid vs Flex или "карта" vs "по месту". Речь про короткий внятный элегантный синтаксис разметки, логику которого не нужно вспоминать. Причем, что важно, именно во взаимодействии нескольких аспектов (flex\вложенность + overflow) или любых других.


      1. dom1n1k
        31.05.2023 19:30

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

        Как будто бы да, но нет. Ваша абстракция протекает.
        Дочерние блоки может и "не знают" о соседях, но косвенно их "чувствуют" при делёжке доступного места. Может возникать неявная взаимозависимость размеров. В гридах такое тоже возможно, если нужно. А если не нужно — легко реализуются действительно независимые ячейки.


        Кроме того, ваша система не умеет нормальный адаптив. Даже тривальный случай (2 в ряд -> 2 в столбик) требует JS, а что-то нетривальное просто невозможно.


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


        1. old-school-geek Автор
          31.05.2023 19:30

          Вижу два посыла в вашем ответе:

          1. Grid мощнее: Я не против грида в принципе. При необходимости можно использовать его. Что считаю неудобным - централизованная "карта" сразу всей разметки в нем. Но это холиварный вопрос, понимаю.

          2. Grid привычнее: А вот здесь очень интересно. В свое время Grid был "непривычной новинкой" и т.д. Попробуйте отнестись к этой статье, не как к руководству к действию, а как обозначению проблемы и некоторым идеям, которые можно использовать как угодно.

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