Вы пишете код, тестируете в Chrome — всё летает. Открываете в Safari — половина не работает. Знакомо? Кроссбраузерность — это боль каждого фронтендера. Но есть хорошая новость: с правильным подходом можно писать код, который будет работать везде, от старого IE до нового Firefox.

Digital-студия WNDER

Почему браузеры такие разные?

Каждый браузер использует свой движок для отображения страниц и выполнения JavaScript. Chrome и Edge работают на V8, Firefox — на SpiderMonkey, Safari — на JavaScriptCore. Это как разные переводчики: один понимает шутки, другой — нет. Добавьте сюда разные версии браузеров, и получите зоопарк, где каждый зверь хочет особого подхода.

Проблемы обычно возникают в трёх местах:

  • Синтаксис — новые фичи вроде optional chaining или nullish coalescing поддерживаются не везде.
  • API — методы вроде Array.prototype.flat() или fetch могут отсутствовать.
  • Поведение — даже одинаковый код может давать разный результат из-за багов или различий в реализации.

Правило первое: не доверяй, проверяй

Правило: Всегда проверяйте поддержку функции перед её использованием. Не полагайтесь на предположения.

Самый простой способ — использовать feature detection. Это когда вы спрашиваете у браузера: «Эй, ты умеешь это делать?» И если нет — используете запасной вариант.

// Плохо: сразу используем fetch
fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));

// Хорошо: проверяем поддержку
if (window.fetch) {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => console.log(data));
} else {
  // Фолбэк для старых браузеров
  var xhr = new XMLHttpRequest();
  xhr.open('GET', '/api/data');
  xhr.onload = function() {
    if (xhr.status === 200) {
      console.log(JSON.parse(xhr.responseText));
    }
  };
  xhr.send();
}

Для проверки методов массивов или строк можно использовать такой трюк:

if (!Array.prototype.flat) {
  Array.prototype.flat = function(depth) {
    // Полифилл для flat
    var flattened = [];
    function flatten(arr, d) {
      for (var i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i]) && d > 0) {
          flatten(arr[i], d - 1);
        } else {
          flattened.push(arr[i]);
        }
      }
    }
    flatten(this, depth === undefined ? 1 : depth);
    return flattened;
  };
}

Полифиллы и транспиляция: два подхода к одной цели

Есть два основных способа сделать код совместимым:

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

Вот сравнение популярных инструментов:

Инструмент Тип Что делает Когда использовать
Babel Транспилятор Преобразует новый синтаксис в старый Когда пишете на ES6+ и нужна поддержка IE11
core-js Полифилл Добавляет недостающие методы и объекты Когда нужны новые API в старых браузерах
Polyfill.io Сервис Автоматически подгружает нужные полифиллы Для быстрого прототипирования

На практике чаще всего используют комбинацию: Babel для синтаксиса и core-js для API.

Практические советы и лайфхаки

Вот несколько приёмов, которые сэкономят вам часы отладки:

  • Используйте addEventListener вместо attachEvent. Старый IE требовал attachEvent, но сейчас это неактуально. Если всё же нужно — проверяйте наличие метода.
  • Осторожно с event. В старых браузерах объект события передавался как глобальная переменная window.event. Всегда принимайте его как аргумент функции.
  • Не используйте innerText. Этот метод нестандартный. Лучше textContent, который работает везде.
  • Проверяйте localStorage. В приватном режиме Safari он может быть недоступен. Оборачивайте в try/catch.
Лайфхак: Используйте try...catch для работы с API, которые могут быть недоступны. Это лучше, чем проверять существование каждого метода.
// Вместо проверки каждого метода
if (window.localStorage) {
  try {
    localStorage.setItem('key', 'value');
  } catch (e) {
    // Обработка ошибки, если хранилище переполнено или недоступно
    console.warn('LocalStorage недоступен:', e);
  }
}

Ещё один важный момент — работа с CSS. Иногда браузеры по-разному применяют стили. Например, flexbox в старых версиях требовал префиксов. Но это уже больше про CSS, чем про JavaScript. Однако если вы динамически меняете стили через JS, учитывайте это.

Автоматизация проверки кроссбраузерности

Вручную тестировать в каждом браузере — это ад. К счастью, есть инструменты, которые помогают:

  • Can I use — сайт, где можно проверить поддержку любой фичи.
  • BrowserStack или Sauce Labs — облачные сервисы для тестирования в разных браузерах.
  • ESLint с плагином compat — статически анализирует код и предупреждает о несовместимых конструкциях.

Вот пример настройки ESLint для проверки совместимости:

// .eslintrc.js
module.exports = {
  plugins: ['compat'],
  rules: {
    'compat/compat': 'error'
  },
  env: {
    browser: true,
    es6: true
  },
  parserOptions: {
    ecmaVersion: 2020
  }
};

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

Заключение

Кроссбраузерность — это не магия, а набор простых правил и инструментов. Главное — не писать код вслепую, а проверять поддержку и использовать полифиллы. Помните: ваш код должен работать не только у вас на компьютере, но и у бабушки с планшетом на Android 5.

Начните с малого: добавьте feature detection, подключите Babel и core-js, настройте ESLint. Через неделю вы заметите, что багов стало меньше, а уверенности — больше. И да, не забывайте тестировать в Safari. Он всегда подкидывает сюрпризы.

Студия WNDER