Retina で canvas が2倍の大きさになる:inset:0 では広がらない(押すと弾けるロゴの不具合)
Koshin Studio 開発の記録 / 2026年10月
このサイトのトップには、大きなロゴがあります。字を押すと、その字がぽんと弾けて、粒が散ります。ロゴを、自分で作った書体「恒紳ポップ」で作り直したあと、この動きについて、「粒が、字ではない変なところから出る」「まだ、右に寄っている」と指摘を受けました。直すまでに三段階かかったので、何が原因で、どう見つけたのかを、測った数字で書きます。
ロゴを、自作の書体で作り直す
それまでのロゴは、明朝体の骨で欧文を組んだものでした。これを、自分で作った書体「恒紳ポップ」(欧文は、骨格から描き起こした、丸みのある字形)に替えました。書体のライセンスは、SIL Open Font License 1.1 で、ロゴへの利用に問題はありません。
作り方は、書体のファイルから、K O S H I N T U D の 9 文字の輪郭を取り出して、SVG の path にします(tools/logotype_pop.py)。ロゴは「絵」として配るので、サイトは書体のファイルを配りません。この書体は、同じ字でも「基本形」「pop1」「pop2」と、跳ね方の違う形を持っているので、3 通りの組み合わせを並べて見比べて、リズムのよい 1 つを選びました。「U」の右側に出た細い継ぎ目の線は、輪郭に、ごく細い線(3 単位)を足して消しました。
一段階目:押したあと、字がずれたまま戻らない
まず、字を押したあとの位置を、時間を追って記録しました。すると、字が元の位置に戻っていませんでした。
| 字 | 押す前の中心 | 押したあと(2 秒後) |
|---|---|---|
| K(上段の左端) | (327, 306) | (320, 313) |
| S(下段の左端) | (331, 442) | (323, 435) |
約 7px のずれです。押したあとの変形を見ると、matrix(1, 0, 0, 1, -46, -42) が残っていました。大きさは 1 倍に戻っているのに、位置のずれだけが残っている状態です。
原因:弾ける動きに使っていたアニメーションの部品(GSAP)が、字の SVG の <g> に、もともと付いていた位置の指定(transform="translate(x,0)")を、自分の計算で書き換えて、その補正の一部が、終わったあとも残っていました。
直し方:位置を持つ層(外側の <g transform>)と、動かす層(内側の <g class="ks-ch">)を分けました。動きは、ブラウザ標準の Web Animations に替えました(終わると、効果が消えて、元の状態に戻ります)。確かめると、12 文字すべてで、字の中心は動かず、動きの終わりの変形は none に戻りました。
二段階目:「まだ右に寄っている」
ところが、直したあとも、「まだ、アニメーションが右に寄っている」と言われました。私の測定では、粒は字の中心から 2〜13px 以内に出ていました。測った結果と、見えたものが、食い違っていました。
私の測定は、倍率 1 倍の画面で、粒の位置を「画面上の座標」で数えたものでした。Retina ディスプレイ(倍率 2 倍)や、スマホ(3 倍)でも測ってみると、粒を描く画面(#burst)の、CSS 上の大きさが、おかしいことが分かりました。
| 画面 | 画面の幅 | 粒の画面の幅(前) | (後) |
|---|---|---|---|
| 1280px・倍率 1 | 1280 | 1280 | 1280 |
| 1440px・倍率 2 | 1440 | 2880 | 1440 |
| 390px・倍率 3(スマホ) | 390 | 780 | 390 |
原因:粒を描く canvas は、CSS で inset:0 と指定していましたが、canvas のような部品は、inset:0 だけでは、画面いっぱいに広がりません。元の大きさ(描く画素数)のままです。倍率 2 の画面では、画素数が 2 倍なので、CSS 上の大きさも 2 倍になり、粒が、座標の 2 倍の位置(右下)に表示されていました。倍率 1 の画面では、同じ大きさなので、気づけません。
直し方:幅と高さを、100% と明示しました。確かめると、どの倍率の画面でも、粒の画面は、画面と同じ大きさになりました。
三段階目:字そのものから出す
最後に、「字自体から出るようにできないか」という要望がありました。それまでは、字の枠の中心から出していたので、「O」のように真ん中が空いた字では、字ではない所から出たように見えます。
そこで、字の形(path)の輪郭に沿って点を選び(getPointAtLength)、その画面上の位置から、字の中心と反対の向き(ばらつきあり)へ、粒を飛ばすようにしました。下段の字は「字=マスク」で描いているので、矩形が指しているマスクの中の path を探して使います。押して撮ると、「O」は輪の周りから、「U」は形に沿って、粒が出ています。
分かったこと
測った数字と、見えたものが食い違ったら、測り方の条件を疑う。
- 見る人の画面は、自分の画面と違う。倍率 1 倍の画面だけで測って、「ずれは小さい」と判断しました。実際は、倍率 2 倍の画面で、粒が 2 倍の場所に出ていました。
- 測る空間を、見る空間に合わせる。私の測定は、粒の「論理的な座標」を数えていたので、画面に出た位置のずれは、そもそも見えませんでした。
- 動きは、終わったあとの状態も測る。字が元に戻らないことは、動いている間だけを見ていたら、気づけませんでした。
- 位置と動きは、別の層に置く。同じ要素に両方を持たせると、片方がもう一方を上書きします。
この記事の数字は、2026年10月8日に、このサイトのトップを、実際のブラウザ(Chrome)で操作して測ったものです。実際の Retina ディスプレイの実機で、直ったことは、まだ確かめていません(倍率 2 倍と 3 倍の状態を、ブラウザの設定で再現して確かめました)。
この話に出てくるもの
- 手元では動くのに、配ると壊れる — このサイトの、別の記録
- アプリを完全に終了すると、サインインが消えた — 「終わったあとの状態」に出る問題の、別の記録
ほかの記録
- 手元では動くのに、配ると壊れる:このサイトで起きた四つの事故 — 古いキャッシュ、組むたびに増えるパンくず、環境の中を指す symlink、手元にしか無い絵。サイトを配るときに続いた四つの事故と、検査を足してはじめて見つかった経緯。
- Firebase Auth:アプリを完全に終了すると、匿名アカウントが量産された(currentUser の復元待ち) — 「アプリを落とすと、また Apple のサインインを求められる」。管理画面を開くと、19人中17人が使い捨ての匿名アカウント。起動直後に currentUser を同期チェックしていた競争を、addStateDidChangeListener で直した記録。
- 「重複は実質ゼロ」と数えたのに、監査は112組を出していた — 重複を統合する処理が、毎晩、先頭のジャンルの途中で時間切れになっていた。「ゼロ」と数えた自分の測り方が間違っていた。持ち回りに直して112組を0組にした記録。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。