45 Модуль 9 Качество и производительность 16 мин урок 20 из 25

Кроссбраузерность и деградация

Сайт должен работать везде — но не обязан выглядеть одинаково. Разбираем прогрессивное улучшение, @supports и что делать с Safari.

Что ты научишься делать

  • Построишь вёрстку так, чтобы новая фича была улучшением, а не условием работы
  • Научишься читать caniuse и решать, брать ли фичу в проект
  • Сделаешь раскладку с subgrid и рабочим запасным вариантом

#Главная мысль урока

Работать и выглядеть одинаково — разные требования, и путать их дорого.

Попытка добиться пиксель в пиксель во всех браузерах съедает недели и рождает костыли, которые ломаются на следующем обновлении. Требование «человек может сделать то, зачем пришёл» выполняется гораздо дешевле.

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

#Прогрессивное улучшение против изящной деградации

Два подхода, которые звучат похоже, а дают разный код.

Изящная деградация — сначала пишем современную версию, потом чиним для старых браузеров. Починка идёт вслед за поломками, и список костылей растёт.

Прогрессивное улучшение — сначала простая рабочая версия, потом улучшения поверх. Ничего не ломается, потому что база не зависит от новых возможностей.

Так не надо

/* Сначала современное… */
.cards {
  display: grid;
  grid-template-columns: subgrid;
}

/* …потом чиним, если не поддержано */
@supports not (grid-template-columns: subgrid) {
  .cards { display: flex; flex-wrap: wrap; }
  .card { flex: 1 1 240px; }
}

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

Так надо

/* База: работает везде */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.card { flex: 1 1 240px; }

/* Улучшение поверх */
@supports (grid-template-rows: subgrid) {
  .cards { display: grid; }
}

База не знает о новых возможностях и не может из-за них сломаться. Улучшение либо применяется целиком, либо не применяется вовсе.

Интерактивный разбор доступен при включённом JavaScript.

#Когда @supports не нужен

Проверка нужна реже, чем кажется. CSS устроен так, что непонятное объявление просто отбрасывается — и этого часто достаточно.

CSSЕстественный запасной вариант
.panel {  /* Старый браузер применит это… */  background: #6544e0;  /* …и отбросит это, если не поймёт функцию */  background: color-mix(in oklab, #6544e0 80%, white);}.card {  /* Пиксели поймут все */  gap: 16px;  /* Современные возьмут значение из шкалы */  gap: var(--space-4, 16px);}

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

СитуацияНужен ли @supports
Одно свойство с запасным значением вышеНет — сработает естественный отбор
Незнакомый псевдокласс в селектореНет — правило целиком не применится
Улучшение из нескольких свойств, которые нужны вместеДа
База и улучшение конфликтуют между собойДа
Незнакомое @-правило вроде @containerНет — блок игнорируется целиком

#Что делать с Safari

Safari регулярно отстаёт по срокам и остаётся единственным движком на iOS: любой браузер там внутри — это WebKit. То есть «просто попросить поставить другой браузер» не работает.

Практические выводы.

  • Проверяй на настоящем Safari. Chrome с эмуляцией iPhone показывает движок Chrome, а не WebKit. Это не проверка.
  • Помни про -webkit-. У части свойств — backdrop-filter, mask, text-size-adjust — префикс до сих пор нужен.
  • Проверяй 100vh. На мобильном Safari он долго считался без панели браузера, и блок вылезал за экран. Современная замена — 100dvh.
  • Не полагайся на самые свежие фичи. Если возможность появилась в Chrome полгода назад, в Safari она может быть ещё в разработке.
CSSВысота экрана без сюрпризов
.hero {  /* Запасное значение для старых браузеров */  min-block-size: 100vh;  /* Динамическая высота: учитывает исчезающую панель браузера */  min-block-size: 100dvh;}

#Префиксы: что живо, что мертво

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

СвойствоПрефикс
border-radius, box-shadow, transition, transformНе нужен уже много лет
flex, gridНе нужен
backdrop-filterНужен -webkit- для части сборок Safari
mask-imageНужен -webkit-
appearanceИногда нужен -webkit-
::-webkit-scrollbarСобственное расширение WebKit, стандарта нет

#Как читать caniuse

Общий процент поддержки — наименее полезное число на странице. Смотреть нужно на другое.

  1. Сравни свою аудиторию с общей

    Девяносто пять процентов в мире ничего не говорят, если половина твоих людей приходит с одного старого устройства. Загляни в аналитику своего сайта.

  2. Прочитай примечания

    Зелёная клетка с цифрой означает частичную поддержку. Внизу страницы описано, чего именно не хватает, — часто это важнее самого факта поддержки.

  3. Посмотри на дату появления

    Фича, появившаяся полгода назад, есть только у тех, кто обновляется. Фича трёхлетней давности — практически у всех.

  4. Реши цену отказа

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

#Тестирование без парка устройств

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

  • Настоящий Safari на любом Mac. Один компьютер закрывает весь iOS: движок тот же.
  • Замедление процессора в DevTools. Четырёхкратное замедление приближает поведение среднего телефона.
  • Отключение возможностей вручную. Временно переименуй @supports (grid-template-rows: subgrid) в заведомо неподдерживаемое — увидишь запасную ветку своими глазами.
  • Проверка без скриптов. Выключи JavaScript и пройди основной сценарий. Это же ловит проблемы у людей с блокировщиками.
  • Масштаб двести процентов. Ловит обрезанный текст и сломанную раскладку лучше, чем эмуляция маленьких экранов.

#Как это выглядит в реальном проекте

CSSgallery.css
@layer components {  /* База: флексы. Работают везде, начиная с очень старых браузеров */  .gallery {    display: flex;    flex-wrap: wrap;    gap: 16px;  }  .gallery__item {    flex: 1 1 240px;    display: flex;    flex-direction: column;  }  /* Кнопка прижата вниз — простое решение без сетки */  .gallery__btn { margin-block-start: auto; }  /* Улучшение первого уровня: сетка вместо флексов */  @supports (display: grid) {    .gallery {      display: grid;      grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));    }  }  /* Улучшение второго уровня: строки выравниваются между карточками */  @supports (grid-template-rows: subgrid) {    .gallery { grid-auto-rows: auto 1fr auto; }    .gallery__item {      display: grid;      grid-row: span 3;      grid-template-rows: subgrid;    }    .gallery__btn { margin-block-start: 0; }  }}

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

