Как добавить CSS-анимацию раскрытия к изображениям
Можно подумать: Ну, это же простая задача! Дополнительный элемент над изображением, который анимируется, и готово
. Всё так, но мы не будем использовать дополнительный элемент или псевдоэлемент. Мы будем работать, используя только элемент <img>
. Ничего больше!
Это может показаться невозможным, потому что, используя только элемент <img>
, мы не можем иметь что-то над ним. Действительно, у нас не будет ничего над ним, но мы его подделаем!
Ниже приведена демонстрация CodePen того, что мы будем изучать вместе.
В демонстрациях используются изображения предоставляемые picsum.photos, которые заблокировали доступ из России. Для просмотра включите прокси, vpn или воспользуйтесь браузером Tor.
Круто, правда? Анимация раскрытия, использующая всего несколько строк кода и ни одного лишнего элемента. Следуйте за мной, и давайте разберём магию, скрывающуюся за кодом.
Исходная конфигурация
Начнём с определения размера изображения:
img {
--s: 200px; /* размер изображения */
width: var(--s);
height: var(--s);
box-sizing: border-box;
}
Пока что ничего сложного. Для простоты мы используем квадратное изображение, но это может быть изображение любого размера. Важно явно задать width
и height
, а не использовать aspect-ratio
или оставить размер изображения по умолчанию. Это обязательно для нашей анимации, и позже мы увидим, почему.
Обратите внимание на использование параметра box-sizing: border-box
, который также важен. Пока он не имеет никакого эффекта, но давайте перейдём к следующему шагу, чтобы понять, зачем он нужен.
Добавление отступов
Что произойдёт, если мы добавим отступ к изображению, для которого мы задали фиксированный размер и использовали box-sizing: border-box
? Давайте попробуем!
Изображение сжимается, как мы можем видеть в приведённой выше демонстрации. Мы добавляем 100px
отступа слева, что оставляет только 100px
пространства для изображения (область содержимого). Это и есть эффект box-sizing: border-box
.
Как объясняет MDN:
border-box
указывает браузеру учитывать любые бордюры и отступы в значениях, которые вы указываете для ширины и высоты элемента. Если вы зададите ширину элемента в 100 пикселей, то в эти 100 пикселей войдут все добавленные вами бордюры и отступы, а поле содержимого уменьшится, чтобы поглотить эту дополнительную ширину.
Теперь представьте ситуацию, когда отступ равен ширине. Да, изображение исчезнет! В демонстрационном примере ниже наведите курсор на изображение и посмотрите результат.
В приведённой выше демонстрации можно отметить два момента. Отступы можно анимировать, что очень здорово, и мы видим важность переменной CSS, которую мы использовали ранее для определения размера изображения. Мы использовали ту же переменную для определения отступов, поэтому нам не нужно повторять одно и то же значение.
Добавляем фоновый цвет
Возьмём предыдущий пример и добавим к нему фон.
Теперь мы начинаем что-то понимать. Фон логически охватывает весь элемент. Мы не видим его под изображением (если только оно не прозрачное), но мы видим его в области отступов.
Наша цель — раскрыть изображение, поэтому давайте начнём с отступов, а затем сделаем их 0
при наведении — противоположность тому, что мы имеем сейчас.
Это ещё не то, к чему мы стремимся, но мы уже ближе! Нам не хватает только одного ингредиента, чтобы сделать нашу "фальшивую" анимацию раскрытия идеальной!
Добавление параметров object-fit
и object-position
Осталось избежать сплющивания изображения, и здесь вступает в действие последний трюк. Мы будем использовать object-fit
со значением cover
.
Как объясняет MDN:
Размер заменяемого содержимого сохраняет соотношение сторон и заполняет все поле содержимого элемента. Если соотношение сторон объекта не совпадает с соотношением сторон его поля, то объект будет обрезан по ширине.
Здесь важны две детали:
Размер заменяемого содержимого сохраняет соотношение сторон
. Это означает, что изображение не уменьшится, а сохранит присущее ему соотношение сторон. Мы использовали квадратное изображение, поэтому оно останется квадратным. Заполняющее все поле содержимого элемента … будет обрезано для соответствия
. Изображение должно заполнить всю область содержимого (область, которую мы уменьшаем, добавляя отступ), но если оно не помещается внутри, мы обрезаем его.
Давайте попробуем это сделать в следующем демонстрационном примере.
Видите? Изображение больше не сжимается! Оно сохраняет своё соотношение внутри области содержимого, пока мы добавляем/убираем отступ.
Итак, мы можем подумать, что эффект не такой, как в начальной демонстрации. Изображение странно движется. Это правда. Поэтому теперь мы обратимся к параметру object-position
. Значение по умолчанию — center
, поэтому изображение все время будет центрировано в области содержимого и будет обрезаться, чтобы поместиться внутри. Вот почему оно движется вместе с анимацией.
Что мы будем делать дальше, предсказать несложно. Мы изменим позицию, чтобы убедиться, что изображение не будет двигаться. Мы добавим отступ слева, поэтому нам следует зафиксировать положение изображения справа с помощью object-position: right
.
Наша анимация раскрытия готова! Нам не понадобилось никакого наложения или дополнительного элемента над изображением. Используя простой цвет фона, а также некоторые трюки с позиционированием изображения, мы получили фантастическую анимацию раскрытия с небольшим количеством простого кода.
Мы можем легко обновить направление, изменив направление padding
и object-position
. Вот первая демонстрация из предыдущих, которая включает в себя четыре направления.
Вот CSS, который мы используем:
img {
--s: 200px; /* размер изображения */
width: var(--s);
height: var(--s);
box-sizing: border-box;
object-fit: cover;
cursor: pointer;
transition: .5s;
}
img.left {
object-position: right;
padding-left: var(--s);
background: #542437;
}
img.right {
object-position: left;
padding-right: var(--s);
background: #8A9B0F;
}
img.top {
object-position: bottom;
padding-top: var(--s);
background: #E94E77;
}
img.bottom {
object-position: top;
padding-bottom: var(--s);
background: #7A6A53;
}
img:hover {
padding: 0;
}
Больше анимаций раскрытия
Мы можем расширить этот трюк, чтобы создать больше вариаций, используя ту же идею. Вместо добавления/удаления отступа с одной стороны, мы можем сделать это для двух сторон или даже для всех сторон.
Если мы проанализируем код приведённой выше демонстрации, то не найдём больших отличий от предыдущей. Все, что мы делаем, это устанавливаем различные конфигурации отступа, чтобы создать больше вариаций для одного и того же эффекта.
img {
--s: 200px; /* размер должен соответствовать внутреннему размеру изображения для "center" и "random" */
width: var(--s);
height: var(--s); /* лучше, чем aspect-ratio в случае, если изображение имеет атрибут height */
box-sizing: border-box;
cursor: pointer;
transition: .5s;
}
img.center {
object-fit: none; /* оно делает магию, это не обычное значение, но оно позволяет сохранить внутренний размер заменяемого содержимого */
padding: calc(var(--s)/2);
background: #88C425;
}
img.vertical {
object-fit: cover; /* мы также можем использовать здесь "none", это даст тот же результат */
padding-block: calc(var(--s)/2);
background: #F07818;
}
img.horizontal {
object-fit: cover;
padding-inline: calc(var(--s)/2);
background: #AB3E5B;
}
/* Вы можете использовать любую комбинацию отступов.
убедитесь, что отступы будут покрывать всю область.
также убедитесь, что правильно задали object-position и object-fit, чтобы создать иллюзию неподвижного изображения.
Возможно, я напишу об этом небольшую статью
*/
img.random {
object-position: top left;
object-fit: none;
padding: 0 var(--s) var(--s) 0;
background: #0E2430;
}
img:hover {
padding: 0;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
grid-template-columns: auto auto;
place-content: center;
gap: 30px;
background: #C6E5D9;
}
Есть одна хитрость в первом и последнем примере, где мы используем object-fit: none
вместо object-fit: cover
. В этих случаях отступ уменьшает ширину и высоту области содержимого до 0
, в то время как во всех остальных случаях мы уменьшаем либо высоту, либо ширину. В таких конфигурациях cover
не будет работать, но none
может справиться с задачей.
MDN утверждает:
Размер заменяемого содержимого не изменяется.
Почему мы просто не используем none
во всех случаях? Мы можем использовать его, и он работает, но у него есть небольшой недостаток. none
будет учитывать внутренний размер изображения, поэтому мы должны установить ширину и высоту CSS равными внутреннему размеру, чтобы трюк сработал. cover
, с другой стороны, сохраняет только соотношение изображения и изменяет его размер в соответствии с областью, поэтому мы можем спокойно определить любой размер CSS для изображений.
Вот сравнение, чтобы мы могли увидеть разницу.
В обоих случаях мы имеем анимацию раскрытия, но при использовании object-fit: none
изображение не изменяется в размере и сохраняет размер по умолчанию (500x500
), что не очень хорошо. object-fit: cover
сохраняет только соотношение и изменяет размер изображения, чтобы оно соответствовало размеру бокса.
Заключение
Я надеюсь, что вам понравился этот небольшой эксперимент с CSS. Используя простые приёмы и несколько строк кода, мы добились классной анимации раскрытия с помощью только элемента <img>
. Мы также сделали множество вариаций, используя ту же структуру кода.
С помощью этого приёма мы можем сделать ещё больше. Напоследок я приведу пример, в котором я превращаю черно-белые изображения в цветные при наведении.
Я предоставляю вам продолжить исследование и попытаться найти более причудливые анимации!