5 ポイント 投稿者 GN⁺ 2024-04-26 | 1件のコメント | WhatsAppで共有
  • canvas-confetti は、Webページでキャンバスベースの confetti アニメーションを実行するクライアント向けライブラリで、NPM インストールと CDN の直接読み込みの両方に対応
  • 基本の confetti() API は、オプションオブジェクト 1 つで粒子数、角度、拡散、速度、重力、色、形状、位置、z-index などを調整でき、Promise 対応環境ではアニメーション完了のタイミングを受け取れる
  • Reduced Motion ユーザー向けに disableForReducedMotion オプションを提供しており、この値はデフォルトで false だが、将来のメジャーリリースで変更される可能性がある
  • SVG Path とテキストベースのカスタム形状を作成でき、squarecirclestar の基本形状に加えて emoji confetti のような効果も実装可能
  • confetti.create() は特定の canvas にインスタンスを作成し、resizeuseWorker などのグローバルオプションをサポートするが、useWorker: true では canvas の制御が Web Worker に移されるため、メインスレッドで操作するとエラーが発生する

インストールと実行方法

  • デモページ でライブラリの動作を確認できる
  • NPM パッケージとしてインストール可能
npm install --save canvas-confetti
  • プロジェクトのビルドでは require('canvas-confetti') で使用できる
  • このライブラリは クライアントコンポーネント であり、Node では実行されない
    • README では、プロジェクトを webpack のようなツールでビルドする必要があると案内している
  • HTML ページでは CDN スクリプトとして直接読み込める
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.4/…;
  • CDN 利用時は、プロジェクトに組み込む時点での最新バージョンを使うことが推奨されており、全バージョンは releases page で確認できる

Reduced Motion 対応

  • 一部のユーザーは Web サイト上の動きを望まない、または減らしたいと考えることがあり、ブラウザは prefers-reduced-motion でそれを伝えられる
  • disableForReducedMotion オプションを使うと、負担になるアニメーション が難しいユーザーに confetti を表示しないようにできる
  • このオプションの現在のデフォルト値は false
  • 今後のメジャーリリースでデフォルト値の変更が検討されており、強い意見があれば issue として伝えられる
  • disableForReducedMotion が適用されて confetti が無効化されると、confetti() Promise は即座に resolve される

基本 API と Promise の挙動

  • NPM インストール時はプロジェクトのビルド内でクライアントコンポーネントとして require でき、CDN 版では windowconfetti 関数として公開される
  • confetti([options]) は任意のオプションオブジェクト 1 つを受け取る
  • window.Promise があれば、アニメーション完了を知らせる Promise を返す
    • Promise がない IE のような環境では null を返す
    • Promise polyfill を使用できる
    • confetti.Promise = MyPromise の形で Promise 実装を直接提供することもできる
  • 完了前に confetti を複数回呼び出すと、毎回同じ Promise が返される
  • 内部的には同じ canvas 要素 を再利用し、既存のアニメーションを継続しながら新しい confetti を追加する
  • 各呼び出しが返した Promise は、すべてのアニメーションが終了した後に resolve される

主なオプション

  • particleCount: 発射する confetti の数、デフォルト値は 50
  • angle: 発射角度、デフォルト値は 90 で、90 は上方向を意味する
  • spread: 中心から広がる範囲、デフォルト値は 45
  • startVelocity: 初速度、デフォルト値は 45
  • decay: 速度が減衰する度合い、デフォルト値は 0.9
    • 0 と 1 の間に保つ必要があり、範囲を外れると速度が増加する可能性がある
  • gravity: 粒子が下に引かれる度合い、デフォルト値は 1
    • 0.5 は半分の重力で、制限がないため上昇させることもできる
  • drift: 左右に流れる度合い、デフォルト値は 0
    • 負の値は左、正の値は右を意味する
  • flat: 実際の 3D confetti のように傾いたり揺れたりする効果を無効にでき、デフォルト値は false
  • ticks: confetti が動く回数、デフォルト値は 200
  • origin: 発射開始位置
    • origin.x: ページ上の x 位置、0 は左、1 は右、デフォルト値は 0.5
    • origin.y: ページ上の y 位置、0 は上、1 は下、デフォルト値は 0.5
  • colors: HEX 形式の色文字列の配列
  • shapes: confetti の形状配列
    • デフォルトの組み込み値は squarecirclestar
    • 初期状態では square と circle が均等に混ざる
    • ['circle', 'circle', 'square'] のように配列比率で混合比を調整できる
  • scalar: 各粒子のスケール、デフォルト値は 1
  • zIndex: confetti の表示レイヤー、デフォルト値は 100
  • disableForReducedMotion: Reduced Motion を好むユーザー向けに confetti を無効化する

