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


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

  • загрузку фоновых изображений для экранов с повышенной плотностью пикселя с помощью функции image-set();
  • как с помощью неё же ускорить загрузку страницы;
  • можно ли использовать нестандартный шрифт без его загрузки;
  • чем полезен псевдо-класс :focus-within при вёрстке кастомных чекбоксов;
  • мой любимый лайфхак на основе пользовательских CSS-свойств.

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


▍ Функция image-set() и новые оптимизации загрузки фоновых изображений


За 13 лет разработки атрибут srcset и элемент <picture> были одними из самых долгожданных для меня фишек. Я помешан на оптимизации. А тогда уже нужно было использовать изображения в разных условиях. Мобильные устройства, экраны с повышенной плотностью пикселя или как способ ускорения загрузки страницы. Во всех этих случаях я применял новые возможности. Так что они очень нравились мне.


Только так нельзя было делать с фоновыми изображениями, вставленными через свойство background-image. Например, нельзя было использовать дескриптеры, как у атрибута srcset. В итоге по-прежнему приходилось загружать одно тяжёлое по весу изображение для всех устройств. Хорошо, что теперь везде работает функция image-set().


Я не помню, когда она появилась. Точно знаю, что с префиксом она работала в Google Chrome в 2012 году. А с 2021 года поддержка стала позволять использовать её в продакшене. С помощью функции мы можем подсказать браузерам, какое изображение им загружать в зависимости от условий. В целом её работа напоминает работу атрибута srcset.


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


.container {
  background-image: image-set(
    url("eiffel-tower-1x.jpg") type("image/jpg") 1x,
    url("eiffel-tower-2x.jpg") type("image/jpg") 2x
  );
}

В этом примере браузеры, используя дескриптер 2х, загрузят изображение eiffel-tower-2x.jpg для экранов с двойной и более плотностью пикселя. А для обычных будет загружено изображение eiffel-tower-1x.jpg. Здорово же? А это ещё не всё! Есть другой пример, где функция image-set() будет полезной.


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


Конечно, есть нюанс с разной поддержкой браузерами. Эту проблему решает элемент <picture>. По этой причине в интернете можно встретить примерно следующий фрагмент кода:


<body>
  <picture>
    <source srcset="eiffel-tower.avif" type="image/avif">
    <source srcset="eiffel-tower.webp" type="image/webp">
    <img src="eiffel-tower.jpg" alt="Эйфелева башня. Вид с реки Сены">
  </picture>
</body>

Если браузер поддерживает AVIF-формат, то загрузится первое изображение. Если нет, то браузер проверит, можно ли погрузить WEBP-версию. Если можно, то она будет загружена. Если нет, то уже JPG-версия.


Самый класс в том, что мы можем сделать то же самое для фоновых изображений с помощью функции image-set().


.container {
  background-image: image-set(
    url("eiffel-tower.avif") type("image/avif"),
    url("eiffel-tower.webp") type("image/webp"),
    url("eiffel-tower.jpg") type("image/jpeg"));
}

Алгоритм работы точно такой же, как в случае с элементом <picture>. Надеюсь, этот подход станет постоянным для вас. Только представьте, насколько быстрее станет ваше приложение. Оно будет как Флэш!


▍ Функция local() экономит трафик пользователя


При оптимизации скорости загрузки страницы, кроме изображений, ещё значительным пунктом являются шрифты. Поскольку они весят много. И каково было моё удивление, когда я узнал о существовании функции local().


В чём суть. С помощью функции мы можем проверить, установлен ли нестандартный шрифт на устройстве пользователя. Если установлен, то не загружать его по сети. Для этого её нужно использовать при объявлении правила @font-face. Например, я подключаю шрифт Cherry Bomb One.


@font-face {
  font-family: 'Cherry Bomb One';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local("CherryBombOne Regular"),
       local("CherryBombOne-Regular"),		
       url("CherryBombOne-Regular.woff2") format('woff2');
}

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


