Теперь можно обойтись и без псевдоэлементов

alexei04/10/2021 - 09:27
Теперь можно обойтись и без псевдоэлементов

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

Согласно спецификации W3C, "псевдоэлемент представляет элемент, непосредственно не присутствующий в дереве документа". Псевдоэлементы существуют еще с версии 1 спецификации CSS. Тогда были представлены ::first-letter и ::first-line. Популярные ::before и ::after были добавлены в версии 2. Они представляют контент, которого вообще нет в исходном документе. Их можно рассматривать как два дополнительных элемента, которые вы можете "привязать" к исходному элементу. Когда фронт-энд разработчики слышат "псевдоэлементы", то как правило вспоминают о ::before и ::after, поскольку часто используют их для добавления различными способами украшательств к элементам.

Помимо этого, существуют и другие псевдоэлементы.

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

Еще один интересный псевдоэлемент - ::selection. Когда пользователь выделяет текст, цветом выделения будет указанный в этом псевдоэлементе цвет.

Стоит сказать, что согласно спецификации CSS версий 1 и 2 псевдоэлементы использовали одно двоеточие, однако начиная с версии 3 псевдоэлементы используют два двоеточия. Это отличает их от псевдоклассов, которые описывают состояние элемента. Псевдоклассы используют одно двоеточие.

  • Используйте два двоеточия для псевдоэлементов (например, ::before, ::after, ::marker).
  • Используйте одно двоеточие для псевдоклассов (например, :hover, :focus).

Псевдоэлементы нужны не всегда

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

Еще один взгляд на проверенные приемы с новыми свойствами CSS

В течение многих лет мы терпеливо ждали, пока браузеры быстрее освоят технологию CSS. Поворотный момент для многих фронт-энд разработчиков наступил, когда некоторые крупные игроки объявили, что прекращают поддержку Internet Explorer (IE11) :

  • Все веб-приложения Microsoft 365 перестали поддерживать IE11 21 августа 2021 года.
  • Google Workspace (Gmail, Calendar, Drive и т.д.) прекратил поддержку IE11 15 марта 2021 года.

Это позволило многим из нас более свободно исследовать новые технологии CSS: CSS Grid, clamp(), background-blend-mode и многое другое. Уровень поддержки новых свойств CSS уже довольно высок. А благодаря обновлению браузеров поддержка ширится. Итак, к примерам!

Угловые кнопки

Многие фронт-энд разработчики знают как используя псевдоэлементы ::before и ::after и правила границ CSS создавать различные фигуры. Для этой цели существует множество онлайн-генераторов. Эти инструменты помогают выбрать форму (часто треугольники) и выдают корректный код CSS.

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

Сравнение количества строительных блоков для угловых кнопок с псевдоэлементами и без
Угловые кнопки с псевдоэлементами могут потребовать больше HTML элементов

Версия с псевдоэлементом

Многим из вас, вероятно, данная версия будет знакома.

HTML


<button class="btn—angle">
  <span class="btn__inner">An angle button</span>
</button>

<hr>

<button class="btn—angle">
  <span class="btn__inner">An angle button with a longer name just fails</span>
</button>

CSS


.btn--angle {
    --angle-width: 2rem;
    --bg-color: lightseagreen;
    --bg-color-hover: steelblue;
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --padding-x: 1rem;
    --padding-y: 1rem;
    
    background-color: transparent;
    color: black;
    display: inline-block;
    line-height: 1;
    padding-right: var(--angle-width);
    position: relative;
}

.btn--angle::before {
    content: '';
    border: 0 solid transparent;
    border-bottom-width: calc(var(--angle-width) * 0.75);
    border-top-width: calc(var(--angle-width) * 0.75);
    border-left: var(--angle-width) solid var(--bg-color);
    position: absolute;
    right: 0;
    top: 0;
    transition: border-color var(--duration) var(--easing);
}

.btn--angle .btn__inner {
    background-color: var(--bg-color);
    display: block;
    padding: var(--padding-y) var(--padding-x);
    transition: background-color var(--duration) var(--easing);
}

.btn--angle:hover::before {
    border-left-color: var(--bg-color-hover);
    transition: border-color var(--duration) var(--easing);
}

.btn--angle:hover .btn__inner {
    background-color: var(--bg-color-hover);
    transition: background-color var(--duration) var(--easing);
}

  • Используем элемент-обертку, который имеет относительное позиционирование, с большим правым отступом, чтобы влез угол - это элемент <button>;
  • Многие из нас привыкли для определения цвета фона кнопки использовать вложенный элемент;
  • И наконец, при помощи абсолютного позиционирования помещаем псевдоэлемент с нужными значениями границы в правый отступ элемента <button> - для этого используем псевдоэлемент ::before.

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

Версия без псевдоэлемента

Без псевдоэлементов все становится гораздо проще.

HTML