カスタム形状の作成

  • confetti.shapeFromPath({ path, matrix? })SVG Path string を使ってカスタム confetti 形状を作成する
  • Path ベースの形状にはいくつか制約がある
    • すべての path は塗りつぶされた形として扱われ、stroke path は実装されていない
    • path は単一色に制限される
    • すべての path には有効な transform matrix が必要
    • matrix の計算にはコストがかかるため、開発中は path ごとに一度計算してキャッシュするのが望ましい
    • matrix は同じ path 値に対して常に同一となる
    • ライブラリを更新した場合は、forward compatibility のために matrix を再生成してキャッシュし直すのが望ましい
    • path ベースの confetti は Path2D をサポートするブラウザに限定される
  • 返り値は Shape オブジェクトで、shapes 配列に直接入れて使える
var triangle = confetti.shapeFromPath({ path: 'M0 10 L5 0 L10 10z' });

confetti({
  shapes: [triangle]
});
  • confetti.shapeFromText({ text, scalar?, color?, fontFamily? }) はテキストベースの confetti 形状を作成し、標準 Unicode emoji を使用できる
  • テキストベースの形状は emoji confetti に適している
    • 揺れる confetti には、概ね正方形に近い単一文字、特に emoji がよく合う
    • テキストは毎回描画するのではなくラスタライズされるため、生成後にスケールを大きく変えるとぼやける可能性がある
    • confetti オプションの scalar を使う予定なら、shape 作成時にも同じ scalar 値を使うのが望ましい
  • テキストオプションは textscalarcolorfontFamily を受け取る
    • fontFamily のデフォルト値はネイティブ OS の emoji レンダリング慣行に従い、sans-serif にフォールバックする
    • Web フォントを使う場合は、confetti のレンダリング前にフォントが読み込まれている必要がある
var scalar = 2;
var pineapple = confetti.shapeFromText({ text: '🍍', scalar });

confetti({
  shapes: [pineapple],
  scalar
});

カスタム canvas と Worker レンダリング

  • confetti.create(canvas, [globalOptions]) は、特定の canvas を使う confetti 関数インスタンスを作成する
  • ページ内の特定領域にだけ confetti を制限したい場合に便利
  • デフォルトでは、このメソッドは canvas に描画すること以外に canvas を変更しない
  • canvas は CSS で表示サイズを変えても実際の canvas 画像サイズは変わらないため、引き伸ばされてぼやけることがある
    • resize オプションを有効にすると、ライブラリが canvas 画像サイズを合わせ、ウィンドウサイズ変更やモバイル回転にも対応する
  • 同じ canvas 要素で confetti インスタンスを複数回初期化せず、作成したカスタムインスタンスを保持する必要がある
  • グローバルオプション

    • resize: canvas 画像サイズを設定し、ウィンドウ変化に合わせて維持するかを決める。デフォルト値は false
    • useWorker: 可能な場合、非同期 Web Worker で confetti アニメーションをレンダリングする。デフォルト値は false
    • デフォルトでは常にメインスレッドでアニメーションが実行される
    • ブラウザが対応していれば、メインスレッドを塞がないようアニメーションはメインスレッド外で実行される
    • 非対応ブラウザではこの値は無視される
    • disableForReducedMotion: その confetti インスタンスがユーザーの Reduced Motion 要求を常に尊重するようにする
  • useWorker: true の注意点

    • useWorker: true を使うと canvas の制御が Web Worker に移される
    • この場合、canvas を DOM から削除すること以外に、メインスレッドで操作するとエラーが発生する
    • canvas を直接操作する必要があるなら useWorker オプションは使うべきではない
    var myCanvas = document.createElement('canvas');
    document.body.appendChild(myCanvas);
    
    var myConfetti = confetti.create(myCanvas, {
      resize: true,
      useWorker: true
    });
    myConfetti({
      particleCount: 100,
      spread: 160
    });
    

アニメーション停止とサンプルパターン

  • confetti.reset() はアニメーションを停止してすべての confetti を消去し、待機中の Promise を即座に resolve する
  • confetti.create() で作成した個別インスタンスは独自の reset メソッドを持つ
confetti();

setTimeout(() => {
  confetti.reset();
}, 100);
  • 基本実行は引数なしで confetti() を呼び出す
  • particleCount: 150 で大量の confetti を発射できる
  • spread: 180 で広く拡散する confetti を作れる
  • originMath.random() を使うと、ページ上のランダムな位置で小さな爆発効果を作れる
  • README の例では requestAnimationFrame を使い、30 秒間にわたって左右の端から継続的に confetti を発射するパターンを紹介している

