has
ここ暫く、新しく実装された css の機能について見てきた。 まあ新しくってのは俺の主観で、つまりは俺が知らなかっただけで、主要ブラウザの全てで実装されて随分と時間が経っているものもあったりするのだが。
それらの中で最も有用だと思うのが has 擬似クラスだ。 その名の通り 「指定した条件に該当する要素を持つ」 という条件指定を可能とするもの。 子孫要素や後続要素の有無や状態を条件としてスタイル指定できる。
この has の使用例として、どっかの外人が MacOS の Dock を作ってた。 それがなかなか良い感じに動いていたので、自分でも作ってみたくなった。 メニューやランチャーとしてではなく、あのヌルッとした動きを。
ということで作ったのがこれ。
本物の Dock はピクセル単位のマウスの動きに反応するが、こいつは hover に反応するだけ。 なので動きがちょっとカクつく。 アニメーションで誤魔化してはいるが、本物に比べるとやっぱり微妙な感じになってしまう。
けどまあ、それっぽくは出来たので良しとしよう。 きっと使うことは無いと思うけど。
以下、ソースと若干の説明。
html
<div class="stage">
<div class="dock">
<div class="icon" alt="水素">H</div>
<div class="icon" alt="ヘリウム">He</div>
<div class="icon" alt="リチウム">Li</div>
<div class="icon" alt="ベリリウム">Be</div>
<div class="icon" alt="ホウ素">B</div>
<div class="icon" alt="炭素">C</div>
<div class="icon" alt="窒素">N</div>
<div class="icon" alt="酸素">O</div>
<div class="icon" alt="フッ素">F</div>
</div>
</div>
css
:root {
--base-size: 50px;
--scale-0: 1.6;
--scale-1: 1.4;
--scale-2: 1.1;
--margin: 0.5em;
}
.stage {
height: 300px;
display: grid;
align-items: end;
justify-content: center;
overflow: hidden;
background-color: black;
}
.stage .dock {
position: relative;
display: flex;
align-items: flex-end;
margin-bottom: 0.5em;
z-index: 2;
}
.stage .dock::before {
content: '';
position: absolute;
left: 0;
width: 100%;
height: calc( var( --base-size ) + var( --margin ) * 2 );
border-radius: 16px;
background-color: rgba( 255, 255, 255, 0.4 );
z-index: 1;
}
.stage .dock .icon {
position: relative;
width: var( --base-size );
height: var( --base-size );
background-color: #ffffff;
border-radius: 24%;
display: grid;
align-items: center;
justify-content: center;
margin: var( --margin );
z-index: 3;
transition-property: all;
transition-duration: 0.2s;
}
.stage .dock .icon::after { /* アイコン間に隙間を作らないための疑似要素 */
content: '';
position: absolute;
left: calc( 0px - var( --margin ) );
top: calc( 0px - var( --margin ) );
width: calc( 100% + var( --margin ) * 2 );
height: calc( 100% + var( --margin ) * 2 );
}
.stage .dock .icon:hover {
width: calc( var( --base-size ) * var( --scale-0 ) );
height: calc( var( --base-size ) * var( --scale-0 ) );
font-size: calc( 100% * var( --scale-0 ) );
}
.stage .dock .icon:hover::before { /* ツールチップ */
content: attr( alt );
position: absolute;
bottom: calc( var( --base-size ) * var( --scale-0 ) + var( --margin ) * 3 );
left: 50%;
display: block;
padding: 0.1em 1em;
translate: -50% 0;
white-space: nowrap;
text-align: center;
font-size: 50%;
color: #000000;
background-color: #d0d0d0;
border-radius: 4px;
}
.stage .dock .icon:hover + .icon , /* フォーカスしたアイコンの右側一番目 */
.stage .dock .icon:has( + .icon:hover ) { /* フォーカスしたアイコンの左側一番目 */
width: calc( var( --base-size ) * var( --scale-1 ) );
height: calc( var( --base-size ) * var( --scale-1 ) );
font-size: calc( 100% * var( --scale-1 ) );
}
.stage .dock .icon:hover + .icon + .icon , /* フォーカスしたアイコンの右側二番目 */
.stage .dock .icon:has( + .icon + .icon:hover ) { /* フォーカスしたアイコンの左側二番目 */
width: calc( var( --base-size ) * var( --scale-2 ) );
height: calc( var( --base-size ) * var( --scale-2 ) );
font-size: calc( 100% * var( --scale-2 ) );
}
has を使っているのは、マウスカーソルが乗っているアイコンの左側の指定。
- 自分の右隣が hover 状態のアイコンだったら
- 自分の右隣の更に右が hover 状態のアイコンだったら
という条件が成り立つ場合に、アイコンをその立ち位置に応じた大きさにしている。
言うまでもないが右側が後続。 先行する要素を条件にするのはこれまでも出来たが、後続の要素を条件には出来なかった。 それが has で出来るようになった。 便利。 先行と後続が同じ書式で記述できれば尚良かったが。
あと、アイコン間に隙間があると、マウスカーソルが隙間にきた時に hover じゃなくなるので、表示がガタつく。 これを防ぐために、見えているアイコンよりも一回り大きな透明領域を持たせて、これを隣接させている。
ツールチップはアイコンの before 疑似要素。 アイコンと中央揃えにするために、親であるアイコンの幅の 50% 位置から、自分の幅の 50% 分だけ左に寄せている。 指定は少々面倒だが、その分 html をシンプルにできた。
まあ、結局はどこかが複雑になるんだけどね。 html か css か、一方をシンプルにすれば他方が複雑になる。 そんな時、選択の余地があるなら html をシンプルにすべきだと、俺は思っている。 今回もそうした選択の結果。