Создавайте адаптивные эффекты изображения с помощью CSS-градиентов и соотношения сторон

Опубликовано: 2022-03-10
Краткий обзор ↬ Классическая проблема в CSS — сохранение соотношения сторон изображений в связанных компонентах, таких как карточки. Недавно поддерживаемое свойство aspect-ratio в сочетании с object-fit обеспечивает решение этой головной боли прошлого! Давайте научимся использовать эти свойства в дополнение к созданию адаптивного эффекта градиентного изображения для дополнительного эффекта.

Чтобы подготовиться к нашим будущим эффектам изображения, мы собираемся настроить компонент карты, который имеет большое изображение вверху, за которым следуют заголовок и описание. Общая проблема с этой настройкой заключается в том, что мы не всегда имеем полный контроль над тем , что представляет собой изображение, и, что более важно для нашего макета, каковы его размеры . И хотя это можно решить, обрезав заранее, мы все еще можем столкнуться с проблемами из-за контейнеров с гибкими размерами. Следствием этого является неравномерное расположение содержимого карты, которое действительно выделяется, когда вы предъявляете ряд карт.

Другим предыдущим решением, помимо обрезки, мог быть переход от встроенного img к пустому div , который существовал только для представления изображения через background-image . Я реализовал это решение много раз в прошлом. Одним из преимуществ этого является использование старого трюка для пропорций, который использует элемент нулевой высоты и устанавливает значение padding-bottom . Установка значения отступа в процентах приводит к конечному вычисляемому значению, которое относится к ширине элемента. Возможно, вы также использовали эту идею, чтобы поддерживать соотношение сторон 16:9 для встраивания видео, и в этом случае значение отступа находится по формуле: 9/16 = 0.5625 * 100% = 56.26% . Но мы собираемся изучить два современных свойства CSS , которые не требуют дополнительной математики, дают нам больше гибкости, а также позволяют сохранить семантику, обеспечиваемую использованием реального img вместо пустого div .

Во-первых, давайте определим семантику HTML, включая использование неупорядоченного списка в качестве контейнера карт:

 <ul class="card-wrapper"> <li class="card"> <img src="" alt=""> <h3>A Super Wonderful Headline</h3> <p>Lorem ipsum sit dolor amit</p> </li> <!-- additional cards --> </ul>

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

 .card { background-color: #fff; border-radius: 0.5rem; box-shadow: 0.05rem 0.1rem 0.3rem -0.03rem rgba(0, 0, 0, 0.45); padding-bottom: 1rem; } .card > :last-child { margin-bottom: 0; } .card h3 { margin-top: 1rem; font-size: 1.25rem; } img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; } img ~ * { margin-left: 1rem; margin-right: 1rem; }

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

И наш прогресс на данный момент приводит нас к следующему виду карты:

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

Наконец, мы создадим стили .card-wrapper для быстрого адаптивного макета с использованием сетки CSS. Это также удалит стили списка по умолчанию.

 .card-wrapper { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(30ch, 1fr)); grid-gap: 1.5rem; }

Примечание . Если этот метод сетки вам незнаком, просмотрите объяснение в моем руководстве о современных решениях для сетки из 12 столбцов.

С этим применением и со всеми карточками, содержащими изображение с действительным исходным путем, наши .card-wrapper дают нам следующий макет:

Три карточки отображаются подряд из-за примененных стилей макета обложки карточек. Каждая карта имеет уникальное изображение с разным естественным соотношением сторон, причем последняя карта имеет вертикально ориентированное изображение, которое более чем в два раза превышает высоту других изображений карт.
Три карточки отображаются подряд из-за примененных стилей макета обложки карточек. Каждая карта имеет уникальное изображение с различными естественными пропорциями, причем последняя карта имеет вертикально ориентированное изображение, которое более чем в два раза превышает высоту других изображений карт. (Большой превью)

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

Еще после прыжка! Продолжить чтение ниже ↓

Включить унифицированные размеры изображений с object-fit

Как отмечалось ранее, вы, возможно, ранее внесли обновление в этот сценарий, чтобы изменить изображения, которые будут добавляться с помощью background-image вместо этого, и использовать background-size: cover для корректного изменения размера изображения. Или вы, возможно, пытались принудительно обрезать кадрирование заранее (все равно достойная цель, поскольку любое уменьшение размера изображения улучшит производительность!).

Теперь у нас есть доступное свойство object-fit , которое позволяет тегу img выступать в качестве контейнера для изображения. Кроме того, оно имеет значение cover , которое приводит к такому же эффекту, как и решение с фоновым изображением, но с дополнительным преимуществом сохранения семантики встроенного изображения. Давайте применим его и посмотрим, как это работает.

