picola
ドキュメント

レシピ

サムネイルから飛び出すトランジション、カスタムツールバーとキャプション、遅延読み込み、リアクティブなスライド、ヘッドレスモード。

よく使われるパターンを紹介します。いずれもクイックスタートを土台にしています。どのレシピも任意です。picola は slidesbind:open だけでも動作します。

サムネイルから飛び出す

アタッチメントでサムネイルを登録すると、開閉のトランジションがそのサムネイルを起点・終点としてアニメーションします。

<script lang="ts">
import { Lightbox, createGalleryOrigins } from 'picola/svelte';
const gallery = createGalleryOrigins();
</script>
{#each slides as slide, i}
<img {@attach gallery.attach(i)} src={slide.placeholder} alt={slide.alt} />
{/each}
<Lightbox bind:open bind:index {slides} origin={gallery.origin} />

シンプルな origin={(i) => thumbEls[i]} リゾルバでも動作します。origin が object-fit: cover<img> の場合、トランジションは単純な拡大縮小ではなく、クリップによる展開としてアニメーションします。

カスタムツールバーボタン

toolbar スニペットは、完全に型付けされたコンテキストを受け取ります。コアの画像ビューワー、アクティブなスライド(meta を含む)、ナビゲーションヘルパーが含まれます。

<script lang="ts">
import { Lightbox, ToolbarButton } from 'picola/svelte';
import Download from '@lucide/svelte/icons/download';
</script>
<Lightbox bind:open bind:index {slides}>
{#snippet toolbar(ctx)}
<ToolbarButton label="Download" onclick={() => download(ctx.slide)}>
<Download />
</ToolbarButton>
{/snippet}
</Lightbox>

カスタムかつインタラクティブなキャプション

スライドに alt があると、picola は行数制限付きのキャプションを表示します(タップで展開、ツールバーのトグルでも切り替え)。caption スニペットで丸ごと置き換えでき、コンテキストに加えて expandedtoggle() を受け取ります。showCaption={false} で無効化もできます。

<Lightbox bind:open {slides}>
{#snippet caption(ctx)}
<MyCaption text={ctx.slide?.alt} expanded={ctx.expanded} ontoggle={ctx.toggle} />
{/snippet}
</Lightbox>

キャプションのタップで独自の全文モーダルを開きたい場合は、モーダルをスニペット内にレンダリングします(Lightbox のルート内に置かれるため、重なり順とフォーカストラップはそのまま機能します)。表示中は picola のキーボード処理を一時停止し、Escape がまず自分のモーダルに届くようにします。

{#snippet caption(ctx)}
<button onclick={() => { altModal = true; ctx.setKeyboardEnabled(false); }}>
{ctx.slide?.alt}
</button>
{#if altModal}
<MyAltModal
text={ctx.slide?.alt}
onclose={() => { altModal = false; ctx.setKeyboardEnabled(true); }}
/>
{/if}
{/snippet}

ユーザーがズームするまで完全な解像度の読み込みを遅延する

表示されるすべてのスライドについて原寸画像を取得するのではなく、実際のズーム操作の意図があるまで読み込みを遅延させます。zoomintent は、最初のピンチ、ダブルタップ、ホイールズーム、または + の操作時に、スライドごとに一度だけ発火します。

<Lightbox
bind:open
{slides}
onzoomintent={async ({ index }) => {
const url = await fetchOriginalUrl(index);
lightbox.updateSlide(index, { src: url });
}}
bind:this={lightbox}
/>

新しいソースがデコードされる間も、前の画像は表示されたまま(ダブルバッファリング)なので、ズームの途中でもシームレスに入れ替わります。

開いた後に解像度をアップグレードする

手元にあるもので即座に開き、その後より高品質なソースに入れ替えます。ズームとパンは入れ替えの前後で保持されます。

<Lightbox
bind:open
{slides}
onchange={async ({ index }) => {
const url = await fetchOriginalUrl(index);
lightbox.updateSlide(index, { src: url });
}}
bind:this={lightbox}
/>

リアクティブなスライドとページネーション

slides プロパティはリアクティブです。配列を置き換えると(ページネーション後の画像の追加、並べ替え、フィルタリングなど)、開いている画像ビューワーに同期され、アクティブなインデックスがクランプされ、ズーム状態が保持されます。

<script lang="ts">
let slides = $state<Slide[]>(firstPage);
async function onchange({ index }: { index: number }) {
if (index >= slides.length - 2) {
slides = [...slides, ...(await fetchNextPage())];
}
}
</script>
<Lightbox bind:open bind:index {slides} {onchange} />

ヘッドレスモード

chrome={false} を指定すると、picola は画像の表示面(ジェスチャー、ズーム、トランジション)のみをレンダリングし、組み込みの UI は一切レンダリングしません。bind:viewer を介して、すべてを自分のコンポーネントから操作できます。

<script lang="ts">
import { Lightbox, type Viewer } from 'picola/svelte';
let open = $state(false);
let viewer = $state<Viewer | null>(null);
</script>
<Lightbox bind:open bind:viewer {slides} chrome={false} />
{#if open && viewer}
<nav class="my-chrome">
<button onclick={() => viewer.prev()}>Prev</button>
<button onclick={() => viewer.next()}>Next</button>
<button onclick={() => viewer.close()}>Close</button>
</nav>
{/if}