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


Сегодня мы рассмотрим:

  • что можно сделать, чтобы браузеры рассчитывали ширину блочного элемента по контенту без изменения значения свойства display;
  • альтернативный подход для отображения вертикального текста;
  • ещё один способ сохранить контрастность текста, находящегося на изображении;
  • как можно управлять областью обрезки элемента с помощью свойства clip-path.

Больше не буду затягивать. Давайте посмотрим, что я вам подготовил.


▍ Сделать ширину блочного элемента по своему контенту — больше не фантастика


На собеседовании периодически задают вопрос: «Как можно сделать так, чтобы браузеры рассчитывали ширину блочного элемента по контенту?». Первым, что приходит на ум — это поменять значение свойства display. Можно использовать любое из следующих: inline-block и inline-flex и inline-grid.


Без вопросов, это правильный ответ. Только есть ещё один метод. Это использовать ключевое слово fit-content или max-content. Сейчас всё покажу.


<body>
  <div class="awesome-block fit-content">
    <span>Блочный элемент с width: fit-content</span>
  </div>
  <div class="awesome-block max-content">
    <span>Блочный элемент с width: max-content</span>
  </div>
</body>

.awesome-block {
  padding: 0.5rem;
  margin-block: 1rem;
  background-color: lightblue;
}

.fit-content {
  width: fit-content;
}

.max-content {
  width: max-content;
}


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


Здесь может возникнуть вопрос: «А в чём разница между ключевыми словами?» Для ответа на этот вопрос мне нужно немного изменить разметку. Дополнительно обверну существующие элементы элементом с классом .container. Ему зададим ширину.


Важный момент. Она должна быть меньше, чем ширина дочерних элементов. Так мы получим переполнение.


<body>
  <div class="container">
    <div class="awesome-block fit-content">
      <span>Блочный элемент с width: fit-content</span>
    </div>
    <div class="awesome-block max-content">
      <span>Блочный элемент с width: max-content</span>
    </div>
  </div>
</body>

.container {
  box-sizing: border-box;
  max-width: 300px;
  padding: 0.5rem;
  background-color: pink;
}

.awesome-block {
  padding: 0.5rem;
  margin-block: 1rem;
  background-color: lightblue;
}

.fit-content {
  width: fit-content;
}

.max-content {
  width: max-content;
}


При использовании ключевого слова fit-content браузеры анализируют размеры родительских элементов. Если контент не помещается, то создаётся перенос. В случае с max-content такого поведения нет. Железобетонно текст будет на одной строке.


▍ В наши дни можно отобразить текст вертикально без танцев с бубном


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


В своём решении мне пришлось разбить текст на символы. Для каждого установить свойство display со значением block.


<body>
  <div class="awesome-block">
    <span class="awesome-block__letter">П</span>
    <span class="awesome-block__letter">р</span>
    <span class="awesome-block__letter">и</span>
    <span class="awesome-block__letter">в</span>
    <span class="awesome-block__letter">е</span>
    <span class="awesome-block__letter">т</span>
  </div>
</body>

.awesome-block__letter {
  display: block;
}

Конечно, мне такое решение не понравилось. Выделять на каждый символ отдельный HTML-элемент засоряет объектную модель документа. Я даже встречал, что разработчики писали JavaScript код для динамического разбиения слов. А что делать? Других вариантов не было. А сегодня есть. Знакомьтесь, свойство text-orientation.


Оно контролирует отображение символов в строке, позволяя повернуть их на 90 градусов для языков с вертикальным направлением письма и чтения. Только для этого нужно не забыть добавить свойство writing-mode. Без изменения направления текста свойство text-orientation не сработает.


Перейдём к примеру. Нам нужно все элементы с классом .awesome-block__letter заменить на один элемент <span> с классом .awesome-block__caption.


<body>
  <div class="awesome-block">
    <span class="awesome-block__caption">Привет</span>
  </div>
</body>

Для него мы определим свойство text-orientation со значением upright.


.awesome-block__caption {
  writing-mode: vertical-rl;
  text-orientation: upright;
}


Как же всё просто. Никакой хитрой магии. Пару свойств и один HTML-элемент. Идеально!


▍ Ещё один способ не дать тексту слиться с изображением


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


<body>
  <div class="hero">
    <span class="hero__msg">Hey! I'm Stas Melnikov</span>
  </div>
</body>

.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;

  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: linear-gradient(to top, rgba(15, 63, 122, 0.5), rgba(15, 63, 122, 0.5)), url("hero.webp");
}

С этим вариантом нет проблем. Просто недавно я наткнулся на ещё одно альтернативное решение. Хочу поделиться с вами.


Для начала мне нужно изменить CSS. Мы добавим свойство border и добавим к нему градиент, который использовался в качестве слоя свойства background-image.


.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;

  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: url("hero.webp");

  border-width: 50px;
  border-style: solid;
  border-image-source: linear-gradient(to top, rgba(15, 63, 122, 0.5), rgba(15, 63, 122, 0.5));
}


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


Дело в том, что по умолчанию область элемента, где отображается свойство border делится на восемь зон. Для демонстрации данного поведения я нашёл хорошую иллюстрацию на сайте MDN.



В зонах с первой по четвёртую располагаются по умолчанию изображения, указанные в свойстве border-image-source. В зонах с пятой по восьмую они тоже могут отображаться, но для этого нужна настройка с помощью свойств border-image-slice и border-image-repeat.


