この記事の概要
以下の内容、ノリで作ったら思ったよりウケたので記事にします。
次のページから実際に触れます(なお、FirefoxとInternet Explorerでは動かないのでご了承ください)。
リポジトリも公開しています。
この記事に書いてあるテクニック自体は役に立つと思いますが、内容そのものは何にも役立たないのでご注意ください!
何をしているのか
CSSの:has()
を使って、子要素の操作をキャッチして親要素のスタイルを変えています。
そのため、プレーンなHTMLとCSSだけで冒頭のツイート内動画の内容を実現できました。
ただし、:has()
を使える環境が割と増えてきたとは言え、Firefoxや少し古いSafariは未対応です。
業務で使うのはまだ時期尚早かもしれないので、そこはご注意ください。
どうしても使いたい場合、ポリフィルに頼るのがベターでしょうか。
過去に記事を書いているので、興味のある方はこちらもどうぞ!
実装内容と解説
今回の内容のコアに関わる部分を抜粋して解説します。
<!-- headなどは省略 -->
<body>
<div class="container">
<header class="header">
<span class="logotype">Dummmmy</span>
</header>
<main class="main">
<div class="panel">
<h1 class="title">ぶっ壊れるページ</h1>
<input type="text" placeholder="文字を入力しないでください" class="input">
</div>
</main>
<footer class="footer">
<div class="footer-inner">
<span>Dummmmy</span>
<span class="copyright">© 2022 Dummmmy Inc.</span>
</div>
</footer>
</div>
</body>
:root {
--header-height: 64px;
--main-height: calc(100vh - var(--header-height) - var(--footer-height));
--footer-height: 240px;
}
.container {
position: relative;
}
.header {
height: var(--header-height);
position: relative;
transform-origin: top left;
z-index: 1;
}
.main {
height: var(--main-height);
position: relative;
}
.panel {
position: relative;
}
.title {
position: relative;
}
.input {
position: relative;
}
.footer {
height: var(--footer-height);
position: relative;
}
@keyframes fall-header {
0% {
left: 0;
rotate: 0;
top: 0;
}
/* 省略 */
100% {
left: 2vw;
rotate: 12deg;
top: calc(var(--main-height));
}
}
.container:has(.input:not(:placeholder-shown)) .header {
animation: fall-header 600ms ease-in-out 0ms 1 forwards;
}
@keyframes fall-panel {
0% {
left: 0;
rotate: 0;
top: var(--header-height);
}
/* 省略 */
100% {
left: -2vw;
rotate: 3deg;
top: calc(2 * var(--header-height));
}
}
.container:has(.input:not(:placeholder-shown)) .panel {
animation: fall-panel 300ms ease-in-out 400ms 1 forwards;
}
@keyframes fall-title {
0% {
left: 0;
rotate: 0;
}
100% {
left: 1vw;
rotate: 3deg;
}
}
.container:has(.input:not(:placeholder-shown)) .title {
animation: fall-title 350ms ease-in-out 450ms 1 forwards;
}
@keyframes fall-input {
0% {
left: 0;
rotate: 0;
top: 0;
}
100% {
left: -5vw;
rotate: -5deg;
top: 50px;
}
}
.container:has(.input:not(:placeholder-shown)) .input {
animation: fall-input 200ms ease-in-out 450ms 1 forwards;
}
@keyframes fall-footer {
0% {
left: 0;
rotate: 0;
top: 0;
}
/* 省略 */
100% {
left: 1vw;
rotate: -2deg;
top: 80px;
}
}
.container:has(.input:not(:placeholder-shown)) .footer {
animation: fall-footer 400ms ease-in-out 200ms 1 forwards;
}
0. CSSカスタムプロパティで高さを再利用
この内容は、単に少し効率が良くなるだけです。
CSSの冒頭で:root
の中でheader
とmain
とfooter
の高さをカスタムプロパティとして登録しています。
これにより「要素の高さ分だけアニメーションさせる」のが容易になります。
1. 動かしたい要素をすべてposition: relative;
で配置する
とんでもないコードです。
実務でこんなコードを書いたらNGですが、今回はギミック優先です。
.container
や.header
など、動かしたい要素はすべてposition: relative;
です。
2. :has
を使う
今回の記事のメインです。
CSS内で何箇所か.container:has(.input:not(:placeholder-shown)) .header {}
といった記述をしています。
(.header
の部分は.panel
だったり.title
だったりしますが、どこも仕組みは一緒です)
個別に和訳(?)すると以下のようになります。
- .container:has()
- 「
.container
の子要素が:has()
の()
内にマッチしたら〜」
- 「
- .input:not()
- 「
.input
のうち:not()
内にマッチしなかったら〜」
- 「
- :placeholder-shown
- 「
placeholder
が表示されていたら〜」
- 「
全体を通すと「placeholder
が表示されていない.input
を子要素に持つ.container
の子要素である.header
にマッチしたら〜」となっています。
長々と書きましたが、この指定によって「ページ内に唯一存在するinput
に文字を入力しただけでページ全体のレイアウトが壊れる」を実現しています。
3. 気合いでアニメーションを書く
特にライブラリなど使っていないので「物が落下してるっぽいアニメーション」を勘で書きました。
これはこれで、いつか記事にするかもしれません。
今回は省略していますが、リポジトリは公開しているので気になる方はこちらをご覧ください。
最後に
冒頭にも書いた通り、今回のコードそのものは実務では全然役立ちません。
しかし「子要素の操作をきっかけに親要素のスタイルを触りたい」場面はごくたまにあります(私だけかもしれませんが……)。
もしそういう場面に遭遇した際、この記事がお役に立てれば幸いです。
最後まで読んでくださってありがとうございます!
Twitterでも情報を発信しているので、良かったらフォローお願いします!
Devトークでのお話してくださる方も募集中です!