Картинки ужали вдвое, а Safari на iPhone всё равно перезагружал вкладку — теперь сразу, без прокрутки, по нажатию на меню. Выключили размытие фона у меню — падать перестало, но выезд стал рваным. Размытие оказалось спусковым крючком, а причиной было то, что под ним: 769 слоёв композитинга.
Это четвёртая часть цикла «Вёрстка без сюрпризов». Первая половина истории — про картинки — в третьей части.
Что такое слой и почему их много
Браузер выносит часть элементов в отдельные слои, которые рисуются видеокартой: так их можно двигать и анимировать без перерисовки страницы. У каждого слоя свой буфер в памяти — ширина × высота × 4 байта × квадрат плотности экрана. Десяток слоёв — это норма. Сотни — это сотни буферов.
Полноэкранное размытие (backdrop-filter) просит браузер размыть всё, что под элементом. Над лёгкой страницей это дёшево, над сотнями слоёв — память кончается.
Как посчитать слои
Счёт слоёв даёт протокол инструментов разработчика Chrome: LayerTree.enable присылает дерево слоёв, а LayerTree.compositingReasons объясняет, почему элемент стал слоем. Это Chrome, а не WebKit, но он показывает главное — кто становится слоем. На верху главной, со всеми загруженными строками, слоёв было 769.
Обычные зонды в Chrome падение Safari не ловят: все наши замеры после первой правки были «зелёные», а у пользователя вкладка падала. Первое, что делать при жалобе «Safari вылетает», — считать слои.
Кто давал слои
- Swiper в Safari сам включает режим
.swiper-backface-hidden, а с ним каждому слайду —transform: translateZ(0)иbackface-visibility: hidden. Каждая миниатюра полосы под слайдером стала слоем — около 200 на главной; translate3d(…)для центрирования миниатюр, кадров галереи, фона категории — трёхмерный сдвиг всегда создаёт слой;clip-pathна классе, который стоял и на контейнере, и на каждом слайде, — около 190 масок-слоёв;- выезд меню через
right: -100% → 0— пересчёт раскладки на каждом кадре анимации.
Лечение
/* 1. полосу двигает сдвиг обёртки — самим миниатюрам слой не нужен */
.thumbsStrip.swiper-backface-hidden .swiper-slide {
transform: none;
backface-visibility: visible;
}
/* 2. центрирование двумерным сдвигом — визуально то же самое */
.thumb img { transform: translate(0, -50%); } /* было translate3d(0, -50%, 0) */
/* 3. маска только контейнеру и только там, где она нужна */
@media (min-width: 821px) {
.productSlider { clip-path: inset(0); }
}
/* 4. выезд меню сдвигом, а не свойством раскладки */
.mobileMenu { transform: translateX(100%); }
.mobileMenu.open { transform: translateX(0); }
И пятое — ограничитель по построению: на устройствах без мыши строки дальше полутора экранов от видимой области получают класс, который прячет содержимое через visibility: hidden. Раскладка и загрузка картинок не меняются, а буферы освобождаются. Память перестала расти с числом прокрученных строк — около четырёх строк в любой момент.
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => e.target.classList.toggle('isFar', !e.isIntersecting));
}, { rootMargin: '150%' });
if (matchMedia('(hover: none)').matches) {
document.querySelectorAll('.productLine').forEach((line) => io.observe(line));
}
Результат
- 769 → 22 слоя;
- у пользователя на айфоне вкладка не падает, меню выезжает плавно, размытие вернули;
- мерцания при листании слайдов без трёхмерных трюков не замечено.
Что грепать в своём коде: translate3d, translateZ, backface-visibility, clip-path, backdrop-filter, will-change, filter:. Каждое — кандидат в слой. Проверять на настоящем устройстве после каждого шага, на тестовой копии до выката.
Safari на айфоне перезагружает вкладку на вашем сайте?
Пришлите адрес — посчитаю слои и найду, кто их плодит.
// загружаю комментарии…