Нам нужно соединить его с размером height , чтобы получить дополнительное представление о том, как мы хотим, чтобы контейнер изображения вел себя (напомним, что мы уже добавили width: 100% ). И мы собираемся использовать функцию max() для выбора либо 10rem либо 30vh в зависимости от того, что больше в данном контексте, что предотвращает слишком сильное уменьшение высоты изображения на меньших окнах просмотра или когда пользователь установил большое масштабирование.

 img { /* ...existing styles */ object-fit: cover; height: max(10rem, 30vh); }

Дополнительный совет по специальным возможностям : всегда следует тестировать макеты с масштабом 200 % и 400 % на рабочем столе. Хотя в настоящее время не существует медиа-запроса zoom , такие функции, как max() , могут помочь решить проблемы с макетом. Еще один контекст, в котором этот метод полезен, — это расстояние между элементами.

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

Изображения с тремя картами теперь имеют одинаковую высоту, а содержимое изображения центрировано внутри изображения, как если бы оно было контейнером.
Теперь изображения с тремя картами имеют одинаковую высоту, а содержимое изображения центрировано внутри изображения, как если бы оно было контейнером. (Большой превью)

Отзывчиво согласованный размер изображения с aspect-ratio

При использовании object-fit сам по себе один недостаток заключается в том, что нам все равно нужно установить некоторые подсказки размеров.

Предстоящее свойство (в настоящее время доступное в браузерах Chromium), называемое aspect-ratio , расширит наши возможности по постоянному размеру изображений.

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

Вот наше полное обновленное правило изображения:

 img { border-radius: 0.5rem 0.5rem 0 0; width: 100%; object-fit: cover; aspect-ratio: 4/3; }

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

Вот обновленные карты, хотя, вероятно, будет трудно заметить визуальную разницу в этом конкретном случае, поскольку соотношение сторон близко соответствует внешнему виду, которого мы достигли, установив height только для object-fit .

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

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

Добавление адаптивных эффектов с помощью градиентов и функций CSS