Все эти зоны также образуют девятую. По умолчанию она не относится к границам, но это можно изменить. Мы можем сообщить браузерам, что девятая зона заполнила весь элемент. Ключевое слово fill это делает.


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


.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;

  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: url("hero.webp");

  border-width: 50px;
  border-style: solid;
  border-image-source: linear-gradient(to top, rgba(15, 63, 122, 0.5), rgba(15, 63, 122, 0.5));
  border-image-slice: fill 1;
}


Всё работает. Осталось убрать свойства border-width и border-style. Я их использовал для демонстрации. Само решение будет работать без них.


.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;

  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: url("hero.webp");

  border-image-source: linear-gradient(to top, rgba(15, 63, 122, 0.5), rgba(15, 63, 122, 0.5));
  border-image-slice: fill 1;
}


▍ Какая есть возможность управления обрезкой элемента


Свойство clip-path достаточно известно. Его классно использовать для создания необычных масок и анимаций. Я лично создал очень много анимации на основе этого свойства.


Обычно используются базовые фигуры, заданные разным способом.


.awesome-box {
  clip-path: inset(100px 50px);
  /* 
  clip-path: circle(50px at 0 100px);
  clip-path: ellipse(50px 60px at 10% 20%);
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  clip-path: path(
    "M0.5,1 C0.5,1,0,0.7,0,0.3 A0.25,0.25,1,1,1,0.5,0.3 A0.25,0.25,1,1,1,1,0.3 C1,0.7,0.5,1,0.5,1 Z"
  );
  */
}

Но это только часть разрешённых значений. Для объяснения нужно немного отвлечься. Вспомним про свойство background.


Одним из его свойств является свойство background-clip. Оно определяет, в каких областях блочной модели будет находиться фон. Для него можно задать значения content-box, padding-box и margin-box. Надеюсь, что вы вспомнили.


Так вот. Для свойства clip-path можно сделать точно также. Мы можем указать, какие области блочной модели будут затронуты браузерами при обрезке элемента. Например, есть значения content-box, padding-box и margin-box.


Попробуем в качестве примера использовать шаблон circle(), который будет отрисован в контентной части элемента. Для этого у свойства clip-path требуется установить значения circle(50%) и content-box через пробел.


.awesome-block {
  box-sizing: border-box;
  width: 300px;
  height: 300px;
  padding: 2rem;
  margin: 2rem;
  background-color: lightblue;

  clip-path: content-box circle(50%);
}


Круг отрисован строго в контентной части до области, где браузеры отображают свойство padding. Для демонстрации разницы я удалю значение content-box.



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


▍ Заключение


Давайте подведём итог. В этой статье мы рассмотрели:

  • как ключевые слова fit-content и max-content меняют алгоритм расчёта ширины элемента, делая его зависимым от контента;
  • свойство text-orientation со значением upright, позволяющее отобразить текст вертикально, без разбиения его на отдельные символы;
  • ключевое слово fill и как его использовать для сохранения контрастности текста поверх изображения;
  • значения свойства clip-path и возможность управления областью, в которой будет происходить обрезка элемента.

Оставляю ссылки на все выпуски:

Также, пожалуйста, напишите в комментариях, какие CSS фишки вы используете, о которых другие могут не знать. Буду ждать их. Спасибо за чтение!


P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.


© 2024 ООО «МТ ФИНАНС»


Telegram-канал со скидками, розыгрышами призов и новостями IT ?

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


  1. o_O_Tync
    19.11.2024 19:41

    Спасибо, очень полезная серия!

    (Мой комментарий не очень полезный — зато вызовет улыбку :) )


    1. dendy142
      19.11.2024 19:41

      Не льстите себе


  1. ImagineTables
    19.11.2024 19:41

    Извините за оффтопик, хочу задать небольшой вопрос. Для этого я сделаю вид, что «Помогаю узнать больше про Accessibility» из профиля это приглашение задавать подобные вопросы )) На самом деле, просто специалистов по accessibility очень мало, и обсудить толком не с кем.

    Как вы боретесь с тем, что aria-controls требует id, а id должны быть глобально-уникальны? Из-за этого становится нельзя пару элементов, один из которых управляет другим, запаковать в шаблон (<template>) и инстанцировать. Точнее, можно, если при инстанцировании шаблона генерировать идентификаторы, но это такой геморрой. Думаю, добавить ли только ради этого в свой шаблонный движок что-то вроде {autoid}, или есть решение красивее?

    Чтобы было понятно, вот как мог бы выглядеть шаблон, соответствующий примеру W3:

    <template class="text-editor">
        <div class="format" role="toolbar" aria-label="Text Formatting"
            aria-controls="{autoid}">…</div>
        …
        <textarea id="{autoid}" rows="20" cols="80" style="font-family: sans-serif">
    </template>
    

    А хотелось бы увязывать вот такие парочки элементов как-нибудь на уровне локального скоупа, а не id, чей скоуп — весь документ. Дело в том, что кроме как для accessibility, это больше ни для чего особенно не нужно. Я вообще стараюсь писать idless разметку.

    Заранее спасибо.


    1. melnik909 Автор
      19.11.2024 19:41

      Как вы боретесь с тем, что aria-controls требует id, а id должны быть глобально-уникальны?

      Не борюсь. Меня id устраивает. Другие вопросы можете в тг писать. В профиле указан ник


      1. ImagineTables
        19.11.2024 19:41

        Спасибо!