Kurs Svelte i SvelteKit · Moduł 10: Akcje

Popularne wzorce Actions

2 min czytania ·
W tej lekcji5

Lazy Loading obrazów

export function lazyLoad(node, src) {
  const observer = new IntersectionObserver(entries => {
    if (entries[0].isIntersecting) {
      node.src = src;
      observer.disconnect();
    }
  });

  observer.observe(node);

  return {
    destroy() {
      observer.disconnect();
    }
  };
}
<img use:lazyLoad={'/images/viking.jpg'} alt="Viking" />

Auto Focus

export function autoFocus(node) {
  node.focus();
}
<input use:autoFocus placeholder="Auto focused!" />

Portal

export function portal(node, target = 'body') {
  const targetEl = document.querySelector(target);
  targetEl.appendChild(node);

  return {
    destroy() {
      node.remove();
    }
  };
}
<div use:portal={'#modals'}>
  Modal content
</div>

Longpress

export function longpress(node, duration = 500) {
  let timer;

  function handleMouseDown() {
    timer = setTimeout(() => {
      node.dispatchEvent(new CustomEvent('longpress'));
    }, duration);
  }

  function handleMouseUp() {
    clearTimeout(timer);
  }

  node.addEventListener('mousedown', handleMouseDown);
  node.addEventListener('mouseup', handleMouseUp);
  node.addEventListener('mouseleave', handleMouseUp);

  return {
    update(newDuration) {
      duration = newDuration;
    },
    destroy() {
      clearTimeout(timer);
      node.removeEventListener('mousedown', handleMouseDown);
      node.removeEventListener('mouseup', handleMouseUp);
      node.removeEventListener('mouseleave', handleMouseUp);
    }
  };
}
<button use:longpress={1000} onlongpress={() => alert('Long pressed!')}>
  Hold me for 1 second
</button>

W Svelte 5 własne zdarzenie wysłane z akcji przez dispatchEvent obsłużysz zwykłym atrybutem: on + nazwa zdarzenia, czyli onlongpress.

TypeScript Actions

import type { Action } from 'svelte/action';

interface TooltipParams {
  text: string;
  position?: 'top' | 'bottom';
}

export const tooltip: Action<HTMLElement, TooltipParams> = (node, params) => {
  // ... implementation

  return {
    update(newParams: TooltipParams) {
      // ...
    },
    destroy() {
      // ...
    }
  };
};

// Trzeci parametr typu opisuje własne zdarzenia akcji
export const longpress: Action<HTMLElement, number, {
  onlongpress: (e: CustomEvent) => void;
}> = (node, duration = 500) => {
  // ...
};
Kod do tej lekcji: App.svelte
<script>
  // Praktyczne akcje - resize observer, intersection observer

  let width = $state(0);
  let height = $state(0);
  let visible = $state(false);

  // Resize observer action
  function resizeObserver(node, callback) {
    const observer = new ResizeObserver(entries => {
      for (const entry of entries) {
        callback(entry.contentRect);
      }
    });

    observer.observe(node);

    return {
      destroy() {
        observer.disconnect();
      }
    };
  }

  // Intersection observer action
  function inview(node, callback) {
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        callback(entry.isIntersecting);
      });
    }, { threshold: 0.5 });

    observer.observe(node);

    return {
      destroy() {
        observer.disconnect();
      }
    };
  }

  function handleResize(rect) {
    width = Math.round(rect.width);
    height = Math.round(rect.height);
  }

  function handleInview(isVisible) {
    visible = isVisible;
  }
</script>

<main class="observer-demo">
  <h1>Observer Actions</h1>

  <section
    class="resizable"
    use:resizeObserver={handleResize}
  >
    <h2>ResizeObserver</h2>
    <p>Szerokość: <strong>{width}px</strong></p>
    <p>Wysokość: <strong>{height}px</strong></p>
    <p class="hint">Zmień rozmiar okna przeglądarki</p>
  </section>

  <div class="spacer">
    <p>↓ Przewiń w dół ↓</p>
  </div>

  <section
    class="observed"
    class:visible
    use:inview={handleInview}
  >
    <h2>IntersectionObserver</h2>
    <p>Status: <strong>{visible ? 'Widoczny!' : 'Ukryty'}</strong></p>
  </section>
</main>

<style>
  .observer-demo {
    font-family: 'Georgia', serif;
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
    background: #1a1a2e;
    color: #e8e8f7;
  }

  h1 {
    color: #7cb3f0;
    text-align: center;
  }

  section {
    background: rgba(124, 179, 240, 0.1);
    border: 1px solid #7cb3f0;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  h2 {
    color: #7cb3f0;
    font-size: 1rem;
    margin-top: 0;
  }

  strong {
    color: #ffd700;
  }

  .hint {
    font-size: 0.85rem;
    color: #6a8ab9;
  }

  .spacer {
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6a8ab9;
  }

  .observed {
    transition: all 0.3s;
    opacity: 0.5;
    transform: scale(0.95);
  }

  .observed.visible {
    opacity: 1;
    transform: scale(1);
    border-color: #ffd700;
  }
</style>

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij action, która automatycznie focusuje element

Przydatne artykuły