CSS transition-behavior完全ガイド - discrete transitionsで全プロパティをアニメーション化
CSS Transitions APIは長年、opacityやtransformなどの連続的なプロパティのみをアニメーション化できました。しかし、CSS transition-behaviorプロパティの登場により、displayやcontent-visibilityなどの離散的(discrete)なプロパティもアニメーション可能になりました。
この記事では、transition-behaviorの基本から実践的な活用方法まで、最新のCSSアニメーション技術を徹底解説します。
transition-behaviorとは
transition-behaviorは、離散的なプロパティ(discrete properties)のトランジション動作を制御するCSSプロパティです。
ブラウザサポート(2025年2月時点)
/* Chrome 117+, Edge 117+, Safari 17.4+ */
.element {
transition-behavior: allow-discrete;
}
- Chrome/Edge: 117+ (2023年9月〜)
- Safari: 17.4+ (2024年3月〜)
- Firefox: 開発中(Nightly版で実験的サポート)
基本構文
/* 構文 */
transition-behavior: normal | allow-discrete;
/* 例 */
.modal {
display: none;
opacity: 0;
transition: opacity 0.3s, display 0.3s;
transition-behavior: allow-discrete;
}
.modal.open {
display: block;
opacity: 1;
}
離散的プロパティとは
従来のCSSトランジションは、数値的に補間可能なプロパティのみをアニメーション化できました。
連続的プロパティ(従来から可能)
/* 数値・色・座標など補間可能 */
.element {
opacity: 0; /* 0 → 0.5 → 1 と滑らかに変化 */
transform: translateX(0); /* 0px → 50px → 100px */
background: red; /* red → purple → blue */
transition: all 0.3s;
}
離散的プロパティ(transition-behavior必須)
/* ON/OFF、表示/非表示など段階的 */
.element {
display: none; /* none → block(中間状態なし)*/
content-visibility: hidden; /* hidden → visible */
visibility: hidden; /* hidden → visible */
transition-behavior: allow-discrete;
transition: all 0.3s;
}
実践例:displayプロパティのアニメーション
問題:従来のアプローチ
/* これは動かない */
.modal {
display: none;
opacity: 0;
transition: opacity 0.3s;
}
.modal.open {
display: block; /* ← 即座に切り替わるため、opacityトランジションが見えない */
opacity: 1;
}
解決:transition-behaviorを使用
.modal {
display: none;
opacity: 0;
transition:
opacity 0.3s,
display 0.3s allow-discrete; /* ← ショートハンド構文 */
}
.modal.open {
display: block; /* トランジション期間中もblock状態を維持 */
opacity: 1;
}
HTMLとJavaScript
<button id="openBtn">モーダルを開く</button>
<div class="modal" id="modal">
<div class="modal-content">
<h2>モーダルタイトル</h2>
<p>コンテンツがフェードインします</p>
<button id="closeBtn">閉じる</button>
</div>
</div>
const modal = document.getElementById('modal');
const openBtn = document.getElementById('openBtn');
const closeBtn = document.getElementById('closeBtn');
openBtn.addEventListener('click', () => {
modal.classList.add('open');
});
closeBtn.addEventListener('click', () => {
modal.classList.remove('open');
});
@starting-styleとの組み合わせ
@starting-styleルールを使うと、初期レンダリング時のスタイルを定義できます。
ページロード時のフェードイン
.header {
opacity: 1;
transform: translateY(0);
transition: opacity 0.5s, transform 0.5s;
}
@starting-style {
.header {
opacity: 0;
transform: translateY(-20px);
}
}
displayとの併用
.dialog {
display: block;
opacity: 1;
scale: 1;
transition:
opacity 0.3s,
scale 0.3s,
display 0.3s allow-discrete;
}
.dialog:not(.open) {
display: none;
opacity: 0;
scale: 0.8;
}
@starting-style {
.dialog.open {
opacity: 0;
scale: 0.8;
}
}
content-visibilityのトランジション
content-visibilityは、レンダリングパフォーマンスを向上させるプロパティです。
基本的な使い方
.card {
content-visibility: auto; /* ビューポート外は非表示 */
transition: content-visibility 0.3s allow-discrete;
}
.card.hidden {
content-visibility: hidden;
}
長いリストのパフォーマンス最適化
.article {
content-visibility: auto;
contain-intrinsic-size: auto 500px; /* 推定サイズ */
transition: content-visibility 0.2s allow-discrete;
}
.article:hover {
content-visibility: visible; /* 強制的に表示 */
}
<div class="feed">
<article class="article">記事1</article>
<article class="article">記事2</article>
<!-- ... 数百件の記事 ... -->
</div>
visibilityとの違い
visibilityとdisplayは似ていますが、動作が異なります。
visibility: hidden
/* スペースは保持される */
.element {
visibility: visible;
opacity: 1;
transition: visibility 0.3s, opacity 0.3s;
}
.element.hidden {
visibility: hidden; /* レイアウトは保持 */
opacity: 0;
}
display: none
/* スペースも削除される */
.element {
display: block;
opacity: 1;
transition:
display 0.3s allow-discrete,
opacity 0.3s;
}
.element.hidden {
display: none; /* レイアウトから完全削除 */
opacity: 0;
}
実践例:ドロップダウンメニュー
.dropdown {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
display: none;
opacity: 0;
transform: translateY(-10px);
transition:
opacity 0.2s,
transform 0.2s,
display 0.2s allow-discrete;
}
.dropdown:hover .dropdown-menu {
display: block;
opacity: 1;
transform: translateY(0);
}
@starting-style {
.dropdown:hover .dropdown-menu {
opacity: 0;
transform: translateY(-10px);
}
}
<nav class="dropdown">
<button>メニュー</button>
<ul class="dropdown-menu">
<li><a href="#">項目1</a></li>
<li><a href="#">項目2</a></li>
<li><a href="#">項目3</a></li>
</ul>
</nav>
トースト通知のアニメーション
.toast-container {
position: fixed;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
.toast {
display: none;
padding: 16px 24px;
background: #333;
color: white;
border-radius: 8px;
opacity: 0;
transform: translateX(100%);
transition:
display 0.3s allow-discrete,
opacity 0.3s,
transform 0.3s;
}
.toast.show {
display: block;
opacity: 1;
transform: translateX(0);
}
@starting-style {
.toast.show {
opacity: 0;
transform: translateX(100%);
}
}
function showToast(message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.querySelector('.toast-container').appendChild(toast);
// トリガー用に少し待つ
requestAnimationFrame(() => {
requestAnimationFrame(() => {
toast.classList.add('show');
});
});
// 3秒後に非表示
setTimeout(() => {
toast.classList.remove('show');
toast.addEventListener('transitionend', () => {
toast.remove();
}, { once: true });
}, 3000);
}
// 使用例
showToast('保存しました!');
アコーディオンメニュー
.accordion-item {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.accordion-header {
padding: 16px;
background: #f5f5f5;
cursor: pointer;
}
.accordion-content {
display: none;
padding: 0 16px;
max-height: 0;
opacity: 0;
transition:
display 0.3s allow-discrete,
max-height 0.3s,
padding 0.3s,
opacity 0.3s;
}
.accordion-item.open .accordion-content {
display: block;
max-height: 500px;
padding: 16px;
opacity: 1;
}
@starting-style {
.accordion-item.open .accordion-content {
max-height: 0;
padding: 0 16px;
opacity: 0;
}
}
document.querySelectorAll('.accordion-header').forEach(header => {
header.addEventListener('click', () => {
const item = header.closest('.accordion-item');
item.classList.toggle('open');
});
});
パフォーマンス考慮事項
GPU アクセラレーション
/* transformとopacityはGPUアクセラレーション可能 */
.element {
display: block;
opacity: 1;
transform: translateY(0); /* GPU加速 */
will-change: opacity, transform; /* ヒント */
transition:
display 0.3s allow-discrete,
opacity 0.3s,
transform 0.3s;
}
contain プロパティ
/* レンダリング最適化 */
.modal {
contain: layout style paint;
display: none;
transition: display 0.3s allow-discrete;
}
フォールバック(レガシーブラウザ対応)
/* モダンブラウザ */
@supports (transition-behavior: allow-discrete) {
.modal {
display: none;
opacity: 0;
transition:
display 0.3s allow-discrete,
opacity 0.3s;
}
.modal.open {
display: block;
opacity: 1;
}
}
/* 旧ブラウザ */
@supports not (transition-behavior: allow-discrete) {
.modal {
visibility: hidden;
opacity: 0;
transition: visibility 0.3s, opacity 0.3s;
}
.modal.open {
visibility: visible;
opacity: 1;
}
}
トラブルシューティング
アニメーションが動作しない
/* ❌ 動かない */
.element {
display: none;
transition: display 0.3s; /* allow-discreteがない */
}
/* ✅ 正しい */
.element {
display: none;
transition: display 0.3s allow-discrete;
}
@starting-styleが必要なケース
/* ページロード時のアニメーション */
.hero {
opacity: 1;
transition: opacity 1s;
}
/* これがないと初期状態からのトランジションが発生しない */
@starting-style {
.hero {
opacity: 0;
}
}
まとめ
CSS transition-behaviorを使うと、以下が可能になります。
主な利点
- displayのアニメーション化 - モーダル、ドロップダウンが自然に
- content-visibilityの制御 - パフォーマンス最適化とUXの両立
- JavaScriptの削減 - CSSだけで複雑なアニメーションを実現
- 保守性の向上 - アニメーションロジックがスタイルシートに集約
ベストプラクティス
/* 推奨パターン */
.component {
/* 初期状態 */
display: none;
opacity: 0;
transform: translateY(-10px);
/* トランジション定義 */
transition:
display 0.3s allow-discrete,
opacity 0.3s,
transform 0.3s;
}
.component.active {
/* 最終状態 */
display: block;
opacity: 1;
transform: translateY(0);
}
/* 初期レンダリング制御 */
@starting-style {
.component.active {
opacity: 0;
transform: translateY(-10px);
}
}
transition-behaviorは、モダンWebアプリケーションのUI/UX向上に不可欠な機能です。ぜひプロジェクトで活用してみてください。