Знакомство с object-fit

Перевод статьи Exploring object-fit с сайта hacks.mozilla.org (лицензия СС3.0), автор — Крис Миллс.

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

Если разрешить соотношению сторон заполнять все пространство элемента-контейнера, то в большинстве случае это будет выглядеть ужасно. А кадрировать и менять размеры динамически «на лету» может оказаться не в ваших силах. (Допустим, если вы работаете с CMS и имеете права только для редактирования содержимого страницы.)

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

Эти элементы неплохо поддерживаются современными браузерами (за исключением IE). В этой статье мы рассмотрим несколько примеров их использования.

Примечание: object-fit работает и с SVG-контентом, но того же эффекта можно достигнуть, если установить атрибут preserveAspectRatio="" в самом SVG.

Как работают object-fit и object-position?

Вы без проблем можете применять object-fit к любому замещаемому элементу, например:

img {
  height: 100px;
  width: 100px;
  object-fit: contain;
}

object-fit может иметь одно из 5 значений:

1.   contain: Размер контента (например, изображения) будет изменен, чтобы отобразить его целиком с сохранением первоначального соотношения сторон, но при этом уместиться в размеры, заданные элементом.

2.   fill: Размер контента будет увеличен, чтобы заполнить размеры, заданные элементом, даже если при этом будет нарушено первоначальное соотношение сторон.

3.   cover: Сохранение соотношения сторон контента, но изменение его ширины и высоты. Таким образом, контент полностью покрывает элемент. Меньшая из двух сторон изменяется до размеров контейнера, а большая – выходит за рамки элемента и обрезается.

4.   none: Полностью игнорирует высоту или ширину, заданные контейнером, и просто использует оригинальный размер контента замещаемого элемента.

5.   scale-down: Размер контента задается либо как при указании none, либо как при contain, смотря что из этого даст меньший размер замещаемого элемента в итоге.

object-position действует так же, как background-position при работе с фоновыми изображениями. Например:

img {
  height: 100px;
  width: 100px;
  object-fit: contain;
  object-position: top 70px;
}

Значения, выраженные в процентах, тоже можно использовать, но они рассчитываются относительно избытка доступного пространства – разницы между шириной элемента и итоговой отрисованной шириной замещаемого контента. Поэтому object-position: 50% 50% (значение, используемое по умолчанию) всегда будет помещать замещаемый элемент точно в центре. Кроме того, object-position: 0% 0% всегда будет означать выравнивание по левому верхнему углуobject-position: 100% 100% *всегда* означает выравнивание по нижнему правому углу, и т.д.

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

Результат использования различных значений object-fit

Следующие примеры кода демонстрируют эффекты от использования различных значений object-fit.

Использование object-fit: contain для размещения изображений с полями (леттербоксинг):

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

Изображения с измененными пропорциями обычно ужасно выглядят, поэтому вместо этого вы можете добавить к изображению поля, с помощью параметра object-fit: contain (пример использования object-fit: contain):

img {
  width: 480px;
  height: 320px;
  background: black;
}
 
.contain {
	object-fit: contain;
}

Кадрирование изображений с помощью object-fit:cover

Еще одним решением, позволяющим сохранить соотношение сторон, будет кадрирование каждого изображения до нужного размера, чтобы оно полностью покрывало элемент <img>, при этом любые выступы будут скрыты. Для этого просто воспользуйтесь object-fit:cover (пример использования object-fit: cover):

.cover {
  object-fit: cover;
}

Изменение соотношения сторон видео с помощью object-fit: fill

С другой стороны, мы также можем взять видео и насильно изменить его соотношение сторон. Возможно, в некоторых видео, присланных вашим редактором контента, задано неправильное соотношение сторон, и вы хотите исправить его на лету, одним движением руки?

Возьмите этот кадр:

video-bad-aspect

Если мы вставим его на страницу, используя этот код:

<video controls="controls" src="windowsill.webm"
    width="426" height="240">
  …
</video>

То выглядеть он будет ужасно: у видео появятся поля, потому что элемент <video> всегда старается сохранить оригинальное соотношение сторон исходного файла. Чтобы исправить это, мы можем использовать object-fit: fill (пример использования object-fit: fill):

.fill {
  object-fit: fill;
}

Это изменит оригинальное соотношение сторон и заставит кадр полностью заполнить элемент <video>, чтобы он отображался корректно.

Интересные эффекты с использованием transition

