React: створюємо хук для прогресивного завантаження зображень

4 хв. читання

Ми регулюємо кожен кілобайт наших JavaScript-пакетів, водночас забуваючи про оптимізацію завантаження зображень. Усе закінчується тим, що користувач сидить декілька секунд і спостерігає білий фон, перш ніж завантажиться фонове зображення.

У статті розглянемо, як створити хук, що дбатиме про прогресивне завантаження зображень замість вас.

Що таке прогресивне завантаження зображень

Прогресивне завантаження зображень (у нашому контексті) — завантаження спочатку зображення з низькою роздільною здатністю, поки його версія з високою роздільною здатністю завантажується у фоновому режимі. Після завершення процесу зображення міняються місцями.

Назвемо наш хук useProgressiveImage і передамо йому об'єкт з src prop та fallbackSrc prop. Він поверне src найкращого з вже завантажених зображень або null, якщо жодне зображення не завантажилось.

function useProgressiveImage({ src, fallbackSrc }) {
  return null;
}

Ми можемо попередньо завантажити подібні зображення, створивши екземпляр Image та встановивши його атрибут src. Визначаємо слухача для події onload і реагуємо відповідно.

Напишемо спочатку деякий шаблонний код:

function useProgressiveImage({ src, fallbackSrc }) {
  const mainImage = new Image();
  const fallbackImage = new Image();

  mainImage.onload = () => {}; //  todo
  fallbackImage.onload = () => {}; //  todo

  mainImage.src = src;
  fallbackImage.src = fallbackSrc;

  return null;
}

Наведений фрагмент буде запускатися при кожному рендері, що призведе до непотрібних запитів. Аби покращити ситуацію, розташуємо код всередині useEffect і запускатимемо його, лише якщо src або fallbackSrc prop змінюється.

function useProgressiveImage({ src, fallbackSrc }) {
  React.useEffect(() => {
    const mainImage = new Image();
    const fallbackImage = new Image();

    mainImage.onload = () => {}; // Still todo
    fallbackImage.onload = () => {}; // Still todo

    mainImage.src = src;
    fallbackImage.src = fallbackSrc;
  }, [src, fallbackSrc]);

  return null;
}

Далі нам треба відстежувати зображення, котрі вже були завантажені. Звичайно ж, ми не хочемо, щоб резервне зображення замінило основне, якщо воно завантажиться першим (через кешування або просто випадковість), тому реалізуємо певну логіку.

Відстежувати стан будемо з хуком React.useReducer, який приймає функцію-редьюсер. Її параметри — попередній стан (завантажуване джерело), а повертає функція новий стан, залежно від типу події, переданої в dispatch.

function reducer(currentSrc, action) {
  if (action.type === 'main image loaded') {
    return action.src;
  } 
  if (!currentSrc) {
    return action.src;
  }
  return currentSrc;
}

function useProgressiveImage({ src, fallbackSrc }) {
  const [currentSrc, dispatch] = React.useReducer(reducer, null);
  React.useEffect(() => {
    const mainImage = new Image();
    const fallbackImage = new Image();

    mainImage.onload = () => {
      dispatch({ type: 'main image loaded', src });
    };
    fallbackImage.onload = () => {
      dispatch({ type: 'fallback image loaded', src: fallbackSrc });
    };

    mainImage.src = src;
    fallbackImage.src = fallbackSrc;
  }, [src, fallbackSrc]);

  return currentSrc;
}

Тут ми реалізували два типи подій: «завантажено основне зображення» та «завантажено резервне зображення». Бізнес-логіку полишаємо на наш редьюсер, який і вирішуватиме, коли оновлювати джерело, а коли лишити його незмінним.

Щодо типів подій

Напевно, ви звикли до вигляду типів подій, на зразок CONSTANT_CASE або, принаймні, camelCase. Виявляється, ви можете називати їх, як хочете. Оскільки події знаходяться всередині цього невеликого хука, не важливо, як саме ви їх назвете.

const HeroImage = props => {
  const src = useProgressiveImage({ 
    src: props.src,
    fallbackSrc: props.fallbackSrc 
  });
  if (!src) return null;
  return <img className="hero" alt={props.alt} src={src} />;
};

Результат можна протестувати за посиланням.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація