レイアウトだけではないCSS Grid応用テクニック 前編 コンテンツのクロスフェード
CSS Gridは、コンテンツのレイアウト以外にもいろいろな活用ができます。まずは、コンテンツのクロスフェードを実現する方法を見てみましょう。
はじめに
CSS Grid(以降、Grid)のdisplay: gridは、Webページのコンテンツのレイアウトをする際、強力な機能を発揮します。CodeGridでも、Gridを用いたレイアウトのテクニックを紹介しています。
一方で、Gridの仕組みを応用すれば、「最大内容の領域を確保する」ということもできます。これは、
- コンテンツのクロスフェード補助
- 「タグ入力UI」の残りの領域確保
といったことに活用できます。
言葉だけですとわかりづらいので、デモもご覧ください。
ボタンを押すと、2つのテキストがクロスフェードして内容が切り替わります。テキストは、短いものと長いものが用意されており、同じ大きさの領域間でのクロスフェードだけでなく、大きさが異なる領域間でのクロスフェードの様子も確認できます。2つのテキストは同じ枠に指定されており、Gridは、2つの要素を重ねるのに役立ちます。
短いテキストと長いテキスト間のクロスフェードでは、Gridの特性によりクロスフェードする双方のうちのコンテンツの最大の大きさが確保され、後続のコンテンツ(デモでは「後続のコンテンツ」と表示されている部分)と重ならないことがわかります。
もう一つ例を見てみましょう。テキストを入力すると、その量に応じて改行するデモです。
これは、「常に最小になろうとする入力枠(赤枠)」が「入力内容テキスト」の大きさに合わせて変動する仕組みになっています。結果として、最小になりたい「入力枠」は最大用量である「入力内容」と同じ大きさになるので、入力枠は、入力内容の大きさに自動で調節されます。
これをflexで並べれば、入力枠は狭い隙間に収まろうとし、収まらなければ次の行に回り込む、という表現ができます。
このように、Gridは、テキストの量による大きさを判定するのに役立ちます。
この2つのデモにおいて、Gridがどのように効果を発揮しているのかを掘り下げてみましょう。
CSS Gridのおさらい
まず、このシリーズで解説するデモで、どのようにGridが使われているかを知るために、Gridの知識をおさらいしておきましょう。
display: gridは、子要素をグリッド(格子状)にレイアウトする機能があります。
たとえば、3x2に分割したグリッドの例を見てみましょう。HTMLを用意しておきます。
3x2グリッドの基本的なHTML構造
<div class="parent">
<div class="div1">上 左</div>
<div class="div2">上 中央</div>
<div class="div3">上 右</div>
<div class="div4">下 中央</div>
</div>CSSでは、親要素に対してdisplay: gridを指定し、200pxを3列(grid-template-columns)として指定します。行は成り行きで決めたいので、grid-template-rowsは指定しません。
親要素の指定
.parent {
display: grid;
grid-template-columns: repeat(3, 200px);
}子要素には、どこに配置するかを一括指定プロパティのgrid-areaで指定します。
子要素の指定
.div1 { grid-area: 1 / 1 / 2 / 2; }
.div2 { grid-area: 1 / 2 / 2 / 3; }
.div3 { grid-area: 1 / 3 / 2 / 4; }
.div4 { grid-area: 2 / 2 / 3 / 3; }grid-areaは、セルの開始位置、終了位置を指定します。たとえば、次のように指定した場合を見てみます。
セルの開始/終了位置の指定
.div3 { grid-area: 1 / 3 / 2 / 4; }すると、上から1番目/左から3番目が開始位置、上から2番目/左から4番目が終了位置となります。以下の図で見ると「上右」のセルの位置になります。
実際にブラウザで表示すると以下のとおりです。
異なる要素に同じ値を指定して重ねる
さて、grid-areaは、異なる要素に、同じ値を指定することもできます。子要素another1を追加して試してみましょう。
子要素を追加
<div class="parent">
<div class="div1">上 左</div>
<div class="div2">上 中央</div>
<div class="div3">上 右</div>
<div class="div4">下 中央</div>
<div class="another1">追加された div</div> <!-- これを追加 -->
</div>CSSでは、div1とanother1に同じ値を指定します。
子要素に同じ値を指定
.div1 { grid-area: 1 / 1 / 2 / 2; }
.another1 { grid-area: 1 / 1 / 2 / 2; } /* 同じ値を指定 */上記のようにgrid-areaに同じ値を指定すると、div1とanother1は同じ枠に登録されて重なります。
子要素が重なっている場合の領域
Gridにより子要素が重なっている場合、親の大きさは「子要素の最大」と同じになります。それを確認するために、まず、重なった子要素の部分だけを切り出してみましょう。
重なった要素
<div class="parent">
<div class="div1">上 左</div>
<div class="another1">追加された div</div>
</div>another1の内容を多くしてみます。
重なった要素の片方の内容を多くする
<div class="parent">
<div class="div1">上 左</div>
<div class="another1">すごく長い...(省略)...すごく長い</div>
</div>重なった要素の片方の内容が多い場合、親は多いほうに合わせて領域を確保します。多くなっても「後続の要素」にかからず、領域が確保されているのがわかります。
ここまでが、今回紹介するテクニックの前提知識です。
- Gridは重ねられる
- 重なった場合、領域は最大子要素の大きさになる
それでは、これらを活かして、実際にどのように活用できるかを見ていきましょう。今回は、冒頭のデモの1つ目、「コンテンツのクロスフェードのためのレイアウト」を解説します。
重なりを活かしたクロスフェード
2つの要素をGridで重ね、片方をフェードアウト、もう片方をフェードインとすれば、クロスフェードを実現できます。
まず、fromとtoの2つの要素を用意し、同じgrid-areaを指定して重ねます。
2つの要素を重ねる
.container {
display: grid;
font-weight: 700;
font-size: 32px;
}
.from,
.to { grid-area: 1 / 1 / 2 / 2; }そして、.fromはfade(不透明から透明)でフェードアウト、.toはreverseによりfadeの逆再生(透明から不透明)でフェードインするようにします。
アニメーションの指定
.ongoing .from {animation: fade 1s 1 both;}
.ongoing .to {animation: fade 1s reverse 1 both;}
.done .from { display: none; }
@keyframes fade {
0% { opacity: 1; }
100% { opacity: 0; }
}これでクロスフェード効果になっているわけです。
そして、JavaScriptでボタンを押したときにアニメーションを実行させるようにします。
ボタンを押したときの処理
const contents = [
'hello',
'world',
'長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長いテキスト 長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長いテキスト',
];
let current = - 1;
const buttonEl = document.querySelector( 'button' );
const containerEl = document.querySelector( '.container' );
const fromEl = document.querySelector( '.from' );
const toEl = document.querySelector( '.to' );
const crossFade = () => {
buttonEl.disabled = true;
current = ( current + 1 ) % contents.length;
const next = ( current + 1 ) % contents.length;
fromEl.textContent = contents[ current ];
toEl.textContent = contents[ next ];
containerEl.classList.add( 'ongoing' );
containerEl.classList.remove( 'done' );
setTimeout( () => {
containerEl.classList.remove( 'ongoing' );
containerEl.classList.add( 'done' );
buttonEl.disabled = false;
}, 1000 );
};contents配列から次のコンテンツを取得し、fromElとtoElにそれぞれセットします。その後、class属性を付け替えてCSSアニメーションを実行させています。
このようにすると、冒頭のデモのように、ボタンを押すと2つの要素がクロスフェードします。
Gridは、「重ね」を実現でき、これがクロスフェードに便利な仕組みというわけです。
Grid以外で表現するには
今回のGridでの要素の重ねを利用してクロスフェードするという方法以外にも、いくつか方法が考えられます。Gridを使った場合との違いについて見てみましょう。
position: absoluteでクロスフェードした場合
たとえば要素を重ねる際、旧来はposition: absoluteを用いることが多かったわけですが、これで絶対配置した要素は、周囲のレイアウトから切り離されてしまいます。その結果、クロスフェードが進行中は周囲の別の要素と重なってしまう心配があります。
デモを見るとわかるように、position: absoluteを利用したデモでは、「後続のコンテンツ」が、クロスフェードが終わる前に移動しています。Gridなら、この最大要素の領域が確保されますので、position: absoluteのように、周囲と重なってしまう心配がありません。
View Transition APIでクロスフェードした場合
GridではなくView Transition APIを使えば、レイアウトの調整は自動で行われるので便利です。ただし、この場合もクロスフェードが進行中は周囲の別の要素と重なってしまいます。
また、View Transition APIのサポートは Safari 18以降とその他のブラウザであるため、2025年段階においては気軽には使えるとは言えません。
View Transition APIを気兼ねなく利用するためには、あと1、2年(2027年くらいまで)待つ必要がありそうです。
安心して使えるのはいつぐらい?
筆者が「2027年くらいまで待つ必要がありそう」と考えたのは、「Baseline」指標を参考にしたからです。Baselineでは、各主要ブラウザで新しい機能のサポートが揃ったことを示す「Newly available」から30ヶ月経過して「Widely available」になると、開発者はその機能をそこそこ安心して使っていいよ、というタイミングとしています。View Transition APIの足並みが揃ったのが2024年ですから、「Widely available」になるのは2027年。ですので、「2027年くらいまで」としました。加えて、個人的には「iPhoneの買い替えは3年毎くらいかな。それによって対応ブラウザのバージョンアップが進んで対応が進みそうだな」という気持ちがあります。
ここまでのまとめ
Gridの仕組みを応用して、コンテンツのクロスフェード補助に利用した例を紹介しました。Gridは要素を重ねることができ、周囲のレイアウトに影響を与えないため、クロスフェードのようなアニメーションに便利です。
次回は、Gridを用いて「タグ入力UI」の残りの領域確保を実現する方法を紹介します。