Хорошо, теперь, когда мы знаем, как настроить изображения одинакового размера, давайте повеселимся с ними, добавив эффект градиента!

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

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

 .card { --card-gradient: #5E9AD9, #E271AD; background-image: linear-gradient( var(--card-gradient), white max(9.5rem, 27vh) ); /* ...existing styles */ }

Но подождите — это функция CSS max() ? В градиенте? Да, это возможно, и это волшебство делает этот градиент эффективным и отзывчивым!

Однако, если бы я добавил скриншот, мы бы еще не увидели, что градиент оказывает какое-либо влияние на изображение. Для этого нам нужно ввести свойство mix-blend-mode , и в этом сценарии мы будем использовать значение overlay :

 img { /* ...existing styles */ mix-blend-mode: overlay; }

Свойство mix-blend-mode похоже на применение стилей наложения слоев, доступных в программном обеспечении для обработки фотографий, таком как Photoshop. А значение overlay позволяет средним тонам изображения смешиваться с градиентом позади него, что приводит к следующему результату:

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

Теперь, на данный момент, мы полагаемся только на значение aspect-ratio для изменения размера изображения. И если мы изменим размер контейнера и заставим макет карты переформатироваться, изменение высоты изображения вызовет несоответствия в том, где градиент становится белым.

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

 img { /* ...existing styles */ max-height: max(10rem, 30vh); }

Важно отметить, что добавление «max-height» меняет поведение «соотношения сторон». Вместо того, чтобы всегда использовать точное соотношение, оно будет использоваться только тогда, когда будет достаточно выделенного места с учетом нового дополнительного ограничения «max-height».

Тем не менее, aspect-ratio прежнему будет обеспечивать постоянное изменение размера изображений, как это было преимуществом по сравнению только с object-fit . Попробуйте закомментировать aspect-ratio в финальной демонстрации CodePen, чтобы увидеть разницу между размерами контейнеров.

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

Альтернативный вариант: mix-blend-mode наложения и добавление фильтра

Использование overlay в качестве значения mix-blend-mode было лучшим выбором для эффекта плавного перехода к белому, который мы искали, но давайте попробуем альтернативный вариант для более драматичного эффекта.

Мы собираемся обновить наше решение, чтобы добавить пользовательское свойство CSS для значения mix-blend-mode а также обновить значения цвета для градиента:

 .card { --card-gradient: tomato, orange; --card-blend-mode: multiply; } img { /* ...existing styles */ mix-blend-mode: var(--card-blend-mode); }

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

Каждое изображение карты имеет сильный оранжевый оттенок из-за нового градиента, который начинается от красно-оранжевого до чистого оранжевого. Белые области остаются белыми, а черные области остаются черными.
Каждое изображение карты имеет сильный оранжевый оттенок из-за нового градиента, который начинается от красно-оранжевого до чистого оранжевого. Белые области остаются белыми, а черные области остаются черными. (Большой превью)

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

Одна дополнительная модификация , которую мы можем добавить, — это использование filter и, в частности, использование функции grayscale() для удаления цветов изображения и, следовательно, сделать градиент единственным источником окраски изображения.

 img { /* ...existing styles */ filter: grayscale(100); }

Использование значения grayscale(100) приводит к полному удалению естественных цветов изображения и преобразованию его в черно-белое. Вот обновление для сравнения с предыдущим снимком экрана его эффекта при использовании нашего оранжевого градиента с multiply :

Теперь каждое изображение карты по-прежнему имеет оранжевый градиент, но все остальные цвета удалены и заменены оттенками серого.
Теперь каждое изображение карты по-прежнему имеет оранжевый градиент, но все остальные цвета удалены и заменены оттенками серого. (Большой превью)

Используйте aspect-ratio как прогрессивное улучшение

Как упоминалось ранее, в настоящее время aspect-ratio поддерживается только в последних версиях браузеров Chromium (Chrome и Edge). Тем не менее, все браузеры поддерживают object-fit и это вместе с нашими ограничениями по height приводит к менее идеальному, но все же приемлемому результату, как показано здесь для Safari:

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

Без функции aspect-ratio результатом здесь является то, что в конечном итоге высота изображения ограничена, но естественные размеры каждого изображения по-прежнему приводят к некоторому расхождению между высотами изображений карточек. Вместо этого вы можете добавить max-height или снова использовать функцию max() , чтобы сделать max-height более чувствительным к разным размерам карточек.

Расширение эффектов градиента

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

Во-первых, мы обновим каждую карточку h3 , чтобы она содержала ссылку, например:

 <h3><a href="">A Super Wonderful Headline</a></h3>

Затем мы можем использовать один из наших новейших доступных селекторов — :focus-within — для изменения градиента карты, когда ссылка находится в фокусе. Для дополнительного охвата возможных взаимодействий мы соединим это с :hover . И мы будем повторно использовать нашу идею max() , чтобы назначить один цвет, чтобы взять на себя покрытие части изображения карты. Недостатком этого конкретного эффекта является то, что остановка градиента и изменение цвета не могут быть надежно анимированы, но скоро они будут реализованы благодаря CSS Houdini.

Чтобы обновить цвет и добавить новую цветовую точку, нам просто нужно переназначить значение --card-gradient в этом новом правиле:

 .card:focus-within, .card:hover { --card-gradient: #24a9d5 max(8.5rem, 20vh); }

Наши значения max() меньше, чем исходные значения, используемые для white , чтобы сохранить размытый край. Если бы мы использовали те же значения, он бы совпал с white и создал бы четкое разделение по линейке.

При создании этой демонстрации я первоначально попробовал эффект, который использовал transform с scale для эффекта увеличения. Но я обнаружил, что из mix-blend-mode и наложения браузер не всегда перерисовывал изображение, что вызывало неприятное мерцание. Всегда будут компромиссы при запросе от браузера выполнения эффектов и анимации только с помощью CSS, и хотя это очень здорово, что мы можем сделать, всегда лучше проверить влияние ваших эффектов на производительность .

Получайте удовольствие от экспериментов!

Современный CSS дал нам несколько замечательных инструментов для обновления наших наборов инструментов для веб-дизайна, причем aspect-ratio является последним дополнением. Так что идите вперед и экспериментируйте с object-fit , aspect-ratio и добавляйте функции, такие как max() , в свои градиенты для получения забавных адаптивных эффектов! Просто не забудьте дважды проверить все в разных браузерах (пока!) и в разных окнах просмотра и размерах контейнеров.

Вот CodePen, включая функции и эффекты, которые мы рассмотрели сегодня:

См. Pen [Эффекты адаптивного изображения с градиентами CSS и соотношением сторон] (https://codepen.io/smashingmag/pen/WNoERXo) Стефани Эклс.

См. «Эффекты изображения, реагирующего на перо, с CSS-градиентами и соотношением сторон» Стефани Эклз.

Ищете больше? Обязательно ознакомьтесь с нашим руководством по CSS здесь, на Smashing →