Приложение Svelte: создание компонента наложения загрузки с помощью svelte/motion

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

Сначала давайте набросаем разметку HTML и CSS. Это довольно просто. Я создаю блок, фиксирую его положение, растягиваю до полноэкранного размера и центрирую его содержимое. Обратите внимание, как мы используем градиент svg для заливки текста разными цветами.

<div class="overlay">
    <svg width="115" height="32" viewBox="0 0 78 32" xmlns="http://www.w3.org/2000/svg">
        <defs>
            <linearGradient id="filler">
                <stop offset="50%" stop-color="#ff006e" />
                <stop offset="50%" stop-color="white" />
            </linearGradient>
        </defs>

        <text x="0" y="24px" fill="url(#filler)">Loading...</text>
    </svg>
</div>

<style>
  .overlay {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 10001;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #3a86ff;
    font-family: "Arial", sans-serif;
    font-size: 1.375rem;
  }
</style>
Вход в полноэкранный режим Выход из полноэкранного режима

Круто. Мы закончили с разметкой. Теперь перейдем к анимации. Для этого мы будем использовать стандартные инструменты Svelte. Используя tweened из svelte/motion, создадим переменную progress и используем ее значение в градиенте svg.

const progress = tweened(0, { easing: linear, duration: 3000 });
Вход в полноэкранный режим Выход из полноэкранного режима
  <linearGradient id="filler">
    <stop offset={`${$progress}%`} stop-color="#ff006e" />
    <stop offset={`${$progress}%`} stop-color="white" />
  </linearGradient>
Войдите в полноэкранный режим Выход из полноэкранного режима

Далее создадим функцию animate, которую мы будем вызывать при монтировании компонента. Мы сделаем эту функцию рекурсивной, чтобы анимация продолжалась бесконечно. Как и любая другая рекурсивная функция, она должна иметь точку выхода. Поэтому, чтобы избежать утечки памяти, мы позаботимся о том, чтобы выполнение функции заканчивалось при уничтожении экземпляра компонента.

  let destroyed = false

  async function animate() {
    if (destroyed) {
      return
    }

    await width.set(100)
    await width.set(0, { duration: 0 })
    await animate()
  }

  onMount(() => {
    animate()
  })

  onDestroy(() => {
    destroyed = true
  })
Вход в полноэкранный режим Выход из полноэкранного режима

Отлично. Наш компонент уже выглядит хорошо. Теперь мы улучшим получившуюся анимацию, сделав ее двухэтапной, где на первом этапе мы закрашиваем белый текст розовым, и наоборот, на этапе №2 мы закрашиваем розовый текст белым. Для этого мы создадим хранилище writable с булевым значением, которое мы будем использовать для обозначения текущей стадии анимации. С каждым вызовом функции animate мы будем менять это значение на противоположное.

  const isFirstStage = writable(true)

  async function animate() {
    if (destroyed) {
      return
    }

    await width.set(100)
    await width.set(0, { duration: 0 })
    isFirstStage.update((value) => !value)
    await animate()
  }
Войти в полноэкранный режим Выход из полноэкранного режима
<linearGradient id="filler">
  <stop offset={`${$progress}%`} stop-color={$isFirstStage ? '#ff006e' : 'white'} />
  <stop offset={`${$progress}%`} stop-color={$isFirstStage ? 'white' : '#ff006e'} />
</linearGradient>
Войти в полноэкранный режим Выход из полноэкранного режима

Итак, мы закончили с самим компонентом индикатора загрузки. Теперь представим, что может существовать несколько компонентов, использующих наш индикатор. Мы могли бы отображать наш оверлей в каждом из них, но давайте лучше усовершенствуем наш подход и создадим функциональность, которая позволит нам отображать индикатор один раз и обеспечит функции для включения и выключения индикатора. Для этого мы создадим еще один writable store, который будет содержать запросы на отображение индикатора. Сам индикатор будет отображаться до тех пор, пока в хранилище остается хотя бы один запрос.

import { writable, derived } from "svelte/store";
import { nanoid } from "nanoid";

export const loadingOverlayQueue = writable([]);

export function showLoadingOverlay() {
  const newRequestId = nanoid();

  loadingOverlayQueue.update((currentValue) => [...currentValue, newRequestId]);

  return newRequestId;
}

export function hideLoadingOverlay(requestId) {
  loadingOverlayQueue.update((currentValue) =>
    currentValue.filter((item) => item !== requestId)
  );
}

export const isLoadingOverlayShown = derived(
  loadingOverlayQueue,
  (requests) => requests.length > 0
);
Вход в полноэкранный режим Выход из полноэкранного режима

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

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