<button class="button—angle">
  An angle button
</button>

<hr>

<button class="button—angle">
  An angle button with a longer name
</button>

CSS


.button--angle {
    --bg-color: lightseagreen;
    --bg-color-hover: steelblue;
    --angle-width: 2rem;
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --padding-x: 1rem;
    --padding-y: 1rem;
    
    background-color: var(--bg-color);
    clip-path: polygon(0% 0%, calc(100% - var(--angle-width)) 0%, 100% 50%, calc(100% - var(--angle-width)) 100%, 0% 100%);
    color: black;
    display: inline-block;
    padding: var(--padding-y) var(--padding-x);
    padding-right: calc(var(--angle-width) + var(--padding-x));
    transition: background-color var(--duration) var(--easing);
}

.button--angle:hover {
    background-color: var(--bg-color-hover);
    transition: background-color var(--duration) var(--easing);
}

  • Используем один элемент-обертку - это <button>.
  • Свойство clip-path используем, чтобы отображать только те части нашей кнопки, которые нам нужны. При помощи calc() с пользовательским свойством CSS задаем размер угла. Следующие наборы точек соответствуют верхнему левому, верхнему правому, среднему правому, нижнему правому и нижнему левому: polygon(0% 0%, calc(100% - var(--angle-width)) 0%, 100% 50%, calc(100% - var(--angle-width)) 100%, 0% 100%).

Чтобы увидеть, как угол у нашей кнопки регулируется, вы можете в интерактивном примере CodePen изменить пользовательское свойство --angle-width с 2rem на какое-нибудь другое значение.

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

Эффект вытеснения

Эффект вытеснения — довольно популярен в стилях кнопок. Здесь мы рассмотрим вытеснение слева направо и сверху вниз.

Версия с псевдоэлементом

Эффект получаем при помощи свойств transition и transform псевдоэлемента.

HTML


<button class="btn—wipe">
  <span class="btn__inner">Wipe left to right</span>
</button>

<button class="btn--wipe btn—wipe-vertical">
  <span class="btn__inner">Wipe top to bottom</span>
</button>

CSS


.btn--wipe {
    --bg-color: thistle;
    --bg-color-hover: darkorange;
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --padding-x: 1.5rem;
    --padding-y: 1rem;
    
    background-color: var(--bg-color);
    color: black;
    display: inline-block;
    line-height: 1;
    position: relative;
}

.btn--wipe::before {
    background-color: var(--bg-color-hover);
    content: '';
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    transform: scaleX(0);
    transform-origin: 0 0;
    transition: transform var(--duration) var(--easing);
    width: 100%;
}

.btn--wipe .btn__inner {
    display: block;
    padding: var(--padding-y) var(--padding-x);
    position: relative;
}

.btn--wipe:hover::before {
    transform: scaleX(1);
}

/* btn--wipe-vertical */

.btn--wipe-vertical::before {
    transform: scaleY(0);
}

  • Задаем абсолютную позицию для псевдоэлемента ::before и определяем свойство transform: scaleX(0), чтобы он был невидим.
  • Также нужно явно установить для него свойство transform-origin: 0 0, чтобы вытеснение начиналось слева, а не по центру (по умолчанию transform-origin имеет значение по центру).
  • Задаем свойству transition значение transform, чтобы анимация при наведении была плавной.
  • Поскольку у нашего псевдоэлемента абсолютное позиционирование, нам нужен вложенный элемент для текста кнопки. Свойство position: relative на этом вложенном элементе создается новый уровень контекста, поэтому наш текст будет над псевдоэлементом, создающим эффект вытеснения.
  • При наведении мы изменяем свойство трансформации псевдоэлемента scaleX на 1 (свойство transform: scaleX(1)).

Версия без псевдоэлемента

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

Нам достаточно использовать linear-gradient() и background-size.

HTML


<button class="button—wipe">
  Wipe left to right
</button>

<button class="button--wipe button—wipe-vertical">
  Wipe top to bottom
</button>

CSS


/* button--wipe */

.button--wipe {
    --bg-color: thistle;
    --bg-color-hover: darkorange;
    --bg-size: 0% 100%;
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --padding-x: 1.5rem;
    --padding-y: 1rem;
    --wipe-dir: 90deg;
        
    background-color: var(--bg-color);
    background-image: linear-gradient(var(--wipe-dir), var(--bg-color-hover), var(--bg-color-hover));
    background-repeat: no-repeat;
    background-size: var(--bg-size);
    color: black;
    display: inline-block;
    padding: var(--padding-y) var(--padding-x);
    transition: background-size var(--duration) var(--easing);
}

.button--wipe:hover {
    --bg-size: 100% 100%;
    
    background-image: linear-gradient(var(--wipe-dir), var(--bg-color-hover), var(--bg-color-hover));
    background-size: var(--bg-size);
}

