世はReact, Vueなどのフロントエンドフレームワーク大航海時代
どんなライブラリーを使って、どんな方法でCSSを書けばよいのか
日夜、熱い議論が交わされている今日この頃‥
ブラウザ標準だけでコンポーネントもスコープドCSSも書けるんだよ!
実は一方でWebの標準仕様であるWeb Componentsが既にIE11を除く全てのモダンブラウザで動作するようになっており、IE11のサポート終了も1年を切ったことから、今後、Web Componentsという標準仕様がどんどん積極的に活用されていくことになるだろうという世情もあったのである‥!(これらは必ずしもReactやVueと競合するものでもないが)。
ちょっとここらで、一度、ReactとかVueとかから少しの間(ほんの10分程度?)頭を離して、標準技術だけで今どんなことが出来るようになっているのか、ひょっこり試してみるにはちょうどいい時期ではないでしょうか?
そう思って、1時間半ほど、Web Components(ShadowDOM, Custom Element)を素振りしてみたところ非常に良い感触が得られましたので、少しご紹介させてください。
今回つくってみたコンポーネント
はい、こんな感じのよくある、ニュースアイテム的なやつですね。Swiperとかでスライドさせて、トップページによく置いてあるやつみたいな感じです。
まず、これのHTMLとCSSを見てみましょう。
<body>
<h1>Shadow DOM </h1>
<news-container>
<news-item date='2021/07/12' thumb="https://picsum.photos/300?random=1">
<p slot="text">定価の30倍も 中古おもちゃ高騰</p>
</news-item>
<news-item date='2021/07/13' thumb="https://picsum.photos/300?random=2">
<p slot="text">大谷ら全選手が背番号44 差別と闘ったあの選手へ敬意</p>
</news-item>
</news-container>
</body>
:root {
--date-weight: bold;
}
news-item{
display:block;
border:1px solid #444;
padding:1em;
width:300px;
}
news-item img{
/* shadow dom の内側にはグローバルなスタイルはきかない*/
opacity:0;
}
news-container{
display:flex;
gap:1em;
}
まず、news-containerとかnews-itemっていう見慣れないHTMLタグがありますね?
これがWeb Componentsのカスタム要素です。
ユーザーによって定義されたセマンティックなタグになります。
そしてこのタグに対して直接CSSを当てることができます。
タグ名を見た瞬間、要素の意味がわかりますね。
非常に好感が持てませんか?
<news-item date='2021/07/12' thumb="https://picsum.photos/300?random=1">
<p slot="text">定価の30倍も 中古おもちゃ高騰</p>
</news-item>
これがShadow DOMによるnews-itemコンポーネントです。
ReactやVueをさわったことのある人なら、コードを見たらどういうふうにレンダリングされるか大体イメージできるのではないでしょうか?
dateやthumbがpropsでslotのタグが所定の場所にマウントされる感じですね。
ここで注目して欲しいのは、実は↑で書いたCSSにあった効かないプロパティです。
news-item img{
/* shadow dom の内側にはグローバルなスタイルはきかない*/
opacity:0;
}
コメントで書いてあるのですが、このnews-itemというShadow DOMの中のimg要素には Shadow DOMの中からしかアクセスできません。よってShadow DOMの外側のCSSは内側には影響を及ぼさないので、安全性が担保されます。
素敵じゃないですか?
ここでやっと、news-itemというコンポーネントを定義しているjsのコードを見てみましょう
const template = document.createElement('template');
template.innerHTML = `
<style>
.date{
font-weight: var(--date-weight);
}
.thumb{
width:100%;
border: 1px solid rgba(0,0,0,1);
}
</style>
<p class='date'></p>
<img class='thumb'/>
<slot name='text' class='news-text'></slot>
`
class NewsItem extends HTMLElement{
constructor(){
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
this.shadowRoot.querySelector('.date').innerText = this.getAttribute('date');
this.shadowRoot.querySelector('.thumb').src = this.getAttribute('thumb')
}
}
customElements.define('news-item',NewsItem)
template内にHTMLの雛形とCSSを書いてあげて、それに属性値の代入やスロットのマウントをおこなっています。
このtemplate内のcssはスコープ付きcssになっていて、コンポーネントの外に影響を及ぼしません。
安全性が確保されているので、シンプルなセレクターで問題がありません。
ちなみに、font-weight: var(--date-weight);と書いてあるように、:rootで定義されたCSSカスタムプロパティはコンポーネント内で利用することができますので、デザイントークンなどとも相性は良いです。
さて、今回つくったコンポーネントのコードは以上になりますが、今一度思い出してもらいたいことがあります。
これらは全てWebの標準技術です。
ここで実装したコンポーネント、カスタムタグ、スコープ付きCSSは、ブラウザの標準機能のみで機能しています。
ReactもVueもCSS-in-JSライブラリーもwebpackも使用されていません。
ただのHTMLとCSSとJSなので、ブラウザのAPIが変わらない限り、Web Componentsによって書かれたコードやCSSはメンテナンスしなくても動き続けることが保証されています。コードの変更を確認するのに、ビルドする必要すらありません!
そして、これらの機能を利用するのにはいわゆるモダンフロントエンドの環境構築は必要ないので、node.jsなどを扱わないマークアップエンジニアの方なども、スコープ付きCSSやコンポーネント化の恩恵を受けることができるようになりますし、Ruby on RailsなどのフレームワークにもWeb Componentsはすんなり入り込んでいくことでしょう。
個人的には、セマンティックにカスタムタグ名を名付けることができるカスタム要素と、標準機能としてのスコープ付きCSSに感銘を受けました。Shadow DOM内のスコープ付きCSSのレンダリングパフォーマンスがCSS Modulesなどに比べてどうなのかというところが私にはわかっておりませんが、Webの標準機能でStyled-Componentsの安全性とセマンティックさとCSS Modulesのレンダリングパフォーマンスのいいとこ取りが出来るようになれば、CSS何で書けばいいのか?問題に対して一つの模範解答が提出されるのではないかと非常に期待を持ちました。
余談になりますが、TailwindCSSはShadow DOMとの相性が非常に悪いみたいです
Tailwind CSSが私には合わなかった理由という記事では、ハック的な方法を用いてビルドプロセスを通してインジェクトしないとTailwindCSSはShadow DOMでは全く使用できないと書かれています。
総評として、今回わずか1時間半ほどShadow DOMに触れてみただけでしたが、私は非常にこの標準技術に希望を持つことができました。IE11のサポートが完全終了してWeb Componentsが躊躇なく使われる世界になると、またReactやVueやRailsなどのフレームワークの実装やプラクティスも影響を受けて進化していきそうな気がします。それらの進化の方向性を予想しつつ、コードを書いていくことができたら楽しいかもしれませんね?
皆さんも是非、Shadow DOMやWeb Componentsをさわってみてください!
なお、今回の記事で紹介したコードはCodepenにアップしてありますので、任意で参考にしたりイジってみたりしてください。
今回、えらそうに記事を書かせていただきましたが、私もWeb Componentsについて全く初心者ですので、何か間違えていることや面白いことがあったら教えてもらえるとありがたいです。
これからどんどん盛り上がってきそうなので、技術記事などもどんどん増えてくればいいなと思います!
以上、最後までお読みいただきありがとうございました!
追記 ShadowDOMにおけるReset CSSの懸念について
ブラウザ標準で完全にCSSのスコープをコンポーネント毎に切り離せるShadowDOMの素晴らしさはわかりましたが、一点、『reset.cssはどうすればいいの?』問題だけが、気がかりでした。
これは、TailwindCSSとShadowDOMの相性の悪さでも共通する話ですが、まさか全てのShadowDOMにreset.cssを読み込むのは辛すぎますよね‥
たまたま、2日前にTwitterで最新のCSSの仕様が紹介されていました。
*:where(:not(iframe, canvas, img, svg, video):not(svg *)){
all: unset;
display: revert;
}
これでユーザーエージェントのデフォルトの全てのcssをリセットできるらしいです!(モダンブラウザーは全て対応済み)
参考 the-new-css-reset
あとは
*,
*::before,
*::after{
box-sizing: border-box;
}
とかも欲しくなりそうですが、これくらいならShadowDOMのreset.css問題はなんなく解決できそうですね!よかった!