От автора: на праздники мы провели несколько экспериментов, чтобы удалить блочные карточки с нашего сайта и заменить их на сплошной фон. В результате нам понадобились разделители, чтобы разграничить статьи на странице (в противном случае все они бы сливались друг с другом — это было плохо).
TL; DR — объединить :not и :nth-child с псевдо-элементом и свойством, которые определяют количество нужных столбцов.
Настройка
Наш сайт использует React с Emotion (библиотека CSS-in-JS) для стилей. В примерах будет использоваться Emotion, но селекторы могут быть преобразованы в CSS, Sass или любой другой метод, который вы используете для стилизации.
В настоящее время для создания «коллекций» статей мы используем Flexbox или CSS Grid. Наша цель в конечном итоге полностью перейти на CSS Grid, но в то же время мы поддерживаем обе технологии. На этом этапе ни Flexbox, ни CSS Grid не обеспечивают простой способ создания разделителей между контентом, для обеспечения согласованности я сосредоточилась на подходах, которые будут работать как с макетами Flexbox, так и с CSS Grid.
Коллекция статей
Одна из наших коллекций позволяет добавлять бесконечное количество статей и принимает значение количества карточек, которое мы хотим разместить в строке, мы будем использовать ее в качестве примера, потому что это охватывает несколько сценариев. (Это одна из наших старых коллекций, поэтому в настоящее время она использует Flexbox).
Как уже упоминалось, эта коллекция имеет возможность устанавливать различное количество столбцов на разных контрольных точках, используя свойства, которые мы передаем, когда настраиваем коллекцию на маршрутах страниц. Мы настроили ее таким образом, чтобы нам нужно было определить только один компонент, и работало бы в ряде сценариев. По сути, CSS тот же, поэтому необходимость поддерживать и управлять несколькими версиями казалась ненужной. Вместо этого мы используем гибкость CSS-in-JS для вывода CSS, который нам нужен, используя значения из свойства. Это означает, что один и тот же компонент может отображать список карточек с 4, 3 или 2 столбцами.
interface CellProps { numberOfItems: number initialColumns: number // small screens intermediateColumns: number // medium screens finalColumns: number // larger screens }
Например, у вас может быть что-то похожее на следующий пример, где мы используем свойство finalColumns с селекторами псевдо-класса :not и :nth-child, чтобы задать поля карточки. (Если вы используете CSS Grid, то можете просто использовать для этого grip-gap.)
export const GridItem = styled('div')<CellProps>(props => ({ [':not(nth-child(' + finalColumns + 'n))']: { marginRight: 12, }, })
Поскольку эти свойства уже существуют для стилизации в нашей коллекции, это значительно упростит добавление разделителей, потому что у нас уже заложена основа.
Добавление разделителей
Чтобы добавить разделители можно использовать селекторы псевдо-класса :not и :first-child, чтобы разместить разделители в нужном месте и добавить псевдо-элементе ::before для создания фактической линии. (Вы также можете использовать :last-child вместо :first-child).
Внутри компонента это может выглядеть следующим образом:
[':not(:first-child)::before']: { content: `''`, backgroundColor: black, height: '100%', width: 1, position: 'absolute', left: 0, transform: 'translateX(-6px)', },
Комбинируя псевдоселектор :not с псевдоселектором :first-child, мы можем добавить псевдо-элемент к каждому элементу, кроме первого потомка. Это предотвращает появление разделителя слева от первой карточки (как на рисунке ниже).
Если в коллекции только один ряд карточек, это будет весь CSS, который нам понадобится. Однако в этом конкретном примере может быть любое количество строк, поэтому нам нужно удалить разделитель для первой (или последней) карточки каждой строки, а не только для первой (или последней) карточки.
Если у нас есть 6 карт в виде 3 столбцов в 2 рядах, вместо использования :first-child мы можем использовать селектор :nth-child, который будет соответствовать элементам на основе их положения в коллекции. Например, :nth-child(3n) выберет каждую 3-ю карточку (см. изображение ниже).
Селектор псевдо-класса :nth-child(3n) выбирает каждую 3-ю карточку
Передав одно из свойств в селектор :nth-child (например, finalColumns), мы сообщим компоненту, сколько столбцов будет в каждой строке.
'&:not(:nth-child(' + finalColumns + 'n))::after: { content: `''`, backgroundColor: black height: '100%', width: 1, position: 'absolute', right: 0, transform: 'translateX(4px)', }
Если на мгновение мы отложим в сторону псевдо-элемент, это выдаст результат div:not(:nth-child(Xn)) — если мы предположим, что для этого примера у нас есть три столбца, то это будет div:not(:nth-child(3n)). Если бы мы изменили фон каждой соответствующей карточки, это выглядело бы как на рисунке ниже.
Возвращаясь к псевдо-элементу: div:not(:nth-child(Xn))::before, если мы снова предполагаем, что у нас есть 3 карточки в строке. Этот код добавит псевдо-элемент к каждому дочернему элементу, кроме 3-й карточки (которая является последней в столбце).
Шесть карточек выложены в два ряда и три столбца с черными вертикальными разделителями
Это будет работать независимо от того, сколько столбцов вы определили, например, если бы у finalColumnпропа было значение 4, оно было бы 4n , и добавило бы разделитель к каждой карточке, кроме 4-й.
Незаполненные колонки
Если у вас есть несоответствующее количество карточек, которые не идеально заполняют каждую строку и колонку, то, к сожалению, этого будет недостаточно — мы получим ситуацию, подобную изображенной ниже, где у вас есть висячий разделитель, который выглядит неуместно.
5 карточек расположены в два ряда по 3 карты в каждом ряду с разделителем между ними. Рядом с последней карточкой размещается висячий разделитель
Чтобы решить эту проблему, мы можем вернуться к нашему селектору и добавить еще один селектор псевдо-класса :not, чтобы исключить последнего потомка в коллекции (:not(:last-child)).
[':not(:nth-child(' + finalColumns + 'n)):not(:last-child)::after']: { content: `''`, backgroundColor: black height: '100%', width: 1, position: 'absolute', right: 0, transform: 'translateX(4px)', }
Возьмем в качестве примера три столбца, это добавит разделитель к каждой карточке, за исключением 3-й в каждом столбце и последней карточки в коллекции.
Делаем это адаптивным
К сожалению, так как мы определяем, где применяются разделители, основываясь на количестве карточек в каждой строке, если это число изменится в маленьких окнах просмотра, это больше не будет работать.
Поскольку мы уже установили количество карточек в каждой строке на основе контрольных точек, мы можем снова использовать эти свойства для добавления / удаления разделителей по мере необходимости.
В настоящее время мы используем следующий селектор для больших областей просмотра, например, 968 пикселей и выше. Если мы обернем это в контрольную точку min-width, это будет применяться только к большим областям просмотра, меньшие области просмотра не будут затронуты, и псевдо-элемент не будет отображаться.
@media screen and (min-width: 968px) { [':not(:nth-child(' + finalColumns + 'n)):not(:last-child)::after'] ...}
Как правило, я использую к контрольным точкам мобильный подход и добавляю только то, что нужно. Однако, когда вы хотите полностью изменить что-то между контрольными точками, например, добавить что-то новое, что отображается только в определенной контрольной точке (или между точками), подход min-width может стать очень запутанным из-за необходимого количества переопределений.
В этой ситуации я использую определенную точку max-width. Например, если бы я хотела вывести только горизонтальные разделители на маленьких окнах просмотра, то могла бы указать @media screen и (max-width: 400px), а затем добавить в стили псевдо-элемент, ограничив его только для мобильных устройств. Это устраняет необходимость затем сбрасывать псевдо-элемент на none в больших окнах просмотра.
В демонстрационных целях я буду предполагать, что в меньших окнах просмотра строки будут иметь ширину всего 1 столбец, что означает, что нам не понадобятся разделители. Поскольку мы игнорируем самые маленькие области просмотра, мы можем сделать комбинированный медиа-запрос и поместить селектор для псевдо-элемента внутри. Это ограничит отображение псевдо-элемента окнами просмотра в диапазоне 400px и 967px.
@media screen and (min-width: 400px and max-width: 967px) { '&:not(:nth-child(' + intermediateColumns + 'n)) :not(:last-child)::after': { content: `''`, backgroundColor: black, height: '100%', width: 1, position: 'absolute', right: 0, transform: 'translateX(4px)', }}
В целях согласованности вы можете вынести все стили разделителя в переменную (или применить миксин, если вы используете Sass), а затем импортировать стили в компонент. Например:
export const divider: CSSObject = { content: `''`, backgroundColor: black, height: '100%', width: 1, position: 'absolute', right: 0, transform: 'translateX(4px)', }export const GridItem = styled('div')<CellProps>(props => ({ ['@media screen and (min-width: 400px and max-width: 967px)']: { '&:not(:nth-child(' + intermediateColumns + 'n)) :not(:last-child)::after': { ...divider } }, ['media screen and (min-width: 968px)']: { ['&:not(:nth-child(' + finalColumns + 'n)) :not(:last-child)::after']: { ...divider } }))
Неважно, как вы пишете CSS, будь то CSS-in-JS, препроцессор, post-css или просто потрясающий CSS. Есть много полезных селекторов, и объединение их позволяет быть достаточно конкретными. В результате важно знать, какие селекторы доступны и как их применять! Селекторы псевдо-классов являются одними из моих любимых аспектов CSS, и часто незаслуженно упускаются из виду разработчиками. Используйте большинство из них, они потрясающи.
Существует много разных способов применения разделителей к макетам, и вы можете использовать много разных подходов, это только один из вариантов. Это может подходить вам или нет, но это дает отправную точку, чтобы придумать собственные решения и эксперименты! Развлекайтесь!
Автор: Mandy Michael
Источник: https://medium.com
Редакция: Команда webformyself.