В веб-интерфейсах постоянно встречаются зоны со скроллом:
- сайдбары
- списки
- фильтры
- модальные окна
И у всех них есть одна и та же проблема:
👉 пользователь не всегда понимает, что блок можно прокрутить
Скролл есть, но визуально это никак не обозначено.
Особенно если скроллбар скрыт или минималистичный.
🎯 Решение
Добавить тени сверху и снизу, которые появляются в зависимости от позиции скролла:
- тень сверху → пользователь уже прокрутил
- тень снизу → есть ещё контент
Это простой паттерн, но он сильно улучшает UX без перегрузки интерфейса.
⚙️ Реализация на Vue
Ниже — компактный и реактивный компонент с использованием VueUse:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useScroll, useElementSize, useResizeObserver } from '@vueuse/core'
defineProps(['containerClass'])
const containerRef = ref<HTMLElement | null>(null)
const { y } = useScroll(containerRef)
const { height: clientHeight } = useElementSize(containerRef)
const scrollHeight = ref(0)
useResizeObserver(containerRef, ([entry]) => (scrollHeight.value = entry.target.scrollHeight))
const showTopShadow = computed(() => y.value > 0)
const showBottomShadow = computed(() => y.value + clientHeight.value < scrollHeight.value - 1)
</script>
<template>
<div class="relative">
<div
v-if="showTopShadow"
class="pointer-events-none absolute top-0 left-0 right-0 h-4 bg-gradient-to-b from-black/10 to-transparent z-10"
/>
<div ref="containerRef" class="overflow-auto relative" :class="containerClass">
<slot />
</div>
<div
v-if="showBottomShadow"
class="pointer-events-none absolute bottom-0 left-0 right-0 h-4 bg-gradient-to-t from-black/10 to-transparent z-10"
/>
</div>
</template>🧠 Как это работает
Компонент опирается на три ключевые вещи:
1. Позиция скролла
const { y } = useScroll(containerRef)
y— текущийscrollTop
2. Видимая высота контейнера
const { height: clientHeight } = useElementSize(containerRef)
- сколько контента видно прямо сейчас
3. Полная высота контента
useResizeObserver(containerRef, ([entry]) => (
scrollHeight.value = entry.target.scrollHeight
))
- вся высота, включая скрытую часть
🧩 Логика отображения теней
Верхняя тень
y.value > 0
Если пользователь прокрутил вниз — показываем.
Нижняя тень
y.value + clientHeight.value < scrollHeight.value - 1
Если есть ещё контент снизу — показываем.
Небольшой -1 — это защита от погрешностей округления.
❗ Почему здесь используется VueUse
Можно было бы написать всё вручную через DOM, но:
useScroll→ уже реактивный scrolluseElementSize→ отслеживает размерuseResizeObserver→ реагирует на изменение контента
👉 в итоге получаем:
- меньше кода
- меньше багов
- лучше читаемость
⚠️ Важный UX-нюанс
pointer-events: none;
Обязательно для теней.
👉 иначе они будут перекрывать контент и ломать клики
🎨 Визуальная часть
Тени — это обычные градиенты:
bg-gradient-to-b from-black/10 to-transparent
bg-gradient-to-t from-black/10 to-transparent
Можно легко адаптировать под:
- тёмную тему
- брендовые цвета
- более мягкий или жёсткий эффект
🧩 Где это реально полезно
Этот паттерн особенно хорошо заходит в:
- админках
- таблицах
- списках заказов
- фильтрах
- чатах
- модалках
💡 Вывод
Scroll-контейнер без подсказки — это скрытая проблема UX.
Но решение максимально простое:
показывать состояние скролла через тени
С помощью VueUse это реализуется буквально за несколько строк — и при этом работает корректно во всех сценариях:
- scroll
- resize
- динамический контент
Если коротко:
👉 маленький визуальный эффект → заметное улучшение интерфейса


Добавить комментарий