Полноэкранный адаптивный фон средствами CSS

Фоновое изображение на весь экран делают четыре свойства: background-image, background-position, background-repeat и background-size: cover. Скопируйте код ниже, замените путь к картинке — и фон закроет блок на любом разрешении без JavaScript. Дальше разберём, как работает cover, что делать с мобильными, весом файла и читаемостью текста.

Сергей Бензенкоавтор материала

Минимальный рабочий код

.hero {
  /* Фон не короче экрана, на мобильных с адресной строкой — 100dvh */
  min-height: 100vh;
  min-height: 100dvh;

  /* Цвет виден, пока картинка грузится */
  background-color: #464646;
  background-image: url(images/background-photo.jpg);
  background-position: center center;
  background-repeat: no-repeat;

  /* Масштабирует картинку так, чтобы она закрыла весь блок */
  background-size: cover;
}

То же одной строкой. Размер в шорткате пишется через слэш сразу после позиции:

/* Работает */
.hero { background: url(photo.jpg) center center / cover no-repeat; }

/* Не работает: браузер отбросит всё объявление */
.hero { background: url(photo.jpg) center center cover no-repeat; }

Слэш обязателен. По спецификации значение background-size допустимо в шорткате только сразу после background-position и через /. Во втором примере cover стоит без слэша, запись невалидна, и браузер отбрасывает её целиком. Поэтому фон пропадает совсем, а не просто без масштабирования. Это самая частая причина, когда фон «не показывается».

Как работает background-size: cover

Значение cover масштабирует картинку пропорционально, пока она не закроет весь блок. Лишнее обрезается по краям, искажений нет. Значение contain вписывает картинку целиком, поэтому по бокам или сверху и снизу остаются пустые поля. Запись 100% 100% растягивает картинку под размер блока и ломает пропорции.

Разницу видно на одном фото в двух одинаковых контейнерах. Вставьте код на страницу и сравните:

<div class="box box-cover"></div>
<div class="box box-contain"></div>

.box {
  width: 320px;
  height: 200px;
  background-color: #464646;
  background-image: url(photo.jpg);
  background-position: center center;
  background-repeat: no-repeat;
}
.box-cover   { background-size: cover; }   /* Кадр закрыт, края обрезаны */
.box-contain { background-size: contain; } /* Фото целиком, по бокам поля */

Свойство background-position задаёт точку привязки: какая часть картинки остаётся на месте при обрезке. Значение center center оставляет в кадре середину. Если на фото главное сверху, ставьте center top.

СвойствоЗначениеЧто делает
background-imageurl(...)Задаёт картинку
background-sizecoverЗакрывает блок целиком, лишнее обрезает
background-sizecontainВписывает картинку целиком, остаются поля
background-positioncenter centerВыбирает точку привязки картинки
background-repeatno-repeatЗапрещает повтор плиткой
background-attachmentfixedПривязывает фон к области просмотра
background-color#464646Заливка под картинкой и до её загрузки

Если картинка меньше блока, cover увеличивает её, и появляется пикселизация. Берите исходник не уже, чем экран в физических пикселях: на экране с плотностью 2x нужна картинка вдвое шире, чем ширина окна в CSS-пикселях.

Ориентиры по ширине исходника такие:

  • 1920 пикселей — экран Full HD, обычный ноутбук или монитор;
  • 2560 пикселей — монитор QHD и ноутбуки с плотностью 1,5–2x;
  • 3840 пикселей — экран 4K. Такой файл тяжёлый, поэтому сжимайте его сильнее или отдавайте только по image-set() для плотных экранов.
Пикселизация: изображение потеряло чёткость после увеличения сверх исходного размера

Фон на весь экран: body, секция, блок

Фон можно повесить на body, на секцию или на любой блочный элемент: div, section, header. Если размеры контейнера меняются, фон подстраивается под него.

Высоту задают через min-height, а не height. Так блок вырастает, когда контента больше, чем помещается на экране. На мобильных единица 100vh считается по большому окну, когда панели браузера убраны. Выезжающая адресная строка перекрывает низ блока. Единица 100svh считается по малому окну с раскрытыми панелями, а 100dvh меняется вместе с ними. Единицы dvh, svh и lvh работают в Chrome и Edge с 108, в Firefox со 101 и в Safari с 15.4. Запись min-height: 100vh перед min-height: 100dvh служит запасным вариантом для старых браузеров.