Комбинирование object-fit и object-position с CSS transition может добавить галерее изображений или видео несколько довольно интересных эффектов. Например:

.none {
  width: 200px;
  height: 200px;
 
  overflow: hidden;
  object-fit: none;
  object-position: 25% 50%;
  transition: 1s width, 1s height;
}
 
.none:hover, .none:focus {
	height: 350px;
	width: 350px;
}

Сначала отображается лишь небольшая часть изображения, а при выборе/наведении курсора на элемент он увеличивается, демонстрируя изображение целиком (пример использования object-fit: none).

Это происходит благодаря установке свойства object-fit: none у элемента <img>. Мы заставляем контент полностью игнорировать заданные ранее ширину и высоту и позволяем выйти за пределы элемента. После этого мы используем overflow: hidden, чтобы обрезать всё, что не поместилось. Для плавного увеличения размера элемента <img> при наведении курсора/выборе используется свойство transition.

Пример галереи

Для демонстрации чуть более практичного использования object-fit мы создали пример галереи:

gallerygallery-full-display

16 изображений загружаются с помощью XHR и вставляются в элементы img как ObjectURL.

for(i = 1; i <= thumbs.length ; i++) {
  var requestObj = 'images/pic' + i + '.jpg';
  retrieveImage(requestObj,i-1);
}
 
function retrieveImage(requestObj,imageNo) {
  var request = new XMLHttpRequest();
  request.open('GET', requestObj, true);
  request.responseType = 'blob';
 
  request.onload = function() {
    var objectURL = URL.createObjectURL(request.response);
    thumbs[imageNo].setAttribute('src',objectURL);
    thumbs[imageNo].onclick = function() {
      ...
    }
  }
 
  request.send();
}

В свою очередь, каждому изображению присваивается обработчик onclick, чтобы по клику они отображались в полном размере, заполняя экран (главному изображению, изначально имеющему CSS-свойство display: none, присваивается класс blowup, который заставляет его отобразиться и заполнить весь экран; после этого атрибут src главного изображения получает URL того объекта, по превью которого кликнул пользователь).

thumbs[imageNo].onclick = function() {
  mainImg.setAttribute('src',objectURL);
  mainImg.className = 'blowup';
  for(i = 0; i < thumbs.length; i++) {
    thumbs[i].className = 'thumb darken';
  }
}

Клик по полноразмерному изображению заставит картинку опять исчезнуть.

mainImg.onclick = function() {
  mainImg.className = 'main';
  for(i = 0; i < thumbs.length; i++) {
    thumbs[i].className = 'thumb';
  }
}

Все размеры устанавливаются в процентах, поэтому сетка сохраняет свои пропорции при любом размере экрана.

body > div {
  height: 25%;
}
 
.thumb {
  float: left;
  width: 25%;
  height: 100%;
  object-fit: cover;
}

Примечание: всем превью был присвоен атрибут tabindex="0", что позволяет переключаться между ними клавишей Tab (чтобы добавить элемент в список перехода по клавише Tab, необходимо присвоить ему tabindex="0"), а обработчик onclick, который позволяет отображать полноразмерное изображение, был продублирован обработчиком onfocus, чтобы обеспечить элементарный доступ с клавиатуры.

thumbs[imageNo].onfocus = function() {
  mainImg.setAttribute('src',objectURL);
  mainImg.className = 'blowup';
  for(i = 0; i < thumbs.length; i++) {
    thumbs[i].className = 'thumb darken';
  }
}

Самое интересное же происходит благодаря object-fit:

  1. У превью установлено свойство object-fit: cover, таким образом все они будут иметь одинаковый размер и правильное соотношение сторон, а лишнее будет обрезано. Выглядит это неплохо и создает интересный эффект при изменении размеров окна.
  2. Основное изображение имеет свойства object-fit: contain и object-position: center, так что оно будет отображаться полностью, с правильным соотношением сторон и с максимально возможным размером.

P.S. Это тоже может быть интересно:

3 Комментарии

  1. Alinaki

    Mozilla не успела выпустить браузер с поддержкой, собственно, object-fit, но статью уже накатала? Прелестно :)

    1. Максим Усачев

      Если верить MDN, то object-fit будет поддерживаться с 36-й версии Firefox, а значит уже не за горами:)

    2. SelenIT

      А если верить календарю релизов Мозиллы, то официальная поддержка появится уже завтра:)

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.