Posted at

PolymerでのCSSについて

More than 5 years have passed since last update.

最近はPolymerにどっぷりです。Polymerをこのタイミングでプロダクション環境に使おうとしているのは、正直かなり厳しいとは思いますが、人柱のつもりでまぁ・・・。いや、プロダクション環境で人柱すんなって話ですね。

それはそれとして、Polymer、というかWeb Componentsが現状全うに動作する環境、というのは、事実上Chrome(PC/Android)しかありません。iOS上ではまだ確認してませんが、運がよければネイティブ実装があるのかなー、という程度です。

実際には、Platform.jsを確認してもらえればわかりますが、他のブラウザでネイティブ実装が進んでいるものはかなり少ない、ほとんどないといっても過言ではありません。

Web Componentsを利用するモチベーションとして、デザインの完全なカプセル化が可能、というのがあげられると思います。

(JavaScriptについては現在進行形で問題として提起されているようです。この辺の解決は既存のJavaScriptの仕組みではかなり難しいような・・・)


PolymerにおけるCSSのスコープについて

詳しくはhttp://www.polymer-project.org/docs/polymer/styling.html に全部書いてあるのですが・・・。

ただ、やってみないことはなんとも???ということなので、現在ネイティブでShadow DOMに対応しているChromeと、対応していないFirefoxで、Polymerを利用したページを開いて比較してみました。

その前に、一応Polymerにおける、ネイティブで対応していない場合のルールを紹介しておきます。


  • :host疑似クラスは、 カスタム要素の名前 に変換する

  • ある要素に対するスタイルは、 カスタム要素 対象の要素 という形になるように

  • ::shadowや/deep/ というのは空白に置換される

これに加えて、::content疑似クラスの場合の処理があるのですが、それが今回実際に実験してみるものになります。なお、::content疑似クラスに対応していない場合のPolymer(というかPlatform)が用意している代替方法は3種類あるのですが、ここではその中で一番利用シーンが多いものだけを取り上げます。


:hostの書き換え

まずは以下の画像をば。

スクリーンショット 2014-07-09 21.59.05.png

みてお分かりのように、左がFirefox、右がChromeです。どちらも全く同じページを表示しているので、違いはShadow DOMがネイティブ対応されているかどうか、になります。

以下がこのページのソースになります。

index.html

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title>Content selector sample</title>
<script src="/bower_components/platform/platform.js"></script>
<link rel="import" href="sample.html">
<style>
body {
background-color : gray;
}
.heading {
color : yellow;
}
body div.heading2 {
color : yellow;
}
</style>
</head>
<body unresolved>
<h1>Simple use :host</h1>
<x-sample></x-sample>
<x-sample class="hosted"></x-sample>
<x-sample class="no-hosted">Using no hosted class</x-sample>
<div class="heading">Not imported DOM</div>

<h1>Use high priority selector</h1>
<x-sample2></x-sample2>
<x-sample2 class="hosted"></x-sample2>
<x-sample2 class="no-hosted">Using no hosted class</x-sample2>
<div class="heading">Not imported DOM</div>

<h1>Use polyfill selector</h1>
<x-sample3></x-sample3>
<x-sample3 class="hosted"></x-sample3>
<x-sample3 class="no-hosted">Using no hosted class</x-sample3>
</body>
</html>

sample.css


:host(.hosted) .heading {
color : red;
}

.heading {
color : blue;
}

:host(.hosted) .heading2 {
color : white;
}

.heading2 {
color : green;
}

sample3.css

polyfill-next-selector {content: ':host(.hosted) .bar'}

::content .bar {
font-size : 2em;
}

:host .bar {
font-size : 2em;
}

polyfill-next-selector {content: ':host > .bar'}
* ::content .bar {
color : red;
}

:host > .bar {
color: red;
}

polyfill-next-selector {content: '.container > *'}
::content * {
border : 1px solid white;
}

.container > * {
border : 1px solid white;
}

sample.html

<link rel="import" href="/bower_components/polymer/polymer.html">

<polymer-element name="x-sample">
<template>
<link rel="stylesheet" href="sample.css">
<div class="heading">Hosted DOM</div>
</template>
<script>
Polymer('x-sample', {});
</script>
</polymer-element>

<polymer-element name="x-sample2">
<template>
<link rel="stylesheet" href="sample.css">
<div class="heading2">Hosted DOM</div>
</template>
<script>
Polymer('x-sample2', {});
</script>
</polymer-element>

<polymer-element name="x-sample3">
<template>
<link rel="stylesheet" href="sample3.css">
<div class="bar">Using polyfill-next-selector</div>
<div>
<span class="bar">Nested polyfill-next-selector</span>
</div>

