Qwik インテグレーション
@tsparticles/qwik パッケージは、Qwik の再開可能モデルに最適化された <Particles> コンポーネントを提供します。遅延初期化には useVisibleTask$ を、リアクティブ更新にはシグナルを使用します。
インストール
npm install @tsparticles/qwik tsparticlesTypeScript 宣言が含まれています — 追加の型パッケージは必要ありません。
エンジンの初期化
Qwik では、エンジンは useVisibleTask$ ブロック内で初期化する必要があります。これにより、クライアント上でのみ実行されることが保証されます(SSR 中は決して実行されません)。準備状態を追跡するにはシグナルを使用します:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
return <>{engineReady.value && <Particles id="tsparticles" options={{}} />}</>;
});基本的な使い方
エンジンの準備ができたら、設定とともに <Particles> コンポーネントをレンダリングします:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
const options = {
background: {
color: "#0d1117",
},
particles: {
color: { value: "#58a6ff" },
links: {
enable: true,
color: "#58a6ff",
distance: 150,
},
move: {
enable: true,
speed: 2,
},
number: {
value: 80,
},
},
};
return <>{engineReady.value && <Particles id="tsparticles" options={options} />}</>;
});条件付きレンダリング
engineReady シグナルパターンにより、<Particles> コンポーネントはエンジンが完全に初期化された後にのみマウントされます。これにより、サーバーとクライアント間のハイドレーションの不一致を防ぎます:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
const loading = useSignal(true);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
loading.value = false;
});
return (
<div style={{ position: "relative", width: "100%", height: "100vh" }}>
{loading.value && (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "100%",
color: "#888",
}}
>
Loading particles...
</div>
)}
{engineReady.value && (
<Particles
id="tsparticles"
options={{
background: { color: "#0d1117" },
fullScreen: { enable: true, zIndex: -1 },
particles: {
color: { value: "#58a6ff" },
links: { enable: true, color: "#58a6ff", distance: 150 },
move: { enable: true, speed: 2 },
number: { value: 80 },
},
}}
/>
)}
</div>
);
});インタラクティブパーティクル
オプションに interactivity セクションを追加して、ホバーとクリックのインタラクションを有効にします:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
const options = {
background: { color: "#0d1117" },
fullScreen: { enable: true },
particles: {
color: { value: "#58a6ff" },
links: { enable: true, color: "#58a6ff", distance: 150 },
move: { enable: true, speed: 1.5 },
number: { value: 100 },
size: { value: { min: 1, max: 4 } },
opacity: { value: 0.6 },
},
interactivity: {
events: {
onHover: { enable: true, mode: "grab" },
onClick: { enable: true, mode: "push" },
resize: { enable: true },
},
modes: {
grab: { distance: 180, links: { opacity: 0.5 } },
push: { quantity: 4 },
},
},
};
return <>{engineReady.value && <Particles id="tsparticles" options={options} />}</>;
});カスタム設定
アニメーション、複数色、豊富なインタラクティビティを備えた完全な設定:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
const options: ISourceOptions = {
background: { color: "#0d1117" },
fpsLimit: 60,
fullScreen: { enable: true, zIndex: -1 },
particles: {
color: {
value: ["#ff5733", "#33ff57", "#3357ff", "#f3f333"],
},
links: {
color: "random",
enable: true,
opacity: 0.3,
distance: 120,
width: 1,
},
move: {
enable: true,
speed: 2,
direction: "none",
random: true,
straight: false,
outModes: "bounce",
attract: {
enable: false,
rotateX: 600,
rotateY: 1200,
},
},
number: {
value: 120,
density: { enable: true },
},
opacity: {
value: 0.8,
animation: {
enable: true,
speed: 0.5,
minimumValue: 0.1,
sync: false,
},
},
size: {
value: { min: 1, max: 6 },
animation: {
enable: true,
speed: 3,
minimumValue: 1,
sync: false,
},
},
twinkle: {
particles: {
enable: true,
frequency: 0.05,
opacity: 0.5,
},
},
},
interactivity: {
events: {
onHover: { enable: true, mode: "repulse" },
onClick: { enable: true, mode: "push" },
resize: { enable: true },
},
modes: {
repulse: { distance: 120, duration: 0.4 },
push: { quantity: 4 },
},
},
detectRetina: true,
};
return <>{engineReady.value && <Particles id="tsparticles" options={options} />}</>;
});TypeScript
@tsparticles/qwik パッケージは完全な型をエクスポートします。型安全な設定には ISourceOptions を、初期化コールバックには Engine を使用します:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine, ISourceOptions } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
const options: ISourceOptions = {
background: { color: "#000" },
particles: {
number: { value: 50 },
color: { value: "#fff" },
},
};
return <>{engineReady.value && <Particles id="tsparticles" options={options} />}</>;
});Reactive Behavior
The <Particles> component reacts to prop changes at runtime:
id,options, orurlchange → the existing container is destroyed and particles are reloaded with the new values.themechange →loadThemeis called on the existing container. This requires the optional@tsparticles/plugin-themespackage to be loaded (otherwise it is a safe no-op).
On component unmount, the particles container is automatically destroyed — no orphan animations remain.
遅延読み込み
Qwik の再開可能モデルでは、パーティクルコードはコンポーネントがビューポートに表示されたときにのみロードおよび実行されます。useVisibleTask$ フックがエンジン初期化をトリガーし、<Particles> コンポーネント自体はインポート時に Qwik によって自動的にコード分割されます:
import { component$, useSignal, useVisibleTask$, $ } from "@builder.io/qwik";
import type { Engine } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
return (
<div>
{engineReady.value && (
<Particles
id="tsparticles"
options={{
background: { color: "#0d1117" },
}}
/>
)}
</div>
);
});コールバックを配線する際には、Qwik 最適化されたイベントハンドラーに $ サフィックス規則を使用します:
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik";
import { Particles, initParticlesEngine } from "@tsparticles/qwik";
import type { Engine, Container } from "@tsparticles/engine";
import { loadFull } from "tsparticles";
export default component$(() => {
const engineReady = useSignal(false);
const containerRef = useSignal<Container | undefined>();
useVisibleTask$(async () => {
await initParticlesEngine(async (engine: Engine) => {
await loadFull(engine);
});
engineReady.value = true;
});
const handleParticlesLoaded = $((container?: Container) => {
containerRef.value = container;
console.log("Particles loaded:", container?.id);
});
return (
<>
{engineReady.value && (
<Particles
id="tsparticles"
options={{ background: { color: "#0d1117" } }}
particlesLoaded={handleParticlesLoaded}
/>
)}
</>
);
});このアプローチにより、パーティクルアニメーションが完全にツリーシェイク可能になり、必要なときにのみクライアントに配信されます。
