React Scroll Hook с тенями

Вы когда-нибудь думали о том, чтобы украсить прокручиваемые области красивыми тенями? Да, во всех браузерах есть полоса прокрутки, но мы можем заставить прокручиваемый контент сиять еще лучше и, в то же время, улучшить удобство использования. Я думаю, что тени добавляют плавность и превосходное ощущение нашему контенту и помогают, показывая пользователям, что «здесь внизу больше контента» или «здесь наверху больше контента».

Если вам приходила в голову та же мысль (я предполагаю, что да, поскольку вы уже читаете это), но вы еще не нашли решение, вам стоит прочитать дальше.

Крючок для прокрутки

Недавно я сделал этот хук, который добавляет внутренние тени при прокрутке элемента с переполненным содержимым. Мы разберем код на протяжении всего поста, посмотрим, как он работает, и как мы используем его в нашем коде.

import { useState } from 'react';

export function useScrollWithShadow() {
  const [scrollTop, setScrollTop] = useState(0);
  const [scrollHeight, setScrollHeight] = useState(0);
  const [clientHeight, setClientHeight] = useState(0);

  const onScrollHandler = (event) => {
    setScrollTop(event.target.scrollTop);
    setScrollHeight(event.target.scrollHeight);
    setClientHeight(event.target.clientHeight);
  };

  function getBoxShadow() {
    const isBottom = clientHeight === scrollHeight - scrollTop;
    const isTop = scrollTop === 0;
    const isBetween = scrollTop > 0 && clientHeight < scrollHeight - scrollTop;

    let boxShadow = 'none';
    const top = 'inset 0 8px 5px -5px rgb(200 200 200 / 1)';
    const bottom = 'inset 0 -8px 5px -5px rgb(200 200 200 / 1)';

    if (isTop) {
      boxShadow = bottom;
    } else if (isBetween) {
      boxShadow = `${top}, ${bottom}`;
    } else if (isBottom) {
      boxShadow = top;
    }
    return boxShadow;
  }

  return { boxShadow: getBoxShadow(), onScrollHandler };
}
Вход в полноэкранный режим Выход из полноэкранного режима

Обработка событий прокрутки

Во-первых, давайте рассмотрим логику прокрутки в части useScrollWithShadow-hook.

import { useState } from "react";

export function useScrollWithShadow() {
  const [scrollTop, setScrollTop] = useState(0);
  const [scrollHeight, setScrollHeight] = useState(0);
  const [clientHeight, setClientHeight] = useState(0);

  const onScrollHandler = (event) => {
    setScrollTop(event.target.scrollTop);
    setScrollHeight(event.target.scrollHeight);
    setClientHeight(event.target.clientHeight);
  };

  return { onScrollHandler };
}
Вход в полноэкранный режим Выход из полноэкранного режима

onScroll — это свойство React, которое мы можем получить на любом элементе HTML для прослушивания событий прокрутки, например, на div. Оно принимает функцию и возвращает событие целевого элемента. На родном JavaScript это было бы эквивалентно element.addEventListener('scroll', () => /* do something*/).

Метод onScrollHandler (также экспортируемый из хука) мы можем передать в качестве значения в свойство onScroll элемента. При прокрутке элемента срабатывает метод onScrollHandler, затем мы берем свойства scrollTop, scrollHeight и clientHeight из цели события и устанавливаем их как значения для соответствующих состояний.

  • clientHeight (видимое содержимое) — это внутренняя высота элемента, включая padding. Мы можем вычислить ее по CSS height + CSS padding. Если присутствует горизонтальная полоса прокрутки, внутренняя высота включает высоту полосы прокрутки.
  • scrollHeight (видимое + скрытое содержимое) — это высота видимого содержимого элемента, включая скрытое содержимое, не видимое на экране для переполненного элемента.
  • scrollTop — это количество пикселей, прокручиваемых по вертикали на содержимом элемента. Для измерения значения берется расстояние от вершины элемента до самой верхней части видимого содержимого.

