Анимация CSS: стрелка на прозрачном блоке при скролле
Решение проблемы наложения анимированной стрелки CSS на прозрачный текстовый блок при скролле. Как исправить z-index, stacking context, заполнить пустоты градиентом и избежать смешения цветов в анимациях CSS.
Как решить проблему наложения анимированной стрелки на прозрачный текстовый блок при скролле в CSS?
При скролле стрелка собирается воедино и накладывается на блок с текстом, который имеет прозрачность. Стрелка не должна налазить под этот блок, чтобы её не было видно сквозь прозрачность. Как заполнить пустоту между красными стрелками, чтобы цвета не смешивались?
Проблема наложения анимации при скролле на прозрачный блок CSS с текстом возникает из-за создания stacking context при opacity < 1 — стрелка CSS просто не может вылезти поверх такого блока, даже с высоким z-index. Чтобы стрелка CSS не просвечивала сквозь текст и собиралась без смешения цветов, задайте ей position: relative и z-index: 10+, а для прозрачности используйте псевдоэлемент ::before только для фона. Пустоты между красными частями стрелки заполните linear-gradient в background, чтобы цвета не мутились при анимации анимаций CSS.
Содержание
- Почему z-index CSS не работает с анимацией при скролле
- Создание правильного stacking context для стрелки CSS
- Прозрачный блок CSS без ловушек opacity
- Заполнение пустот между стрелками градиентом
- Полный пример анимации стрелки при скролле
- Дополнительные хитрости для анимаций CSS
- Источники
- Заключение
Почему z-index CSS не работает с анимацией при скролле
Представьте: вы скроллите страницу, стрелка CSS анимированно собирается из частей снизу вверх, но вдруг ныряет под блок с полупрозрачным текстом. Почему так? Всё дело в z-index CSS и stacking contexts.
Элемент с opacity меньше 1 автоматически создаёт новый контекст наложения — как стек пластинок, где внутри него z-index работает только сам с собой. Ваша стрелка, даже с z-index: 999, окажется ниже, если текстовой блок “захватил” её в свой слой. Официальная документация MDN чётко объясняет: z-index действует только для position ≠ static, но внутри локального контекста.
А при анимации при скролле это усугубляется — transform или opacity в keyframes могут создавать дополнительные контексты. Стрелка “собирается воедино”, но части смешиваются с прозрачностью текста. Коротко: проверьте родительские элементы. Если у них opacity или transform — вот ваша ловушка.
Создание правильного stacking context для стрелки CSS
Чтобы стрелка CSS уверенно сидела поверх, сначала убедитесь, что она вне “пленительного” контекста. Задайте:
.arrow {
position: relative; /* Обязательно! */
z-index: 10; /* Или выше, чем у блока с текстом */
animation: assemble 2s ease-in-out; /* Ваша анимация при скролле */
}
Но если блок текста имеет opacity: 0.8, он создаст контекст — стрелка не вылезет. Хитрость из статьи на Хабре: слегка уменьшите opacity стрелки до 0.99, если нужно “опустить” её ниже без z-index. Нет, seriously — это создаст свой мини-контекст, но для подъёма используйте родительский контейнер.
При анимации при скролле добавьте Intersection Observer в JS, чтобы запускать keyframes только при видимости. Без этого браузер рендерит анимацию заранее, и z-index глючит.
// Простой триггер скролла
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.animationPlayState = 'running';
}
});
});
observer.observe(document.querySelector('.arrow'));
Так стрелка оживает ровно при скролле, не конфликтуя с z-index.
Прозрачный блок CSS без ловушек opacity
Прозрачный текст — круто для дизайна, но убивает анимации CSS. Стрелка видна сквозь него? Разделите фон и контент. Вместо opacity на всём блоке, используйте ::before для фона, как советуют в Skillbox.
.text-block {
position: relative;
z-index: 1; /* Ниже стрелки */
}
.text-block::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5); /* Только фон прозрачный */
z-index: -1;
}
.text-block__text {
position: relative;
z-index: 2; /* Текст поверх фона, но ниже стрелки */
color: white;
}
Теперь opacity только у фона — stacking context не захватывает стрелку. Она собирается при скролле, не просвечивая. Выиграли? Точно. И никаких смешений цветов.
А если текст сам полупрозрачный? Дублируйте его в SVG или canvas — но это overkill для простых случаев.
Заполнение пустот между стрелками градиентом
Между красными частями стрелки пустоты? При анимации цвета мусятся с фоном. Заполните их фоном! Web-revenue рекомендует linear-gradient:
.arrow-parts {
background:
linear-gradient(to right,
red 0%, red 30%,
transparent 30%, transparent 70%,
red 70%, red 100%
);
/* Или для вертикальной сборки */
background:
linear-gradient(to bottom,
transparent 0%, transparent 20%,
red 20%, red 80%,
transparent 80%, transparent 100%
);
animation: slide-up 3s forwards; /* Собирается снизу */
}
Градиент маскирует пробелы — красный “сливается” без мутации. При анимации при скролле добавьте mask или clip-path для точности:
.arrow-parts {
mask: linear-gradient(transparent 0%, black 20%);
/* Заполняет пустоты чёрным, скрывая смешение */
}
Тестируйте в DevTools — увидите, как цвета перестают плясать.
Полный пример анимации стрелки при скролле
Соберём всё вместе. HTML:
<div class="container">
<div class="text-block">
<div class="text-block__text">Прозрачный текст здесь</div>
</div>
<div class="arrow-container">
<div class="arrow-parts"></div>
</div>
</div>
CSS полный:
.container { position: relative; height: 100vh; }
.text-block {
position: absolute; top: 20%; left: 20%;
width: 300px;
}
.text-block::before {
content: ''; position: absolute; inset: 0;
background: rgba(0,0,0,0.6); z-index: -1;
}
.text-block__text {
position: relative; z-index: 1; color: white;
}
.arrow-container {
position: fixed; bottom: 0; left: 50%;
z-index: 20;
}
.arrow-parts {
position: relative; z-index: 10;
width: 100px; height: 100px;
background: linear-gradient(to right, red 0% 25%, transparent 25% 75%, red 75% 100%);
transform: translateY(100px);
animation: assemble 2s 1s forwards;
}
@keyframes assemble {
to { transform: translateY(-50px); opacity: 1; }
}
/* Триггер при скролле */
.arrow-container {
animation-play-state: paused;
}
JS для скролла — как выше. Стрелка собирается, не ныряя под текст, пустоты заполнены. Работает в Chrome, FF, Safari.
Что если мобильный скролл? Добавьте will-change: transform; для плавности.
Дополнительные хитрости для анимаций CSS
Иногда z-index всё равно бунтует? Проверьте overflow: hidden на родителях — оно режет контексты. Или используйте isolate в isolation: для изоляции слоёв (новое в CSS).
Для сложных анимаций CSS с несколькими стрелками — CSS Grid для позиционирования частей, анимация grid-template-columns для “сборки”. Быстрее JS.
И вопрос: а зачем opacity на всём блоке? Часто background с rgba хватит. Экономит производительность при скролле.
Тестируйте на реальных устройствах — мобильные браузеры строже к stacking.
Источники
- z-index - CSS: каскадные таблицы стилей
- То, что вам никто не говорил о z-index
- z-index CSS: Как всё правильно наложить? Гид с примерами!
- Свойство opacity в CSS: как задать прозрачность элементам на сайте
- Z-index не работает на opacity - Stack Overflow на русском
Заключение
В анимациях CSS с анимацией при скролле ключ — правильный z-index CSS плюс разделение opacity на псевдоэлементы и градиенты для пустот стрелки CSS. Стрелка больше не тонет в прозрачном блоке, цвета чистые. Начните с position и z-index, протестируйте stacking — и скролл засияет. Экспериментируйте, но помните: меньше контекстов — лучше производительность.