/* button--wipe-vertical */

.button--wipe-vertical {
    --bg-size: 100% 0%;
    --wipe-dir: 180deg;
}

  • Для состояния по умолчанию элемента <button> определяем свойство background-color, а также настраиваем linear-gradient через свойство background-image. Но у background-size будет значение 0, поэтому по умолчанию мы ничего не увидим.
  • При наведении курсора свойство background-size получает значение 100% 100% (плавное заполнение обеспечивает свойство transition), что и дает нужный эффект!

Следует помнить, что linear-gradient() использует свойство background-image, и оно заменяет свойство background-color, так что именно это имеет приоритет при наведении.

Вот и все. Вложенный элемент не требуется. Хотите эффект в вертикальном направлении? Просто измените направление linear-gradient и значение background-size. Мы изменили их с помощью пользовательских свойств CSS.

Плитки с цветным наложением

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

Версия с псевдоэлементом

Некоторые из тех же приемов действуют в версии с псевдоэлементом:

HTML


<div class="tle">
    <div class="tle__inner">
        <strong>This uses a pseudo-element…sad!</strong>
    </div>
</div>

CSS


.tle {
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --overlay: rgb(250 255 0 / 0.675);
    --overlay-hover: rgb(0 191 255 / 0.675);
    --duration: 150ms;
    
    /* photo: https://unsplash.com/photos/Gk7enstVtGE */
    background-image: url(https://marcel.imarc.host/900x540.jpg);
    background-repeat: no-repeat;
    padding-top: 60%;
    position: relative;
    transition: background-color var(--duration) var(--easing);
}

.tle::before {
    background-color: var(--overlay);
    content: '';
    height: 100%;
    left: 0;
    position: absolute;
    top: 0;
    transition: background-color var(--duration) var(--easing);
    width: 100%;
}

.tle__inner {
    align-items: flex-end;
    display: flex;
    height: 100%;
    left: 0;
    padding: 1rem;
    position: absolute;
    top: 0;
    width: 100%;
}

.tle:hover::before {
    background-color: var(--overlay-hover);
}

  • Устанавливая для плитки свойство padding-top в значение 60%, мы задаем нужное соотношение сторон (5:3).
  • Мы должны расположить наш псевдоэлемент для наложения цвета, задав ему 100% по ширине и высоте, чтобы заполнить всю плитку. Мы устанавливаем, что при наведении курсора этот псевдоэлемент будет изменять свой background-color.
  • Из-за того, что у псевдоэлемента абсолютное позиционирование, мы должны использовать вложенный элемент для текстового содержимого, также задав ему position: absolute, чтобы он отображался над наложенным цветом и там, где он должен быть внутри плитки.

Версия без псевдоэлемента

Благодаря свойствам aspect-ratio и background-blend-mode все становится намного проще.

Внимание! Свойство aspect-ratio не работает в Safari 14.x, но будет работать в 15 версии.

Тем не менее, на момент написания этой статьи данное свойство имело более 70% глобальной поддержки.

HTML


<div class="tile">
    <strong>Hover me. No pseudo, baby!</strong>
</div>

CSS


.tile {
    --duration: 300ms;
    --easing: cubic-bezier(0.165, 0.840, 0.440, 1);
    --overlay: rgb(250 255 0 / 0.675);
    --overlay-hover: rgb(0 191 255 / 0.675);
    --duration: 150ms;
    
    align-items: flex-end;
    aspect-ratio: 400 / 240;
    background-blend-mode: overlay;
    background-color: var(--overlay);
    /* photo: https://unsplash.com/photos/Gk7enstVtGE */
    background-image: url(https://marcel.imarc.host/900x540.jpg);
    display: flex;
    padding: 1rem;
    transition: background-color var(--duration) var(--easing);
}

.tile:hover {
    background-color: var(--overlay-hover);
    transition: background-color var(--duration) var(--easing);
}

  • Трюк с отступом заменяется aspect-ratio: 400/240 (здесь мы могли бы использовать любое значение, основанное на соотношении 5:3).
  • Мы используем свойства background-image и background-color в сочетании со свойством background-blend-mode - нужно просто изменить значение background-color элемента плитки при наведении курсора.

background-blend-mode

Свойство background-blend-mode смешивает background-color с background-image элемента. Тот, кто знаком с редактором Photoshop, найдут много общего между свойством background-blend-mode и режимами наложения Photoshop. При этом, в отличие от mix-blend-mode свойство background-blend-mode не создает новый многоуровневый контекст! Так что про ад z-index можно забыть!

Заключение

Фронт-энд разработка увлекательна и быстро развивается. С помощью новых свойств CSS мы можем смахнуть пыль с наших старых методов и придать им другой вид. Это помогает делать более эффективный и простой код. Псевдоэлементы безусловно полезны, но нам вовсе не нужно полностью полагаться на них.