background-attachment: fixed и его цена

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

  • Кадрирование. При fixed область позиционирования фона — начальный содержащий блок, то есть по сути область просмотра. Поэтому cover считает размер от окна, а не от элемента. На длинном блоке картинка выглядит иначе, чем вы ожидали, и видна только сквозь «окно» размером с экран.
  • Мобильные. Can I use отмечает, что на большинстве мобильных устройств фон с fixed обновляет позицию с задержкой после прокрутки. Прокрутка может выглядеть рывками. Проверяйте результат на реальном телефоне.

Если параллакс не нужен, оставьте значение по умолчанию scroll. Если эффект нужен и на мобильных, вынесите фон в отдельный слой:

.bg-layer {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: #464646 url(photo.jpg) center center / cover no-repeat;
}

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

Разные картинки под разные экраны

Функция image-set() позволяет перечислить варианты картинки с плотностью пикселей и форматом. Браузер выбирает подходящий сам:

.hero {
  /* Запасной вариант для браузеров без image-set() */
  background-image: url(photo-1x.jpg);

  /* Только плотность: старые Chrome и Safari */
  background-image: -webkit-image-set(
    url(photo-1x.jpg) 1x,
    url(photo-2x.jpg) 2x
  );

  /* Плотность и формат: браузер берёт первый вариант, который умеет показать */
  background-image: image-set(
    url(photo-1x.avif) type("image/avif") 1x,
    url(photo-2x.avif) type("image/avif") 2x,
    url(photo-1x.webp) type("image/webp") 1x,
    url(photo-2x.webp) type("image/webp") 2x,
    url(photo-1x.jpg) type("image/jpeg") 1x,
    url(photo-2x.jpg) type("image/jpeg") 2x
  );
}

Запись без префикса работает в Chrome и Edge с 113, в Firefox с 89 и в Safari с 17. В более старых Chrome и Safari нужен -webkit-image-set(). Браузер учитывает плотность экрана и не перезагружает картинку при изменении размера окна. Если браузер не разбирает запись целиком, он пропускает объявление и остаётся на предыдущем — поэтому JPEG в первой строке обязателен. Поддержку type() проверяйте на своих браузерах: у разных движков она расходится.

Прежний способ — медиа-запрос:

@media (max-width: 767px) {
  .hero { background-image: url(photo-mobile.jpg); }
}

У него два минуса. Если менять ширину окна, скажем, с 1200 до 640 пикселей, страница мерцает, пока браузер подгружает другую картинку. К тому же медиа-запрос смотрит на ширину, а не на плотность пикселей: телефон с экраном высокого разрешения получит мелкий файл и покажет его размытым. Используйте медиа-запрос как запасной вариант, когда нужна другая композиция кадра, а не просто другой размер.

Вес и форматы

Фон на 5500×3600 пикселей и 1,7 МБ — пример того, как делать не надо: один файл перевешивает всю остальную страницу. Мобильную версию в 768×505 пикселей удалось сжать с 1741 до 114 КБ, то есть на 93%. Единого официального лимита в килобайтах нет. Рабочий ориентир такой: мобильный вариант — около сотни килобайт, как в примере выше, десктопный — заметно тяжелее, но не мегабайты. Чем меньше файл, тем быстрее страница открывается на медленной сети.

  • Формат. AVIF работает в Safari с 16.1, полностью с 16.4. WebP в Safari частично поддерживается с 14 и полностью с 16. Перечисляйте форматы от более лёгкого к запасному: AVIF, затем WebP, затем JPEG.
  • Чем сжимать. Сервис Smush.it давно закрыт. Из живых инструментов подойдут Squoosh, который сжимает картинки прямо в браузере, TinyPNG и ImageOptim.
  • Цвет подложки. Пока фон грузится, читаемость держит background-color. Подберите его под главный цвет фото. Ещё один приём — размытая миниатюра в несколько килобайт, которую подставляют вместо цвета, пока грузится оригинал.

Фон как подложка под текст

