context API は、データや関数をプロパティとして渡したり、たくさんのイベントをディスパッチしたりすることなく、コンポーネント同士で'会話'するための仕組みを提供します。これは高度ですが、便利な機能です。この演習では、generative art のパイオニアである George Nees の Schotter を、context API を使って再現してみましょう。
Canvas.svelte
にはアイテムを canvas に追加する addItem
関数があります。これを <Canvas>
内のコンポーネント (例えば <Square>
) で利用できるようにするには、setContext
を使います:
Canvas.svelte
<script>
import { setContext, afterUpdate, onMount, tick } from 'svelte';
// ...
onMount(() => {
ctx = canvas.getContext('2d');
});
setContext('canvas', {
addItem
});
function addItem(fn) {...}
function draw() {...}
</script>
子コンポーネントでは、この context を getContext
で取得できます:
Square.svelte
<script>
import { getContext } from 'svelte';
export let x;
export let y;
export let size;
export let rotate;
getContext('canvas').addItem(draw);
function draw(ctx) {...}
</script>
ここまでは、そう…退屈ですよね。グリッドにランダム性を追加してみましょう:
App.svelte
<div class="container">
<Canvas width={800} height={1200}>
{#each Array(12) as _, c}
{#each Array(22) as _, r}
<Square
x={180 + c * 40 + jitter(r * 2)}
y={180 + r * 40 + jitter(r * 2)}
size={40}
rotate={jitter(r * 0.05)}
/>
{/each}
{/each}
</Canvas>
</div>
ライフサイクル関数と同様に、setContext
と getContext
はコンポーネントの初期化中に呼び出す必要があります。(context のキー (この演習の場合は 'canvas'
) には文字列以外も含め、好きなものを指定でき、context にアクセスする人をコントロールするのに有用です。)
context オブジェクトには、store を含めあらゆるものを含めることができます。これにより、時間の経過とともに変化する値を子コンポーネントに渡すことができます:
// in a parent component
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
setContext('my-context', {
count: writable(0)
});
// in a child component
import { getContext } from 'svelte';
const { count } = getContext('my-context');
$: console.log({ count: $count });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<script>
import Canvas from './Canvas.svelte';
import Square from './Square.svelte';
// we use a seeded random number generator to get consistent jitter
let seed = 1;
function random() {
seed *= 16807;
seed %= 2147483647;
return (seed - 1) / 2147483646;
}
function jitter(amount) {
return amount * (random() - 0.5);
}
</script>
<div class="container">
<Canvas width={800} height={1200}>
{#each Array(12) as _, c}
{#each Array(22) as _, r}
<Square
x={180 + c * 40}
y={180 + r * 40}
size={40}
/>
{/each}
{/each}
</Canvas>
</div>
<style>
.container {
height: 100%;
aspect-ratio: 2 / 3;
margin: 0 auto;
background: rgb(224, 219, 213);
filter: drop-shadow(0.5em 0.5em 1em rgba(0, 0, 0, 0.1));
}
</style>