Хорошо, так что в последний раз, когда мы регистрировалисьмы использовали CSS Grid и комбинировали их с CSS clip-path
и mask
методы создания сеток причудливых форм.
Вот лишь одна из фантастических сеток, которые мы сделали вместе:
Готовы ко второму туру? Мы все еще работаем с CSS Grid, clip-path
и mask
, но к концу этой статьи мы получим различные способы расположения изображений в сетке, включая некоторые эффекты наведения, которые создают аутентичный интерактивный опыт просмотра изображений.
И угадай что? Мы используем та же разметка, которую мы использовали в прошлый раз. Вот это еще раз:
<div class="gallery">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<!-- as many times as we want -->
</div>
Как и в предыдущей статье, нам нужен только контейнер с изображениями внутри. Больше ничего!
Вложенная сетка изображений
В прошлый раз наши сетки были типичными сетками изображений. Если не считать аккуратных форм, которыми мы их замаскировали, это были довольно стандартные симметричные сетки с точки зрения расположения изображений внутри.
Давайте попробуем вложить изображение в центр сетки:
Начнем с установки сетки 2✕2 для четырех изображений:
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gap between images */
display: grid;
gap: var(--g);
grid-template-columns: repeat(2, auto);
}
.gallery > img {
width: var(--s);
aspect-ratio: 1;
object-fit: cover;
}
Пока ничего сложного. Следующий шаг — обрезать угол нашего изображения, чтобы освободить место для вложенного изображения. У меня уже есть подробная статья как срезать углы с помощью clip-path
и mask
. Вы также можете использовать мой онлайн-генератор чтобы получить CSS для маскировки углов.
Здесь нам нужно вырезать углы под углом, равным 90deg
. Мы можем использовать то же самое техника конического градиента из этой статьи, чтобы сделать это:
.gallery > img {
mask: conic-gradient(from var(--_a), #0000 90deg, #000 0);
}
.gallery > img:nth-child(1) { --_a: 90deg; }
.gallery > img:nth-child(2) { --_a: 180deg; }
.gallery > img:nth-child(3) { --_a: 0deg; }
.gallery > img:nth-child(4) { --_a:-90deg; }
Мы могли бы использовать clip-path
метод срезания углов из той же статьи, но здесь больше подойдет маскирование градиентами, поскольку у нас для всех изображений одинаковая конфигурация — все, что нам нужно, это поворот (определяемый переменной --_a
) получить эффект, поэтому мы маскируем изнутри, а не снаружи.
Теперь мы можем разместить вложенное изображение внутри замаскированного пространства. Во-первых, давайте удостоверимся, что у нас есть пятый элемент изображения в HTML:
<div class="gallery">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
</div>
Чтобы разместить его здесь, мы будем полагаться на старое доброе абсолютное позиционирование:
.gallery > img:nth-child(5) {
position: absolute;
inset: calc(50% - .5*var(--s));
clip-path: inset(calc(var(--g) / 4));
}
Ассоциация inset
Свойство позволяет нам разместить изображение в центре, используя одно объявление. Мы знаем размер изображения (определяется переменной --s
), и мы знаем, что размер контейнера равен 100%. Мы делаем некоторые математические расчеты, и расстояние от каждого края должно быть равно (100% - var(--s))/2
.
Вам может быть интересно, почему мы используем clip-path
вообще здесь. Мы используем его с вложенным изображением, чтобы обеспечить постоянный зазор. Если бы мы удалили его, вы бы заметили, что у нас не одинаковый разрыв между всеми изображениями. Таким образом, мы немного вырезаем пятое изображение, чтобы обеспечить правильное расстояние вокруг него.
Полный код еще раз:
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gap between images */
display: grid;
gap: var(--g);
grid-template-columns: repeat(2, auto);
position: relative;
}
.gallery > img {
width: var(--s);
aspect-ratio: 1;
object-fit: cover;
mask: conic-gradient(from var(--_a), #0000 90deg, #000 0);
}
.gallery > img:nth-child(1) {--_a: 90deg}
.gallery > img:nth-child(2) {--_a:180deg}
.gallery > img:nth-child(3) {--_a: 0deg}
.gallery > img:nth-child(4) {--_a:-90deg}
.gallery > img:nth-child(5) {
position: absolute;
inset: calc(50% - .5*var(--s));
clip-path: inset(calc(var(--g) / 4));
}
Теперь многие из вас могут задаться вопросом: зачем все эти сложные вещи, когда мы можем разместить последнее изображение сверху и добавить к нему рамку? Это скрыло бы изображения под вложенным изображением без маски, верно?
Это правда, и мы получим следующее:
Нет mask
, Не clip-path
. Да, код легко понять, но есть небольшой недостаток: цвет рамки должен совпадать с цветом основного фона, чтобы иллюзия была идеальной. Этого небольшого недостатка мне достаточно, чтобы усложнить код в обмен на реальную прозрачность, независимую от фона. Я не говорю, что пограничный подход плох или неправилен. Я бы рекомендовал это в большинстве случаев, когда известна предыстория. Но мы здесь для того, чтобы исследовать что-то новое и, что самое важное, создавать компоненты, которые не зависят от окружающей среды.
На этот раз попробуем другую форму:
На этот раз мы сделали вложенное изображение кругом, а не квадратом. Это простая задача с border-radius
Но нам нужно использовать круглый вырез для других изображений. Однако на этот раз мы будем полагаться на radial-gradient()
вместо conic-gradient()
чтобы получить этот красивый округлый вид.
.gallery > img {
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2), #000 calc(51% + var(--g)/2));
}
.gallery > img:nth-child(1) { --_a: calc(100% + var(--g)/2) calc(100% + var(--g)/2); }
.gallery > img:nth-child(2) { --_a: calc(0% - var(--g)/2) calc(100% + var(--g)/2); }
.gallery > img:nth-child(3) { --_a: calc(100% + var(--g)/2) calc(0% - var(--g)/2); }
.gallery > img:nth-child(4) { --_a: calc(0% - var(--g)/2) calc(0% - var(--g)/2); }
Все изображения используют ту же конфигурацию, что и предыдущий пример, но мы каждый раз обновляем центральную точку.
На рисунке выше показана центральная точка каждого круга. Тем не менее, в реальном коде вы заметите, что я также учитываю зазор, чтобы гарантировать, что все точки находятся в одном и том же положении (центр сетки), чтобы получить непрерывный круг, если мы их объединим.
Теперь, когда у нас есть макет, давайте поговорим об эффекте наведения. Если вы не заметили, крутой эффект при наведении увеличивает размер вложенного изображения и соответствующим образом настраивает все остальное. Увеличение размера — относительно простая задача, но обновить градиент сложнее, поскольку по умолчанию градиенты нельзя анимировать. Чтобы преодолеть это, я буду использовать font-size
взломать, чтобы иметь возможность анимировать радиальный градиент.
Если вы проверите код градиента, вы увидите, что я добавляю 1em
:
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em));
Известно, что em
единицы измерения относятся к родительскому элементу font-size
, поэтому изменение font-size
.gallery
также изменит вычисленное em
значение — это трюк, который мы используем. Мы анимируем font-size
от значения 0
заданному значению, и в результате градиент анимируется, увеличивая вырезанную часть в соответствии с размером вложенного изображения, которое становится больше.
Вот код, который выделяет части, участвующие в эффекте наведения:
.gallery {
--s: 200px; /* controls the image size */
--g: 10px; /* controls the gaps between images */
font-size: 0; /* initially we have 1em = 0 */
transition: .5s;
}
/* we increase the cut-out by 1em */
.gallery > img {
mask:
radial-gradient(farthest-side at var(--_a),
#0000 calc(50% + var(--g)/2 + 1em), #000 calc(51% + var(--g)/2 + 1em));
}
/* we increase the size by 2em */
.gallery > img:nth-child(5) {
width: calc(var(--s) + 2em);
}
/* on hover 1em = S/5 */
.gallery:hover {
font-size: calc(var(--s) / 5);
}
Ассоциация font-size
трюк полезен, если мы хотим анимировать градиенты или другие свойства, которые нельзя анимировать. Пользовательские свойства, определенные с помощью @property, могут решить такую проблему, но поддержка для этого на момент написания все еще отсутствует.
Я обнаружил font-size
трюк от @SelenIT2 пытаясь решить вызов в Твиттере.
Другая форма? Пойдем!
На этот раз мы обрезали вложенное изображение в форме ромба. Я позволю вам проанализировать код в качестве упражнения, чтобы понять, как мы сюда попали. Вы заметите, что структура такая же, как в наших примерах. Единственные различия заключаются в том, как мы используем градиент для создания формы. Копайте и учитесь!
Круглая сетка изображений
Мы можем объединить то, что мы узнали здесь и в предыдущих статьях, чтобы создать еще более интересную сетку изображений. На этот раз давайте сделаем все изображения в нашей сетке круглыми и при наведении курсора расширим изображение, чтобы показать все изображение целиком, поскольку оно покрывает остальную часть фотографий.
В структуре сетки HTML и CSS нет ничего нового, поэтому давайте пропустим эту часть и вместо этого сосредоточимся на желаемой круглой форме и эффекте наведения.
Мы собираемся использовать clip-path
и ее circle()
функция — как вы уже догадались! — вырежьте из изображений круг.
Этот рисунок иллюстрирует clip-path
используется для первого изображения. Слева показано исходное состояние изображения, а справа — состояние при наведении. Вы можете использовать этот онлайн-инструмент играть и визуализировать clip-path
значения.
Для других изображений мы можем обновить центр круга (70% 70%
), чтобы получить следующий код:
.gallery > img:hover {
--_c: 50%; /* same as "50% at 50% 50%" */
}
.gallery > img:nth-child(1) {
clip-path: circle(var(--_c, 55% at 70% 70%));
}
.gallery > img:nth-child(2) {
clip-path: circle(var(--_c, 55% at 30% 70%));
}
.gallery > img:nth-child(3) {
clip-path: circle(var(--_c, 55% at 70% 30%));
}
.gallery > img:nth-child(4) {
clip-path: circle(var(--_c, 55% at 30% 30%));
}
Обратите внимание, как мы определяем clip-path
значения как запасной вариант внутри var()
. Этот способ позволяет нам более легко обновлять значение при наведении, устанавливая значение --_c
переменная. Когда используешь circle()
, положение центральной точки по умолчанию 50% 50%
, поэтому мы можем опустить это для более краткого кода. Вот почему вы видите, что мы только устанавливаем 50%
вместо 50% at 50% 50%
.
Затем мы увеличиваем размер нашего изображения при наведении курсора мыши до общего размера сетки, чтобы мы могли покрыть другие изображения. Мы также обеспечиваем z-index
имеет более высокое значение на изображении при наведении, поэтому оно находится вверху в нашем списке. контекст стекирования.
.gallery {
--s: 200px; /* controls the image size */
--g: 8px; /* controls the gap between images */
display: grid;
grid: auto-flow var(--s) / repeat(2, var(--s));
gap: var(--g);
}
.gallery > img {
width: 100%;
aspect-ratio: 1;
cursor: pointer;
z-index: 0;
transition: .25s, z-index 0s .25s;
}
.gallery > img:hover {
--_c: 50%; /* change the center point on hover */
width: calc(200% + var(--g));
z-index: 1;
transition: .4s, z-index 0s;
}
.gallery > img:nth-child(1){
clip-path: circle(var(--_c, 55% at 70% 70%));
place-self: start;
}
.gallery > img:nth-child(2){
clip-path: circle(var(--_c, 55% at 30% 70%));
place-self: start end;
}
.gallery > img:nth-child(3){
clip-path: circle(var(--_c, 55% at 70% 30%));
place-self: end start;
}
.gallery > img:nth-child(4){
clip-path: circle(var(--_c, 55% at 30% 30%));
place-self: end;
}
Что происходит с
place-self
свойство? Зачем нам это нужно и почему каждое изображение имеет определенную ценность?
Помните проблему, с которой мы столкнулись в предыдущей статье, когда создание сетки из кусочков головоломки? Мы увеличили размер изображений, чтобы создать переполнение, но у некоторых изображений переполнение было неправильным. Мы исправили их с помощью place-self
имущество.
Та же проблема здесь. Мы увеличиваем размер изображений, чтобы каждое из них выходило за пределы ячеек сетки. Но если мы ничего не сделаем, все они переполнятся в правой и нижней части сетки. Что нам нужно:
- первое изображение, выходящее за правый нижний край (поведение по умолчанию),
- второе изображение выходит за нижний левый край,
- третье изображение, которое выходит за верхний правый край, и
- четвертое изображение выходит за верхний левый край.
Для этого нам нужно правильно разместить каждое изображение, используя place-self
имущество.
Если вы не знакомы с place-self
, это сокращение от justify-self
и align-self
разместить элемент горизонтально и вертикально. Если он принимает одно значение, оба выравнивания используют одно и то же значение.
Расширение панелей изображений
В предыдущей статьеЯ создал классный эффект масштабирования, который применяется к сетке изображений, где мы можем контролировать все: количество строк, количество столбцов, размеры, масштабный коэффициент и т. д.
Особым случаем стали классические расширяющиеся панели, где у нас есть только один ряд и контейнер во всю ширину.
Мы возьмем этот пример и объединим его с фигурами!
Прежде чем мы продолжим, я настоятельно рекомендую прочитать мою другая статья чтобы понять, как работают приемы, которые мы собираемся рассмотреть. Проверьте это, и мы продолжим уделять внимание созданию фигур панели.
Во-первых, давайте начнем с упрощения кода и удаления некоторых переменных.
Нам нужна только одна строка, а количество столбцов должно корректироваться в зависимости от количества изображений. Это означает, что нам больше не нужны переменные для количества строк (--n
) и столбцы (--m
), но нам нужно использовать grid-auto-flow: column
, что позволяет сетке автоматически генерировать столбцы по мере добавления новых изображений. Мы будем считать фиксированную высоту нашего контейнера; по умолчанию он будет полной ширины.
Давайте обрежем изображения наклонной формой:
Еще раз: каждое изображение содержится в своей ячейке сетки, поэтому между изображениями больше места, чем хотелось бы:
Нам нужно увеличить ширину изображений, чтобы создать перекрытие. Мы заменяем min-width:
100%
min-width: calc(100% + var(--s))
, Где --s
— новая переменная, управляющая формой.
Теперь нам нужно исправить первое и последнее изображения, чтобы они как бы выходили за пределы страницы без пробелов. Другими словами, мы можем удалить наклон с левой стороны первого изображения и наклон с правой стороны последнего изображения. Нам нужен новый clip-path
специально для этих двух изображений.
Нам также необходимо исправить переполнение. По умолчанию все изображения будут переполняться с обеих сторон, но для первого нам нужно переполнение с правой стороны, а для последнего изображения — левое.
.gallery > img:first-child {
min-width: calc(100% + var(--s)/2);
place-self: start;
clip-path: polygon(0 0,100% 0,calc(100% - var(--s)) 100%,0 100%);
}
.gallery > img:last-child {
min-width: calc(100% + var(--s)/2);
place-self: end;
clip-path: polygon(var(--s) 0,100% 0,100% 100%,0 100%);
}
Конечным результатом является красивая расширяющаяся панель наклонных изображений!
Мы можем добавить столько изображений, сколько вы захотите, и сетка настроится автоматически. Кроме того, нам нужно контролировать только одно значение, чтобы управлять формой!
Мы могли бы сделать такой же макет с помощью flexbox, поскольку имеем дело с одной строкой элементов. Вот моя реализация.
Конечно, наклонные изображения — это круто, но как насчет зигзагообразного рисунка? Я уже дразнил это на конец последней статьи.
Все, что я здесь делаю, это заменяю clip-path
mask
… И угадайте, что? У меня уже есть подробная статья создавая эту зигзагообразную форму — не говоря уже об онлайне генератор для получения кода. Видите, как все сходится?
Самая сложная часть здесь — убедиться, что зигзаги идеально выровнены, а для этого нам нужно добавить смещение для каждого :nth-child(odd)
элемент изображения.
.gallery > img {
mask:
conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)
100% calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y,
conic-gradient(from 45deg at left, #0000, #000 1deg 89deg, #0000 90deg)
0% calc(50% + var(--_p, 0%))/51% calc(2*var(--s)) repeat-y;
}
/* we add an offset to the odd elements */
.gallery > img:nth-child(odd) {
--_p: var(--s);
}
.gallery > img:first-child {
mask:
conic-gradient(from -135deg at right, #0000, #000 1deg 89deg, #0000 90deg)
0 calc(50% + var(--_p, 0%))/100% calc(2*var(--s));
}
.gallery > img:last-child {
mask:
conic-gradient(from 45deg at left, #0000, #000 1deg 89deg, #0000 90deg)
0 calc(50% + var(--_p, 0%)) /100% calc(2*var(--s));
}
Обратите внимание на использование --_p
переменная, которая вернется к 0%
но будет равен --_s
для странных изображений.
Вот демо, которое иллюстрирует проблему. Наведите указатель мыши, чтобы увидеть, как смещение, определенное --_p
— исправляет расстановку.
Также обратите внимание, как мы используем разные маски для первого и последнего изображения, как в предыдущем примере. Нам нужен только зигзаг на правой стороне первого изображения и левой стороне последнего изображения.
А почему не закругленные бока? Давай сделаем это!
Я знаю, что код может выглядеть пугающим и сложным для понимания, но все, что происходит, — это комбинация различных приемов, которые мы рассмотрели в этой и других статьях, которыми я уже поделился. В этом случае я использую ту же структуру кода, что и для зигзагообразных и наклонных фигур. Сравните его с этими примерами, и вы не найдете разницы! Это те же приемы моя предыдущая статья об эффекте масштабирования. Затем я использую свой другое письмо и мой онлайн-генератор чтобы получить код маски, которая создает эти закругленные формы.
Если вы помните, что мы сделали для зигзага, мы использовали одну и ту же маску для всех изображений, но затем нам пришлось добавить смещение к нечетным изображениям, чтобы создать идеальное перекрытие. В этом случае нам нужна другая маска для изображений с нечетными номерами.
Первая маска:
mask:
linear-gradient(-90deg,#0000 calc(2*var(--s)),#000 0) var(--s),
radial-gradient(var(--s),#000 98%,#0000) 50% / calc(2*var(--s)) calc(1.8*var(--s)) space repeat;
Второй:
mask:
radial-gradient(calc(var(--s) + var(--g)) at calc(var(--s) + var(--g)) 50%,#0000 98% ,#000)
calc(50% - var(--s) - var(--g)) / 100% calc(1.8*var(--s))
Единственное, что я здесь сделал, — это обновил вторую маску, включив в нее переменную разрыва (--g
), чтобы создать пространство между изображениями.
Последний штрих – закрепить первое и последнее изображение. Как и во всех предыдущих примерах, первому изображению нужен прямой левый край, а последнему — прямой правый край.
Для первого изображения мы всегда знаем, какая маска ему нужна, а именно:
.gallery > img:first-child {
mask:
radial-gradient(calc(var(--s) + var(--g)) at right, #0000 98%, #000) 50% / 100% calc(1.8 * var(--s));
}
Для последнего изображения это зависит от количества элементов, поэтому имеет значение, является ли этот элемент :nth-child(odd)
or :nth-child(even)
.
.gallery > img:last-child:nth-child(even) {
mask:
linear-gradient(to right,#0000 var(--s),#000 0),
radial-gradient(var(--s),#000 98%,#0000) left / calc(2*var(--s)) calc(1.8*var(--s)) repeat-y
}
.gallery > img:last-child:nth-child(odd) {
mask:
radial-gradient(calc(var(--s) + var(--g)) at left,#0000 98%,#000) 50% / 100% calc(1.8*var(--s))
}
Вот и все! Три разных макета, но каждый раз одни и те же приемы CSS:
- структура кода для создания эффекта масштабирования
- маска или контур для создания фигур
- в некоторых случаях отдельная конфигурация для нечетных элементов, чтобы обеспечить идеальное перекрытие
- определенная конфигурация для первого и последнего изображения, позволяющая сохранять форму только с одной стороны.
А вот большая демонстрация, в которой собраны все они вместе. Все, что вам нужно, это добавить класс, чтобы активировать макет, который вы хотите видеть.
А вот реализация Flexbox
Подведение итогов
Уф, мы закончили! Я знаю, что между этой и предыдущей статьями есть много приемов и примеров CSS, не говоря уже обо всех других приемах, на которые я ссылался здесь из других написанных мною статей. Мне потребовалось время, чтобы собрать все воедино, да и не обязательно понимать все сразу. Одно прочтение даст вам хороший обзор всех макетов, но, возможно, вам придется прочитать статью несколько раз и сосредоточиться на каждом примере, чтобы понять все хитрости.
Вы заметили, что мы вообще не коснулись HTML, за исключением, возможно, количества изображений в разметке? Все созданные нами макеты используют один и тот же HTML-код, который представляет собой не что иное, как список изображений.
Прежде чем закончить, я оставлю вам последний пример. Это «против» между двумя аниме-персонажами с крутым эффектом наведения.
А вы? Можете ли вы создать что-то на основе того, что вы узнали? Это не обязательно должно быть сложно — представьте себе что-нибудь крутое или забавное, как я это сделал в том аниме-матче. Это может стать для вас хорошим упражнением, а в конце мы опубликуем отличную подборку в разделе комментариев.