<div class="container">
<span>contained</span>
</div>
</template>
<script>
Polymer('x-sample3', {});
</script>
</polymer-element>

こんな感じです。さて、FirefoxとChromeでは、いくつかスタイルが異なっています。これは、Firefoxでは上述したCSSの置き換えによる、擬似的なカプセル化のため、より強いスタイルがあった場合、そっちの方が優先して適用されてしまうためです。

対してChromeでは、Shadow DOMがネイティブで実装されているため、外側のスタイルを無視して、内部のスタイルのみがあたっていることがわかります。


::contentの置き換え

こちらも画像を見てもらった方が早いので。

スクリーンショット 2014-07-09 22.07.05.png

この画像の中で、 Light DOM となっている部分が、Shadow DOMに外部から埋め込まれたDOMで、Shadow DOMと対比してLight DOMと呼ばれているものです。

さっきと同様にここで利用しているソースを以下にのせます。

index.html

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title>Host selector sample</title>
<script src="/bower_components/platform/platform.js"></script>
<link rel="import" href="sample.html">
<style>
body {
background-color : gray;
}

.styling {
color : purple;
}

html body div.body .strong-styling {
color : purple;
}
</style>
</head>
<body unresolved>
<h1>Simple use ::content</h1>
<x-sample></x-sample>
<x-sample>
<div class="styling">Light DOM</div>
</x-sample>
<x-sample class="hosted">
<div class="strong-styling">Light DOM</div>
</x-sample>
</body>
</html>

sample.css

:host(.hosted) .heading {

color : red;
}

.heading {
color : blue;
}

polyfill-next-selector {content: ':host .body div'}
::content div {
font-size : 2em;
border-top : 1px solid white;
}

polyfill-next-selector {content: ':host(.hosted) .body div'}
:host(.hosted) ::content div {
font-size : 2em;
border-top : 1px solid white;
color : green;
}

sample.html

<link rel="import" href="/bower_components/polymer/polymer.html">

<polymer-element name="x-sample">
<template>
<link rel="stylesheet" href="sample.css">
<div class="heading">Shadow DOM</div>
<div class="body">
<content></content>
</div>
</template>
<script>
Polymer('x-sample', {});
</script>
</polymer-element>

今度は、二つめのLight DOMの色が違います。これも、FirefoxではShadow DOMがPlatformが提供しているPolyfillが利用されているためです。ですが、Chromeでも同様にLight DOMに対して、強いスタイルが当てられているようにも見えますが、そもそもShadow DOMの内部に直接スタイルを当てることはできませんので、ここでは外からスタイルを当てることができていないです。


polyfill-next-selectorの注意

上のCSSでいきなり書いているpolyfill-next-selectorですが、これがShadow DOMに対応していないブラウザにおいて、擬似的に::content疑似クラスに対応するための要素になります。

polyfill-next-selector のcontent属性に対して、 置き換え後のセレクタ を渡しています。これは、その直後のセレクタに対して、 Shadow DOMに対応していなければ そのセレクタの代わりとして利用されます。対応していれば、polyfill-next-selectorに書かれているセレクタは単純に無視されます。

この置き換え後のセレクタは、割と考えるのが面倒ですが、ここをちゃんと記述しておくことで、複数のブラウザにちゃんと対応させられることができる、と思われます。

ちなみに、この置き換え処理は結構厳密?なのかどうなのかはわかりませんが、polyfill-next-selectorのcontent属性と:(コロン)の間にスペースがあると、よくわからない置換が行われて、スタイルが全くあたらない、という事態になりますのでご注意ください。


二つのスタイルのCSS

現状、Shadow DOMを実装していない側が大勢である以上、polyfill-next-selectorなどを利用することは必要だと思われます。ただ、それはCSSの中に複数のセレクタが混在してしまうため、メンテナンスコストがそれなりに発生します。その辺は仕方ないと割り切る、というのも、プラットフォームが限られていれば有り・・・かも?

あと、本当に複数の環境に対応することを考えるのであれば、Custom Elementの中でid指定を多用するのも一瞬立ち止まった方がいいかもしれません。まぁ、Core Elementsですでに大量に使っているから、どうしようも無い部分もあるのですが・・・。

どちらにせよ、まだ黎明期ですので、いろいろあるとは思います。これからWebは衰退していくことが確実、だとしても、Web Componentsがすべてのブラウザ上で完全に動作し、どんなデバイスでも部品を組み立てて全く同じようにレンダリングすることができるーそんな未来を妄想するのも楽しいものだと思います。

今回利用したすごい簡単なサンプルは、以下のリポジトリにおいてあります。

https://github.com/derui/polymer-samples

以上、PolymerのCSSスタイリングについての紹介でした。