▍ Более надёжные кастомные чекбоксы с псевдо-классом focus-within


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


<body>
  <div class="custom-checkbox">
    <input id="cb" type="checkbox" class="custom-checkbox__input">
    <label class="custom-checkbox" for="cb">Запомнить данные</label>
  </div>
</body>

.custom-checkbox__input:focus + .custom-checkbox__label::before {
  outline: 3px solid #222;
}

Есть специалисты, которые нашли недостаток этого способа. По их мнению, часто происходит так, что люди случайно вставляют элемент <input> в другое место. По этой причине селектор перестаёт работать.


Думая над решением этой проблемы, ко мне пришла мысль: «А почему бы не заменить соседнего родственного комбинатора + на псевдо-класс focus-within?». Ведь данный псевдо-класс срабатывает, когда сам элемент или кто-то из его дочерних элементов находится в фокусе. И в случае использования его в нашей задаче, получится так, что позиция элемента <input> неважна.


Заменим фрагмент кода с комбинатором + на псевдо-элемент :focus-within.


.custom-checkbox:focus-within .custom-checkbox__label::before {
  outline: 3px solid #222;
}

Мне кажется, даже код выглядит «красивее». А вы как думаете? Напишите в комментариях.


▍ Пользовательские CSS-свойства спасают от «причуд» дизайнеров


Я думаю, каждый верстальщик хоть раз мысленно «бесился» от макета дизайнера. Порой я хватался за голову от их творческих идей. Благо из этого опыта я вынес очень полезный лайфхак.


Представим абстрактные разделы на странице. Например, «О нас», «Наши проекты» и «Контакты».


<body>
  <section class="section" aria-labelledby="about-us-heading">
    <h2 id="about-us-heading">О нас</h2>
    <!-- здесь контент раздела -->
  </section>
  <section class="section" aria-labelledby="projects-heading">
    <h2 id="projects-heading">Наши проекты</h2>
    <!-- здесь контент раздела -->
  </section>
  <section class="section section_size-l" aria-labelledby="contacts-heading">
    <h2 id="contacts-heading">Контакты</h2>
    <!-- здесь контент раздела -->
  </section>
</body>

Дизайнер захотел так, чтобы у первых двух разделов отступ от верхней границы раздела и заголовка был 2.5rem, а у последнего — 5rem. Я создам класс .section, в котором определю наиболее часто встречающееся значение 2.5rem. А для значения 5rem буду использовать класс section_l.


.section {
  padding-top: 2.5rem;
}

.section_size-l {
  padding-top: 5rem;
}


Всё работает, как задумывалось. Только в коде есть проблема. Если случайно правила поменяются местами, то у нас будет ошибка. Значение 2.5rem переопределит значение 5rem. В итоге у раздела «Контакты» будет неправильный отступ.


.section_size-l {
  padding-top: 5rem;
}

.section {
  padding-top: 2.5rem;
}


В такие моменты к нам на выручку приходят пользовательские CSS-свойства. При правильном их использовании мы можем перестать зависеть от порядка правил. Всё сводится к нескольким деталям.


Во-первых, нам нужно определить их для основного элемента.


.section {
  padding-top: var(--section-padding-gap);
}

Второй шаг заключается в том, что нужно определить значение по умолчанию. Я предлагаю использовать наиболее часто встречающиеся. В моём примере это 2.5rem.


.section {
  padding-top: var(--section-padding-gap, 2.5rem);
}

Последний шаг — определить значение для класса .section_l не через свойство padding, а через пользовательское свойство --section-padding-gap.


.section {
  padding-top: var(--section-padding-gap, 2.5rem);
}

.section_size-l {
  --section-padding-gap: 5rem;
}

Осталось проверить, будет ли всё работать, если поменять местами правила. Слушайте, а давайте добавим ещё один размер отступа. Устроим полную проверку.


