Если у вас есть собственный сайт — вы наверняка проверяли его работу с телефона. Открыли, полистали, остались довольны: «Всё летает». Но это не гарантия, что так же быстро сайт загрузится у ваших посетителей.

Представьте: пользователь заходит на ваш сайт с iPhone (неважно, нового или трёхлетней давности) — и страница зависает, изображения грузятся по одному, скролл дёргается. Через 5–10 секунд он просто закрывает вкладку и уходит к конкурентам. Проблема не в вашем телефоне или интернете, а в скрытых особенностях браузера Safari и устройств iOS.

Ниже — руководство по оптимизации, которое поможет избежать таких сценариев. Пройдитесь по чек‑листу и убедитесь, что каждый пункт выполнен. Даже если ваш сайт кажется быстрым, с большой вероятностью он теряет часть аудитории на Safari.

1. Проблема: почему сайт тормозит в Safari

Safari (особенно на iOS) имеет ряд особенностей:

  • HTTP/1.1 – ограничение 6 соединений на домен. При большом количестве ресурсов (изображения, скрипты, стили) запросы встают в очередь, страница грузится десятками секунд.

  • Однопоточный JS-движок – частые вызовы scroll/resize без ограничений вызывают thrashing (подвисания).

  • Жёсткие лимиты памяти – слишком большой canvas (буфер 4000×10000 пикселей) может привести к OOM‑kill – Safari просто закроет вкладку.

  • Блокирующий рендеринг – синхронные скрипты и шрифты задерживают отображение страницы.

2. Чек‑лист обязательных оптимизаций

🔴 Критические (без них сайт может быть практически неработоспособен в Safari)

1. Включить HTTP/2 на сервере

Пример конфигурации nginx (фрагмент):

server {
    listen 443 ssl http2;
    server_name mysite.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    root /var/www/html;
    index index.html;
}

После применения конфига перезагрузите nginx: sudo nginx -s reload.Проверка: откройте DevTools (F12) → вкладка Network → найдите любой ресурс (CSS, JS) → колонка Protocol должна показывать h2.

2. Добавить loading="lazy" для всех изображений ниже первого экрана

Пример:

<!-- Изображение в hero-секции — без lazy -->
<img src="hero.jpg" alt="Главный баннер" class="hero-image">

<!-- Изображения ниже первого экрана — с lazy -->
<img src="gallery/photo1.jpg" alt="Фото" loading="lazy">
<img src="gallery/photo2.jpg" alt="Фото" loading="lazy">

<!-- Для слайдера Swiper — lazy-изображения в swiper-lazy -->
<div class="swiper">
  <div class="swiper-wrapper">
    <div class="swiper-slide">
      <img data-src="slide1.jpg" class="swiper-lazy" alt="Слайд">
      <div class="swiper-lazy-preloader"></div>
    </div>
  </div>
</div>

Если имеются виджеты с изображениями, которые видны на первом экране, им не надо добавлять loading=“lazy”

3. Добавить defer ко всем скриптам, кроме jQuery (если используете jQuery)

<!-- jQuery без defer, загружается первым -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<!-- Все остальные скрипты с defer -->
<script src="js/slider.js" defer></script>
<script src="js/main.js" defer></script>
<script src="js/widgets.js" defer></script>

Если используете сторонние библиотеки (Swiper, Masonry и т.д.), тоже добавляйте defer.

4. Удалить неиспользуемые библиотеки (например, three.js)

// Откройте консоль (F12) и выполните:
console.log(window.THREE);

Если результат undefined – библиотека не используется, удалите тег <script src="three.js">.Также можно поискать в коде: Ctrl+Shift+F по всем файлам проекта на THREE. или three.js.

5. Реализовать throttle для обработчиков scrollresizewheel

Например:

// Вместо прямого подписывания на scroll
function onScrollHeavyTask() {
  // Здесь тяжёлые вычисления, изменение классов, анимации
  console.log('scroll event (throttled)');
}

