Scroll-контейнер с тенями на Vue: простой UX-паттерн, который реально улучшает интерфейс

от автора

в
Время чтения: 2 мин.

В веб-интерфейсах постоянно встречаются зоны со скроллом:

  • сайдбары
  • списки
  • фильтры
  • модальные окна

И у всех них есть одна и та же проблема:

👉 пользователь не всегда понимает, что блок можно прокрутить

Скролл есть, но визуально это никак не обозначено.
Особенно если скроллбар скрыт или минималистичный.


🎯 Решение

Добавить тени сверху и снизу, которые появляются в зависимости от позиции скролла:

  • тень сверху → пользователь уже прокрутил
  • тень снизу → есть ещё контент

Это простой паттерн, но он сильно улучшает 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 → уже реактивный scroll
  • useElementSize → отслеживает размер
  • 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
  • динамический контент

Если коротко:

👉 маленький визуальный эффект → заметное улучшение интерфейса


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Сколько будет 6 + 2?