Highlight
Safari 19 では scroll-driven animations、cross-document view transitions、anchor positioning が導入され、スクロール連動アニメーション、ページ間トランジション、ポップオーバーの位置決めをすべて純粋な CSS だけで実現できるようになりました。
核心となる内容
スクロール進捗バーを作る場合、これまでは scroll イベントを監視して JavaScript で transform を書き換える必要がありました。Saron Yitbarek 氏は Session の冒頭で次のように指摘しています。JavaScript でできることは数多くありますが、省けるものを省くだけで、ユーザーはより良いパフォーマンスとバッテリー持続時間を手に入れられる、と(03:24)。Safari 19 はこれを CSS の世界に持ち込みました。
彼女は「A-School of Code」サイトを例として、4 つの新機能カテゴリを紹介しています。アニメーションの面では、scroll-driven animations によって CSS から直接スクロール進捗にバインドでき、view transitions により同一オリジンのページ遷移にスムーズなトランジションを付けられます。レイアウトの面では、anchor positioning がポップオーバーやメニューといったよくあるインタラクションに必要な位置計算をブラウザに任せます。ビジュアルの面では、background-clip: border-area でボーダーをグラデーションで塗りつぶせるようになり、shape() 関数が path() に代わってレスポンシブなシェイプを記述できます。メディア面では、SVG favicon、HDR 画像、Ogg Opus/Vorbis のサポートが追加されました。
詳細な内容
Scroll-driven animation の最小例(06:18)。フッターの ::after 擬似要素を進捗バーとして使い、animation-timeline: scroll() でアニメーションをスクロールコンテナにバインドします。
footer::after {
content: "";
height: 1em;
width: 100%;
background: var(--yellow);
left: 0;
bottom: 0;
position: fixed;
transform-origin: top left;
animation: progress-scale linear;
animation-timeline: scroll();
}
@keyframes progress-scale {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
ポイント:
position: fixed+bottom: 0で進捗バーをビューポート下端に固定します。transform-origin: top leftでスケールの起点を左側に揃えます。animation: progress-scale linearでアニメーションのイージングを linear に指定し、duration は書きません。animation-timeline: scroll()でタイムラインを最も近いスクロールコンテナのスクロール進捗に差し替えます。デフォルトのタイムラインを上書きするため、必ずanimationの後に書く必要があります。@keyframesはおなじみの CSS のままで、scaleX(0)からscaleX(1)まで横方向に伸ばしていきます。
view() タイムライン + animation-range(12:20)。要素がビューポートに出入りするタイミングでアニメーションさせたい場合は、scroll() ではなく view() を使います。
.topic-item {
animation-fill-mode: both;
animation-timeline: view();
animation-range: 0% 50%;
&:nth-child(3n + 1) { animation-name: in-from-left; }
&:nth-child(3n + 2) { animation-name: in-from-middle; }
&:nth-child(3n + 3) { animation-name: in-from-right; }
}
ポイント:
animation-fill-mode: bothでアニメーション開始前と終了後の両方で対応するフレームのスタイルを保持させます。animation-timeline: view()で進捗を「要素のビューポート内での相対位置」にバインドします。animation-range: 0% 50%でアニメーションの再生区間を「要素がビューポートに入り始めてから、ビューポートの半分までスクロールするまで」に限定します。残りのスクロール中は要素は静止して読みやすい状態のままになります(12:00)。:nth-child(3n + 1/2/3)を使って 3 列にそれぞれ異なる@keyframesを当てることで、要素ごとにずれた登場アニメーションを表現します。
Cross-document view transitions(14:20)。同一オリジンのページ間ナビゲーションは、最低 1 行で済みます。
@view-transition {
navigation: auto;
}
@media not (prefers-reduced-motion) {
@keyframes slide-in {
from { translate: 100vw 0; }
}
@keyframes slide-out {
to { translate: -100vw 0; }
}
}
ポイント:
@view-transition { navigation: auto }を両方のページに記述しておけば、ブラウザが同一オリジン遷移に自動的にフェードを掛けてくれます。@media not (prefers-reduced-motion)でカスタムのスライドエフェクトを囲み、システムの「視差効果を減らす」設定を尊重します(16:00)。- 個別の要素に
view-transition-nameを宣言し、::view-transition-old(...)と::view-transition-new(...)でそれぞれの出入りアニメーションを制御します。
Anchor positioning(21:58 → 23:25)。popover API と組み合わせれば、メニューの位置計算に JavaScript は不要になります。
.profile-button {
anchor-name: --profile-button;
}
.profile-menu {
position-anchor: --profile-button;
position-area: top right;
}
ポイント:
anchor-name: --profile-buttonでボタンをアンカーとして登録します。名前は CSS のカスタム識別子です。position-anchor: --profile-buttonでメニューがどのアンカーを参照するかを指定し、紐付けを確立します。position-area: top rightはキーワード指定で、メニューがアンカーの右上領域に出現することを宣言します。bottom center、span-right、span-leftなどの値も指定できます。- より細かく制御したい場合は
top: anchor(bottom); left: anchor(left)のように記述します。anchor()関数はアンカーの該当辺の座標を返すので、calc()の中にも入れられます(28:26)。
border-area によるグラデーションボーダー(31:05):
.primary-btn {
background: border-area linear-gradient(to bottom right in hsl, yellow, orange);
border-color: transparent;
}
ポイント:
backgroundのショートハンド内にあるborder-areaキーワードによって、背景画像をボーダー領域まで延長します。border-color: transparentでボーダー自体の色を抜き、下のグラデーションを透けさせます。- これは、これまで擬似要素や SVG を駆使しないと作れなかったグラデーションボーダーを、純粋な CSS だけで書けるようにする手法です。
中心となるヒント
-
スクロール進捗バーを CSS に戻す: サイトのフッターや記事冒頭の進捗バーを、既存の scroll listener から
animation-timeline: scroll()に置き換えましょう。やる価値がある理由は、JavaScript を取り除くことでメインスレッドがスクロールイベントで起こされなくなり、モバイルのバッテリー持続時間とフレームレートがより安定するからです。始め方としては、まず Safari Technology Preview でデモページを開き、既存の進捗バーコンポーネントをposition: fixedの擬似要素に書き換え、animation-timeline: scroll()を 1 行追加して動作を確認します。 -
view() + animation-range でカードの登場演出: リストやカードグリッド、長い文書の見出しエリアでは、これまで IntersectionObserver でアニメーションをトリガーしていました。やる価値がある理由は、
view()が「要素のビューポート内での相対位置」をそのままアニメーションの進捗に流し込んでくれるため、逆方向にスクロールしたときも自然に巻き戻し再生できるからです。始め方としては、既存の IntersectionObserver ベースの登場アニメーションを 1 つ選び、JavaScript を削除してanimation-timeline: view()に書き換えます。animation-rangeは0% 50%に設定して、アニメーションをビューポートの前半だけに収め、情報の可読性を確保しましょう。 -
anchor positioning で popper.js を置き換える: popper.js / floating-ui の位置計算に依存しているドロップダウンメニューや tooltip すべてが対象です。やる価値がある理由は、
anchor-name+position-area+position-tryの組み合わせによって反転ロジックがブラウザ側に移り、バンドルサイズ、バグ、キーボードフォーカスといった問題がまとめて解決するからです。始め方としては、最もシンプルなアバターメニューを 1 つ選び、HTML にpopover+popovertargetを付け、CSS でボタンにanchor-name、メニューにposition-anchorとposition-area: bottom centerを付与します。さらに@supportsを使って古いブラウザ向けのフォールバック経路を残しておきましょう。 -
prefers-reduced-motionをハード制約として扱う: scroll-driven や view-transition のコードはすべて@media not (prefers-reduced-motion)で囲みましょう。やる価値がある理由は、Saron 氏が Session 内で motion discomfort を一般的な症状として明確に挙げており、めまいや吐き気といった現実の生理反応を伴うことを示しているからです。始め方としては、チームの CSS lint にルールを 1 つ追加し、animation-timelineや@view-transitionを含むセレクタは必ずprefers-reduced-motionのメディアクエリ内に存在しなければ警告するようにしておきます。
関連 Session
- Discover Apple-Hosted Background Assets — Apple がホストする Background Assets を活用し、ユーザーの初回起動前に大容量リソースを事前ダウンロードしておく方法。
- Dive deeper into Writing Tools — Writing Tools をテキストコントロールに組み込み、校正・リライト・変換をシステム層で完結させる方法。
- Dive into App Store server APIs for In-App Purchase — App Store Server API と Notification V2 の最新アップデートで、サブスクリプションや返金コールバックを処理する方法。
- Enhance child safety with PermissionKit — PermissionKit を使い、コミュニケーション系アプリで子どもアカウントに保護者がコントロールできる安全レイヤーを追加する方法。
コメント
GitHub Issues · utterances