CSSアニメーション(transition)で起こるバブリングの原因と対策
正確には「バブリング」とは呼ばないと思うし、ちょっと意味合いが違うかもしれないけど、CSSで子要素に与えたtransition
が親要素へ伝播し、アニメーションの挙動をおかしくさせる現象に何度か遭遇した。
もしかすると、CSS界隈では有名な事象なのかもしれない。ただ、ググっても出てこないし、そもそも何てググればいいのかもよく分からず、この現象にあうたびにデベロッパーツールを開いてなんだかんだやっていたので、その原因を忘れないように書き残しておく。
どういう現象か
visibility: hidden;
とopacity: 0;
で非表示にした要素を、マウスホバーで表示、マウスアウトで非表示に切り替える(アニメーション付き)際に、マウスアウトして完全に非表示となっているのにも関わらず、その要素が表示されていた部分にマウスをもっていくと消えた要素が再表示されてしまうという現象です! 基本的には問題のない挙動ではあるけど、マウスアウトして完全に消えても再表示されてしまうので、かなり邪魔くさいし明らかに邪魔。
ちょっと何言ってるか良く分からないと思うので、以下にデモを。「ここをマウスホバー」という部分に文字通りマウスホバーしてみてください。
(これはかなり極端な例なので通常ではこんなことはありえないけど)マウスアウトして完全に消えた後、表示されていた部分にマウスをもっていくと再表示されてしまうのが分かると思う。ちょうどこのテキストの上に非表示の要素が表示されるはず。かなり極端な例なので、結構時間が経っても再表示されると思う(5秒以内)。
ちなみに、Chrome、Safari、Firefoxで確認していて、それぞれ同じ現象となるので多分バグとかではなく、そもそもそういうCSSの仕様で正しい挙動なんだと思う(ちゃんと調べた訳ではないので分からない)。なお、スマホの場合はマウスホバーがないので確認してない。
追記:後に確認したところ、Firefoxではこの現象は出なかった(FirefoxでCodePenを開いて確認した時は出ていたので、CodePen側の問題だったのかもしれない)。また、Edge、IE11でもこの現象は出なかったので、Webkit系のバグなのかもしれない。
この現象が起こる条件
この現象は、表示・非表示で切り替わる要素(親要素)の子要素に transition が設定されている場合に現れる(正確に言うとtransition-property
の問題だけど)。
詳しくは、上記デモのコードを参照ください。
HTML
<div class="test__container">
<div class="test__parent">
<a class="test__child">
<p>この部分が表示されたら、一旦マウスアウトして完全に消えてからまた<em>この部分</em>をマウスホバーすると再表示される。</p>
<p>この部分の duration は0.2秒に設定しているので、本来であれば0.2秒経ってからのマウスホバーには反応しないはず。</p>
</a>
</div>
</div>
.test__parent
が親要素で、.test__child
が子要素になり、.test__child
にtransition
が設定されている。
CSS(SCSS)は以下の通り。装飾系の不要なスタイルは削除した。そして結構適当です。
SCSS
.test__container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
max-width: 320px;
height: 40px;
&::before {
content: "ここをマウスホバー";
}
&:hover .test__parent {
visibility: visible;
opacity: 1;
}
}
.test__parent {
position: absolute;
top: 100%;
left: 0;
width: 100%;
transition: all .2s ease;// 親要素の変化は0.2秒
visibility: hidden;
opacity: 0;
}
.test__child {
display: block;
color: #333333;
background: #eeeeee;
transition: all 5s ease; // 極端な例なので5sにしてある
&:hover {
color: #323232; // ここは意図的に色の変化を抑えている
background: #efefef; // ここは意図的に色の変化を抑えている
}
}
上記のようなスタイルの時に現れる。
子要素にtransition
が設定されているというところが肝で、transition-duration
の値(5s)がそっくりそのまま親要素へ伝わってしまっている。つまり、子要素のスタイルが親要素に影響を与えているということなので伝播なのではと個人的には思っている。
どうすればいいのか
ここからが本題なんだけど、子要素のアニメーション(transition
)があるからこの現象が現れるので、子要素のアニメーション(transition
)自体を失くしてしまえばこの現象は現れない。
ただ、それだと、子要素自体のホバーアニメーションもなくなってしまうのであまり良い方法とは言えない。なので、子要素の transition-property をスタイルごと個別に指定してあげれば全て丸く収まる。具体的にはall
をやめろということです。
SCSS
.test__child {
display: block;
color: #333333;
background: #eeeeee;
transition: color 5s ease, background 5s ease; //上記に合わせてるので5sにしてある
&:hover {
color: #323232; // ここは意図的に色の変化を抑えている
background: #efefef; // ここは意図的に色の変化を抑えている
}
}
この方法だと子要素自体のホバーアニメーションもつけることができ、上のデモのように再表示される(バブリングの)心配がない。
(デモのように極端な感じにはしていないけど)上記のコードペンでそれぞれの違いが分かると思う。追記:Chrome以外では確認できないので、Chromeで確認してみてください。
まとめ
記述量が減らせたり、とにかく楽なのでショートハンド等の省略系を使うことが多いけど、それが原因ではまることもよくあるので注意したい。それと、transition-property:all;
は、かなりの頻度で使用しているけど、あまりよくはないんでしょうね。