Вычисление положения тени

Мы должны произвести некоторые вычисления, чтобы определить, следует ли нам показывать тени сверху, снизу или и там, и там. У нас будут тени сверху и снизу элемента, когда мы не находимся в верхней или нижней части прокрутки.

const isBottom = clientHeight === scrollHeight - scrollTop;
const isTop = scrollTop === 0;
const isBetween = 
     scrollTop > 0 && clientHeight < scrollHeight - scrollTop;
Вход в полноэкранный режим Выход из полноэкранного режима
  • isBottom: true, когда внутренняя высота видимого элемента равна общей высоте видимого и скрытого содержимого минус (-) пикселей, прокрученных от верха элемента.
  • isTop: true, если прокрутка еще не производилась, или scrollTop равен 0 пикселей.
  • isBetween: true, когда мы прокрутили более 0 пикселей сверху, и внутренняя высота видимого элемента меньше (<), чем высота видимого плюс (+) скрытого содержимого минус пиксели, прокрученные сверху.

Ниже приведен пример элемента, имеющего внутреннюю высоту 400 пикселей, и мы прокрутили 150 пикселей сверху; мы должны получить, что isBetween true:

// Some element inner height: 400px
// Totalt height of the visible and hidden content: 700px
// Pixels scrolled from top of the element: 150px

const isBottom = 400 === 700 - 150;           // false
const isTop = 150 === 0;                      // false
const isBetween = 150 > 0 && 400 < 700 - 150; // true
Вход в полноэкранный режим Выход из полноэкранного режима

Определение типа тени на основе вычисленной позиции

Наша функция getBoxShadow() обрабатывает логику возврата правильного значения CSS box-shadow в виде строки. Это значение мы хотим установить для целевого элемента при прокрутке. Здесь мы будем использовать переменные вычисления, чтобы определить, когда мы должны использовать различные тени:

function getBoxShadow() {
    const isBottom = clientHeight === scrollHeight - scrollTop;
    const isTop = scrollTop === 0;
    const isBetween = 
     scrollTop > 0 && clientHeight < scrollHeight - scrollTop;    let boxShadow = "none";
    const top = "inset 0 8px 5px -5px rgb(200 200 200 / 1)";
    const bottom = "inset 0 -8px 5px -5px rgb(200 200 200 / 1)";

    if (isTop) {
      boxShadow = bottom;
    } else if (isBetween) {
      boxShadow = `${top}, ${bottom}`;
    } else if (isBottom) {
      boxShadow = top;
    }
    return boxShadow;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Мы хотим установить нижнюю тень, если мы находимся в верхней части элемента. Если мы находимся между (как мы вычислили в предыдущем примере кода), мы хотим показать и верхнюю, и нижнюю тени, или же, если мы находимся в нижней части элемента, мы хотим показать верхнюю тень.

Использование крючка прокрутки

Чтобы использовать крючок, нам нужно создать компонент, который может его использовать. В блоке кода ниже у нас есть компонент под названием App. Мы импортируем хук useScrollWithShadow, разрушаем метод onScrollHandler() и переменную boxShadow. Мы передаем onScrollHandler() в качестве значения свойства div’ onScroll и устанавливаем boxShadow в качестве инлайн-стиля.

import { useScrollWithShadow } from "./hooks/useScrollWithShadow";

export function App() {
  const { boxShadow, onScrollHandler } = useScrollWithShadow();

  return (
    <div onScroll={onScrollHandler} style={{ boxShadow }}>
      /* ... Scroll area here ... */
    </div>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Без некоторых CSS это не будет работать очень хорошо. Посмотрите конечный результат для стилизованного решения с содержимым и исходным кодом на Codesandbox.

Окончательный результат

Вот окончательный результат. Нажмите «toggle shadows» для переключения между режимами с тенями и без них (исходный код на Codesandbox):

Спасибо, что читаете! Код для шизы

(Эта статья была первоначально опубликована на Medium)

Оцените статью
Procodings.ru
Добавить комментарий