#Когда НЕ надо

  • Поддерживать браузеры, которых нет у аудитории. Каждая ветка совместимости — код, который надо сопровождать. Проверь аналитику, прежде чем платить эту цену.
  • Ставить префиксы «на всякий случай». Устаревшая префиксная реализация может перекрыть правильную и вести себя иначе.
  • Оборачивать в @supports всё подряд. Одиночное свойство и так безопасно отбрасывается.
  • Добиваться одинакового вида. Разные системы по-разному рисуют шрифты, поля и полосы прокрутки. Это нормально и не требует починки.

Где спотыкаются почти все

Современное значение написано выше запасного

Почему Браузер применяет последнее объявление, которое понял. Если современное идёт первым, запасное перекроет его у всех.

Как надо Сначала запасное значение, потом современное.

Проверка через <code>@supports not</code>

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

Как надо Делай базу рабочей, а улучшения добавляй положительной проверкой.

Тестирование iOS в режиме устройства Chrome

Почему Меняется только размер окна и строка User-Agent. Движок остаётся прежним, баги WebKit не проявятся.

Как надо Открой настоящий Safari — один Mac закрывает весь iOS.

Префикс после стандартного свойства

Почему Браузер, понимающий оба, применит устаревший префиксный вариант.

Как надо Префиксное объявление всегда идёт первым.

Прогрессивное улучшение progressive enhancement
Рабочая база плюс необязательные улучшения поверх.
Изящная деградация graceful degradation
Современная версия, дополненная починками для старых браузеров.
Проверка поддержки @supports
Правило, применяющее блок только если браузер понимает конструкцию.
Префикс поставщика vendor prefix
Приставка вроде -webkit- для экспериментальных реализаций.
Динамическая высота экрана dvh
Единица, учитывающая исчезающие панели мобильного браузера.

Проверь себя

  1. В каком порядке писать запасное и современное значения?

    Браузер применяет последнее объявление, которое понял. Современное должно идти последним.

  2. Когда <code>@supports</code> действительно необходим?

    Одиночное непонятное объявление и так отбрасывается. Проверка нужна там, где ветки мешают друг другу.

  3. Почему режим устройства в Chrome не заменяет проверку на iOS?

    Все браузеры на iOS работают на WebKit. Его баги в Chrome не воспроизведутся.

  4. Что означает <code>@supports</code>, ответивший «да»?

    Проверяется разбор, а не качество реализации. Тестирование в живом браузере это не отменяет.

  5. Самое полезное число на странице caniuse — это…

    Общий процент не говорит ничего о твоей аудитории, а примечания часто важнее самого факта поддержки.

Задание

Раскладка с рабочим запасным вариантом

Галерея свёрстана только на subgrid: там, где его нет, карточки просто ложатся в столбик без выравнивания.

Переверни подход. Сделай базой флексы: карточки в ряд с переносом, кнопка прижата вниз через margin-block-start: auto. А subgrid добавь сверху через положительный @supports. Отрицательную проверку убери.

Открыть в песочнице
    Подсказка 1

    База: .gallery { display: flex; flex-wrap: wrap; gap: 12px; } и .gallery__item { flex: 1 1 200px; display: flex; flex-direction: column; }

    Подсказка 2

    Кнопке в базовом варианте: .gallery__btn { margin-block-start: auto; }

    Подсказка 3

    Улучшение: @supports (grid-template-rows: subgrid) { … } — внутрь переносишь всё, что было про grid.

    Показать решение
    HTMLindex.html
    <div class="gallery">  <article class="gallery__item">    <h3>Эспрессо</h3>    <p>Двойная порция.</p>    <button class="gallery__btn">Заказать</button>  </article>  <article class="gallery__item">    <h3>Раф на кокосовом молоке</h3>    <p>Мягкий сладковатый напиток для тех, кому латте кажется резким.</p>    <button class="gallery__btn">Заказать</button>  </article>  <article class="gallery__item">    <h3>Фильтр</h3>    <p>Зерно из Эфиопии.</p>    <button class="gallery__btn">Заказать</button>  </article></div>
    CSSstyle.css
    /* База: работает везде */.gallery {  display: flex;  flex-wrap: wrap;  gap: 12px;}.gallery__item {  flex: 1 1 200px;  display: flex;  flex-direction: column;  gap: 6px;  padding: 12px;  background: #f5f5fa;  border-radius: 10px;}.gallery__btn { margin-block-start: auto; }/* Улучшение: строки выравниваются между карточками */@supports (grid-template-rows: subgrid) {  .gallery {    display: grid;    grid-template-columns: repeat(3, 1fr);    grid-auto-rows: auto 1fr auto;  }  .gallery__item {    display: grid;    grid-row: span 3;    grid-template-rows: subgrid;  }  .gallery__btn { margin-block-start: 0; }}.gallery__item h3 { margin: 0; font-size: 15px; }.gallery__item p { margin: 0; font-size: 13px; color: #5b6070; }.gallery__btn {  padding: 8px;  font: inherit;  font-size: 13px;  color: #fff;  background: #6544e0;  border: 0;  border-radius: 8px;}

    Проверь себя