1件のコメント

 
GN⁺ 2024-04-26
Hacker Newsのコメント
  • ここで高性能なアニメーションを作るコツは、canvas に描画して、その canvas を他のすべての要素の前面に置きつつ、ポインターイベントを無効化してページと引き続き相互作用できるようにすること

    • その通り。ポインターイベントの無効化は意外と役に立つ
    • これを高性能アニメーションのコツだと言っていたけど、これ以外の実装方法があまり思い浮かばない。素朴な実装ってどんな感じなんだろう?
  • 2015年に高校で Web 開発をしていた良き時代を思い出す。ホームカミングに一緒に行かないか女の子を誘うために、紙吹雪が出る小さなWebサイトを作ったんだけど、振り返るとものすごくオタクっぽかった
    当時は、子どもにとって Web サイトを作ることが超能力みたいに感じられた
    時期的にこのパッケージではなかった気がするけど、アニメーションはかなり良かった
    こういう純粋に楽しい小さなプロジェクトが好き。プログラミングを始めた理由もそれだし、今でも大きな原動力になっている

    • うまくいったの? その子は yes って言った?
  • デモページのこの部分が好き:

    If you happened to get curious and changed the particle count to 400 or so, you saw something disappointing. An even "flattened cone" look to the confetti, making it look way too perfect and ruining the illusion.

    こういう細部へのこだわりは珍しくて、統計可視化でも映画の小道具でも Web サイトの紙吹雪でも、見つけるたびに大切だと感じる
    解決策としては、乱数分布そのものを変えてみると思う。実際に確認はしてみるつもりだけど、現実の分布はガウス分布に近いんじゃないかという気がする

  • 営業担当者が成約したときに出る管理ダッシュボードの紙吹雪を追加したら、意外と楽しくてモチベーションが上がる

  • reset 関数を confetti.resetti() と呼んでほしかった

    • JavaScript なんだから、少なくともローカルでは "confetti.resetti = confetti.reset" で簡単に直せる
      このアプローチにはソフトウェア工学的なコストが多少あるだろうけど、注意深い観察者なら誰の目にも明らかなように利益が圧倒的に大きいので、やってしまっていいと思う
    • この人には仕事を与えるべき。すでに仕事があるならクッキーくらいあげるべき
    • PR を作ってみてもいいかも
  • かっこよくて便利なライブラリであることとは別に、John Ousterhout がPhilosophy of Software Designで述べている深いモジュールの良い例でもある
    もっとも基本的なバージョン、つまり紙吹雪を発生させる機能はとても簡単に使える一方で、オプションを見ていくと雪や特定の色、さまざまな紙吹雪効果など、かなり多くのことができる

  • すごくかっこよくて印象的
    その一方で、自分が使うどの Web サイトでも動いてほしいとは思わない。特にニュースレターのポップアップやカートに商品を入れたときに紙吹雪がついてくるのは望まない

    • 不思議なことに、この効果はかなりうまく使える場合がある。こういう全画面方式はどうか分からないけど、最近訪問した顧客企業が使っていたプロジェクト管理ソフトでは、項目を完了するとボタンが緑色に変わってこういう効果が付いていた
      控えめだけど気づける程度で、会議のあと別の開発者と2人で「かなりいい効果だったね」と話した
      「よし、進展している!」という感じを伝えていた
      ただし選べるようにしておくべき

    • 正当な使いどころなら YouTube のいいねボタンみたいなものだと思う。良いアニメーションがあって、モバイルアプリでは端末も振動する。とても気持ちの良いユーザー体験

    • https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...

      ブラウザでは動きを減らすことを好むよう設定できる。サイト運営者やライブラリ保守者は、紙吹雪のようなものを実装するときにはこれを尊重すべき。このライブラリには特に disableForReducedMotion オプションがある

    • こういう効果が合う場所はある。たとえばゲームをクリアしたときなど

    • 私たちは誰かが特定の条件を満たしたときにこのライブラリを使っている。オンボーディングフローにかなり良い効果を与えている

  • Party.js ライブラリもある: https://party.js.org/

    • では、どちらのほうが小さいんだろう?
      10.4 kB minified、4.2kB minified + Gzip
      https://bundlephobia.com/package/canvas-confetti@1.9.2

      28.3kB minified、7.4kB minified + Gzip
      https://bundlephobia.com/package/party-js@2.2.0

      ただ、bundlephobia がどう動いているのかはよく分からない。パッケージの最終サイズを最もうまく示しているとは限らないかもしれない。たぶんコード分割や必要なものだけを取り込む方式は反映していないと思う。素早い大まかな概要として見ている

      Gzip 基準では confetti のほうが数 KB 勝っているようなので、その数 KB をどうしても絞り出す必要がないなら、必要な機能がどちらにあるか次第でどちらもあり

    • 元記事のスクリプトのほうがモバイルではずっと性能が良さそう

    • 元記事のライブラリのほうがずっと性能が良さそう。自分の古い仕事用 PC では Party.js は3回クリックしただけで少し遅延を感じる
      canvas-confetti は数秒間休みなくクリックして、おそらく30個を超える紙吹雪インスタンスと大量の粒子を作るくらいになってようやく遅延が始まる

  • downforacross.com でクロスワードを解いていて、パズルを解くと紙吹雪が出る
    もっと軽く感じさせるために、ここのより高性能なコードの一部を使えるかもしれない
    ただし、「楽しい」サイトやまれな使用でない限り、こういうアニメーションがどこにでも出てくるのは見たくない

  • タイトルにusefulをわざわざ入れる必要はないと思う

    • モチベーションツール兼、コードがコンパイルされたか確認する手段としてはどう?: https://squint-cljs.github.io/squint/
    • そう。とはいえ、その単語のおかげで本当に興味を持ったし、実際にはあまり useful ではなくて笑ってしまった。おすすめ
    • 現実の紙吹雪と同じくらい useful、つまり 100% useful