Светлый текст на светлой части фото плохо читается. Решение — полупрозрачный градиент вторым слоем в background-image. Слои перечисляются через запятую, верхний идёт первым:

.hero {
  background-image:
    linear-gradient(rgb(0 0 0 / 0.5), rgb(0 0 0 / 0.5)),
    url(photo.jpg);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: #fff;
}

Проверьте контраст текста и подложки. Для основного текста рекомендация WCAG — отношение не ниже 4,5 к 1. Проверяйте по самому светлому участку фото под текстом, а не по среднему.

Когда фон должен быть тегом img

Если картинка — часть содержания, а не украшение, делайте её тегом img. У него есть alt, поисковики его индексируют, а атрибут loading="lazy" откладывает загрузку. Фон в CSS для этого не подходит: у него нет ни текстового описания, ни семантики.

.cover-img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

Свойство object-fit: cover ведёт себя как background-size: cover. Свойство aspect-ratio сохраняет пропорции блока, пока картинка грузится, и страница не прыгает.

Частые ошибки

  • Фон исчез после перехода на шорткат. Причина: cover написан без / после позиции, и браузер отбросил объявление.
  • Низ блока обрезан на телефоне. Причина: 100vh считается по окну без панелей. Замените на 100dvh.
  • Картинка размытая. Причина: исходник меньше блока или экрана с высокой плотностью. Возьмите файл шире.
  • Страница долго открывается. Причина: фон весит мегабайты. Сожмите его и перейдите на AVIF или WebP.
  • Текст не читается. Причина: нет подложки. Добавьте градиент вторым слоем.
  • Прокрутка дёргается на телефоне. Причина: background-attachment: fixed. Уберите его или вынесите фон в слой с position: fixed.
  • До загрузки виден белый экран. Причина: не задан background-color.

Комментарии

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

у меня не получилось

Nata L.

Это применимо к одному изображению? Как сделать если на сайте в каждом разделе фоновая картинка разная. Ведь в body указан адрес ссылается на одну картинку или я что-то не понимаю?

ТЗ
Твердый З.

молодец очень полезно))то что я и искал)

N
nord

У меня одного это все работает исключительно к body?

A
Andrew

А можно ли сделать так, чтобы этот фон менялся автоматически при каждом посещении?

1
111

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

А
Алексей

Здравствуйте! Выражаю огромную благодарность автору данной статьи за тщательно подготовленный и легко усваиваемый материал. Разумеется, что теперь данный сайт у меня размещён в закладках. Очень рад, что нашёл вас! Единственный вопрос, который у меня возникает... как наложить полупрозрачный фон? Спасибо!

Р
Р.Денишев

Спасибо огромное, ваша статья очень выручила.

В
Валерий

Жаль, что данную статью я прочитал в конце пути, столько бы времени сэкономил. К данному решению я пришел сам, перерыв целую кучу статей. Увы, когда мне нужно было решить эту задачу, поисковые системы эту статью не выдавали. А прочитал я ее, рыская в просторах сети, так как у меня в связи с применением этого метода, возникла другая проблема. Дело в том, что у меня задача то похожая, но другая. Нужно, чтобы была не одна картинка, а слайдер, с таким же свойством растягивания, при чем, чтобы сверху был тоненький (пикселей на 40-50) header, ниже шел до конца экрана браузера слайдер, а при скроллинге - картинка чуть приподымалась, header прятался и появлялся footer. И вся проблема в том, что background не обладает свойством контента и не занимает места. Соответственно, ниже картинки простыми способами нельзя разместить элемент, чтобы он снизу обтекал картинку и вместе с ней растягивался и сжимался. Получается либо привязываешь footer к контейнеру слайдера, придавая ему свойство relative, тогда оно живет одним целым, но не растягивается, либо подстраивая свойством размера контейнера настраиваешь положения footerа к слайдеру, но все прогнозируемо разъезжается на других разрешениях монитора. У кого есть соображения, как решить этот ребус?? Не хотелось бы на это убить еще несколько месяцев.

А
Алекс

Вы уже решили этот вопрос? У меня кажется получилось сделать что-то подобное при помощи <figure>, и в том числе, этого пояснения http://habrahabr.ru/post/157817/
с некоторыми изменениями