Создам класс .section_size-xl и добавлю его для раздела «Наши проекты».


<body>
  <section class="section" aria-labelledby="about-us-heading">
    <h2 id="about-us-heading">О нас</h2>
    <!-- здесь контент раздела -->
  </section>
  <section class="section section_size-xl" aria-labelledby="projects-heading">
    <h2 id="projects-heading">Наши проекты</h2>
    <!-- здесь контент раздела -->
  </section>
  <section class="section section_size-l" aria-labelledby="contacts-heading">
    <h2 id="contacts-heading">Контакты</h2>
    <!-- здесь контент раздела -->
  </section>
</body>

.section_size-l {
  --section-padding-gap: 5rem;
}

.section {
  padding-top: var(--section-padding-gap, 2.5rem);
}

.section_size-xl {
  --section-padding-gap: 7.5rem;
}


Отлично. Работает. Данный лайфхак полезен при вёрстке новых проектов или при рефакторинге. Им можно задавать отступы, размеры текста, цвет, да вообще всё. Я использовал его для лендингов, дизайн-систем и проектов с сотней компонент. Он выручал меня везде.


▍ Заключение


Давайте подведём итог. Сегодня мы можем с помощью CSS:

  • использовать дескрипторы плотности пикселя экрана для отображения специальных фоновых изображений в свойстве background-image;
  • загружать более лёгкие форматы изображения при оптимизации скорости загрузки страницы;
  • подсказать браузерам, что нужно проверить, установлен ли нестандартный шрифт на пользовательском устройстве, чтобы избежать его загрузки;
  • сделать более надёжными кастомные чекбоксы;
  • использовать пользовательские CSS-свойства для упрощения поддержки кода.

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


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


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

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


  1. vanxant
    09.04.2024 15:12
    +3

    image-set

    ... не работает по поддерживаемому формату файла. По крайней мере, в тех браузерах, где это реально нужно (например, старые айфоны). Браузер увидит avif, честно его скачает, после чего скажет "фигня какая-то" и картинку не нарисует. Проверено.

    Как лечить: смотрим заголовок Accept в начале сессии и вешаем на body классы типа supports-avif и supports-webp. Дальше в CSS указываем

    .supports-webp .my-elem { background-image: url(...webp) }

    Шрифты local сломаны с момента рождения (лет 15 назад). У юзера под заказанным именем может стоять что угодно. В частности, я напарывался а) на шрифты без кириллических символов и б) на шрифты-тёзки, тех же Open Sans-ов до гугла уже было штук 5 разных.


    1. goose228
      09.04.2024 15:12

      Вы абсолютно правы, удивился что src: local кто-то достал из чулана в 2к24


    1. melnik909 Автор
      09.04.2024 15:12

       У юзера под заказанным именем может стоять что угодно. В частности, я напарывался а) на шрифты без кириллических символов и б) на шрифты-тёзки, тех же Open Sans-ов до гугла уже было штук 5 разных.

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


      1. vanxant
        09.04.2024 15:12

        • софт, который ставит в систему свои шрифты. Если он не русифицирован, то кириллицы там скорее всего не будет, даже если она есть в полном оригинальном шрифте. И я уже не говорю про версии (да, шрифты тоже часто обновляют)

        • дизайнеры и прочие близкие по духу товарищи, которые тянут всё, что плохо лежит, потому что ну надо там быстро что-то поправить, а в брендбуке заказчика прописана какая-то чуть ли не индивидуальной разработки дичь


        1. melnik909 Автор
          09.04.2024 15:12

          Спасибо


  1. ikratkiy
    09.04.2024 15:12
    +1

    А зачем в примере с уникальным значением отступа для последнего элемента вообще дополнительный класс? Есть же отличный селектор :last-child. И не нужно плодить никаких сущностей.


    1. melnik909 Автор
      09.04.2024 15:12

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