Меню, яке показувало один пункт: Safari й backdrop-filter
Тести зелені, а на iPhone у меню лише «Роботи». Розбираю, чому filter і backdrop-filter ламають position: fixed, і як ловити це тестом.
Сайт готовий. Скрипт перевірки пройшов 21 сторінку в трьох ширинах і двох темах, клацнув мобільне меню, перевірив фільтр робіт. «Проблем немає». Я публікую реліз і йду по чай .
За п’ять хвилин приходить скриншот з iPhone: у повноекранному меню — один-єдиний пункт. «Роботи». Решти немає. Не сховані, не за краєм — їх просто не видно.
Що сталося
Шапка сайту напівпрозора й розмиває те, що під нею: backdrop-filter: blur(14px). Гарно. Мобільне меню — це панель position: fixed на весь екран, і в розмітці вона лежить усередині шапки.
А тепер рядок зі специфікації, який варто татуювати на руці фронтендера: якщо предок має filter, backdrop-filter, transform, perspective, contain: paint чи will-change: transform, він стає контейнером для fixed-нащадків. Тобто position: fixed рахується вже не від вікна, а від цього предка.
Моя «на весь екран» панель стала «на всю шапку»: її розміри й положення тепер рахувались від смужки заввишки 64 пікселі. З усього меню лишився видимим перший пункт.
/* було: розмиття на самій шапці — і fixed-панель усередині «живе» в її межах */
.site-header { position: sticky; backdrop-filter: saturate(1.4) blur(14px); }
.drawer__panel { position: fixed; inset: 64px 0 0 0; }
Виправлення на три рядки
Розмиття не обов’язково вішати на саму шапку. Досить псевдоелемента, який лежить під її вмістом:
.site-header::before {
content: ''; position: absolute; inset: 0; z-index: -1;
background: color-mix(in srgb, var(--bg) 88%, transparent);
backdrop-filter: saturate(1.4) blur(14px);
}
Шапка так само матова, але фільтр тепер на сусідньому елементі, а не на предку панелі. Меню знову на весь екран, і в ньому всі шість пунктів.
Чому тест не впіймав
Найцікавіше не в самій ваді, а в тому, чому тест був зелений. Наш headless Chromium не підтримує backdrop-filter. Правило стоїть під @supports (backdrop-filter: blur(1px)), тож у тестовому браузері фільтра просто не було, а значить і вади. Тест чесно перевіряв сайт, якого ніхто з людей не бачить.
Висновок: мало перевіряти, що «пункт меню видно». Треба перевіряти геометрію й причини:
// панель має бути справді на весь екран
const h = await page.locator('.drawer__panel').evaluate((e) => e.getBoundingClientRect().height);
if (h < 500) bad(`панель заввишки лише ${h}px`);
// жоден предок fixed-панелі не має filter / backdrop-filter / transform
const guilty = await page.locator('.drawer__panel').evaluate((e) => {
const out = [];
for (let n = e.parentElement; n; n = n.parentElement) {
const cs = getComputedStyle(n);
if (cs.filter !== 'none' || cs.backdropFilter !== 'none' || cs.transform !== 'none') out.push(n.className);
}
return out;
});
Друга перевірка ловить ваду навіть у браузері, який сам її не відтворює: вона дивиться на CSS, а не на картинку. Тепер вона живе в наборі перевірок сайту, і повторити цей трюк уже не вийде.
Коротко
filter,backdrop-filter,transform,perspective,contain,will-changeна предку змінюють, від чого рахуєтьсяposition: fixed.- Розмиття фону — на псевдоелемент, а не на контейнер із fixed-дітьми.
- Тестуйте причини, а не лише симптоми: headless-браузер може не мати тієї самої властивості, що ламає сайт у людей.
- І завжди відкривайте реліз на справжньому телефоні. Скриншот від власника — найчесніший тест .