let ticking = false;
window.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      onScrollHeavyTask();
      ticking = false;
    });
    ticking = true;
  }
});

// Аналогично для resize
let resizeTicking = false;
window.addEventListener('resize', () => {
  if (!resizeTicking) {
    requestAnimationFrame(() => {
      // код обработки ресайза
      resizeTicking = false;
    });
    resizeTicking = true;
  }
});

6. Уменьшить размер canvas на мобильных устройствах

Если на сайте имеется задний фон, реализованный через canvas (например, анимированные звёзды или частицы), нужно снизить размер буфера для мобильных устройств.

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

🟠 Высокий приоритет (заметно ускоряют загрузку)

7. Проверить и включить gzip (или Brotli) сжатие

Пример настройки gzip в nginx:

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

После перезагрузки nginx проверьте в DevTools → Network → выберите любой CSS/JS файл → заголовок Content-Encoding: gzip.

8. Добавить preconnect для всех используемых CDN

В секцию <head> добавьте:

<link rel="preconnect" href="https://code.jquery.com">
<link rel="preconnect" href="https://cdn.jsdelivr.net">
<link rel="preconnect" href="https://unpkg.com">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

9. Настроить PurgeCSS для удаления неиспользуемых стилей

Пример конфигурации для Gulp:

const gulp = require('gulp');
const purgecss = require('gulp-purgecss');

gulp.task('purge', () => {
  return gulp.src('dist/css/style.css')
    .pipe(purgecss({
      content: ['**/*.html', '**/*.js'],
      safelist: [/swiper/, /modal/, /active/, /open/] // динамические классы
    }))
    .pipe(gulp.dest('dist/css'));
});

Для PostCSS (webpack):

const purgecss = require('@fullhuman/postcss-purgecss');
module.exports = {
  plugins: [
    purgecss({
      content: ['./src/**/*.html', './src/**/*.js'],
      safelist: ['swiper', 'modal', 'active']
    })
  ]
};

10. Удалить jQuery Migrate, если он не нужен (если используете jQuery)

Если в консоли нет предупреждений JQMIGRATE, просто удалите тег:

<!-- Удалите эту строку -->
<script src="https://code.jquery.com/jquery-migrate-3.4.0.js"></script>

Проверьте работу слайдеров и плагинов – если всё работает, migrate был не нужен.

11. Добавить font-display: swap для всех локальных шрифтов

@font-face {
  font-family: 'Open Sans';
  src: url('/fonts/OpenSans.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

🟡 Средний приоритет (хорошо для метрик Lighthouse)

12. Оптимизация CSS: critical inline + асинхронная загрузка остального

В <head>:

<style>
  /* Critical CSS — стили только для первого экрана */
  .header { background: #fff; }
  .hero { font-size: 2rem; }
  .button { background: blue; }
</style>
<!-- Остальные стили загружаем асинхронно -->
<link rel="preload" href="css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="css/main.css"></noscript>

13. Конвертация изображений в WebP с fallback через <picture>

Пример для разных форматов:

<picture>
  <source srcset="images/photo.webp" type="image/webp">
  <source srcset="images/photo.jpg" type="image/jpeg">
  <img src="images/photo.jpg" alt="Описание" loading="lazy" width="800" height="600">
</picture>

Для автоматической конвертации можно использовать плагин imagemin в сборщике (например, Gulp):

const imagemin = require('gulp-imagemin');
const webp = require('gulp-webp');

gulp.task('webp', () => {
  return gulp.src('src/images/**/*.{jpg,png}')
    .pipe(webp())
    .pipe(gulp.dest('dist/images'));
});

Заключение

Следуя этому чек-листу, вы обеспечите быструю и стабильную работу сайта в Safari (и других браузерах). Наибольший эффект дают HTTP/2, lazy loading, defer скриптов, throttle обработчиков и PurgeCSS. Регулярный аудит производительности помогает не накапливать технический долг и не терять посетителей из‑за скрытых проблем.