CSSのdisplay: none
やvisibility: hidden
によって不可視状態になっている要素を、表示して即座にフォーカスしたいということがある。たとえば、初期状態では非表示になっている検索ボックスを、ユーザーのインタラクションに応じて表示するような場合。そうしたとき、通常では、スタイルを切り替えてすぐにfocus
メソッドを呼び出すだけで良い。
<input type="search" />
input[type='search'] {
display: none;
&.open {
display: revert;
}
}
const searchBoxElement = document.querySelector('input[type="search"]');
function open() {
searchBoxElement.classList.add('open');
searchBoxElement.focus();
}
function close() {
searchBoxElement.classList.remove('open');
}
しかし場合によっては、表示する際にフェードインなどのアニメーションを伴わせたいこともある。これを簡単に実現するには、CSSのtransition
プロパティを使いつつ、opacity
プロパティとvisibility
プロパティを操作するとよい。visibility
プロパティはアニメーション可能(discrete)であるため、transition
プロパティで制御することができる。
input[type='search'] {
visibility: hidden;
opacity: 0;
transition:
visibility 300ms,
opacity 300ms;
&.open {
visibility: revert;
opacity: revert;
}
}
const searchBoxElement = document.querySelector('input[type="search"]');
function open() {
searchBoxElement.classList.add('open');
searchBoxElement.focus();
}
function close() {
searchBoxElement.classList.remove('open');
}
ただし、このような実装ではフォーカスができずに失敗してしまう。なぜなら、スタイルを書き換えた直後にはまだ不可視状態(visibility: hidden
)のままだからだ。フォーカスさせるには、アニメーションが開始してvisibility: visible
の状態になるまで待機しなければならない。そのために対処として、requestAnimationFrame
メソッドが使用されがちである。
function open() {
searchBoxElement.classList.remove('open');
requestAnimationFrame(() => {
searchBoxElement.focus();
});
}
残念ながら、これは必ずしもうまくいくわけではない。requestAnimationFrame
メソッドが呼び出される時点ですでにアニメーションが開始しているかどうかは保証されていないからだ。そこで苦肉の策として、requestAnimationFrame
メソッドを二重に入れ子にすることで成功率を上げるというやり方もあるが、やはり確実ではない。
requestAnimationFrame(() => {
requestAnimationFrame(() => {
searchBoxElement.focus();
});
});
ここで重要なのは、アニメーションの開始タイミングに合わせて処理をすることである。そのためには、transitionstart
イベントを使用するのが適切だ。
function open() {
searchBoxElement.classList.remove('open');
searchBoxElement.addEventListener(
'transitionstart',
() => {
searchBoxElement.focus();
},
{ once: true },
);
}
visibility
プロパティをvisible
に書き換えると、アニメーションの開始タイミングですぐにvisible
に切り替わる(Animation of visibility)。そのため、トランジションの開始をフックにすればうまくいく。
さらには、transition-behavior
プロパティと@starting-style
を活用すればより簡単に実現できそうだ。visibility
プロパティをアニメーションさせるのと違って、display
プロパティにallow-discrete
を適用すると、表示の際にはトランジションの開始を待たずに切り替わるらしい。この性質を利用すれば、focus
メソッドは単に同期的に呼び出すだけでよい。
input[type='search'] {
display: none;
opacity: 0;
transition:
opacity 300ms,
display 300ms allow-discrete;
&.open {
@starting-style {
opacity: 0;
}
display: revert;
opacity: revert;
}
}
const searchBoxElement = document.querySelector('input[type="search"]');
function open() {
searchBoxElement.classList.add('open');
searchBoxElement.focus();
}
function close() {
searchBoxElement.classList.remove('open');
}