28
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Polymer1.0におけるStylingとCSS Variables

Last updated at Posted at 2015-06-13

laco0416です。Polymer 1.0におけるShadow DOMとStyling、CSS Variablesの話をします。

Shadow DOM

PolymerはWebComponentsの三本柱の1つ、Shadow DOMを用いることで、Custom Elements内外のDOMを隔離します。隔離されたCustom Elements内のDOMを Local DOM と呼びます。

Local DOMを生成するのにPolymerは2つの実装を使い分けることができます。基本的にはPolymerに含まれる Shady DOM というShadow DOM実装を使用しますが、ユーザーが明示的に指定することでブラウザにビルトインのShadow DOM実装を使ってLocal DOMを生成することができます。(将来的にはデフォルトがビルトイン実装の使用になる予定です)
ビルトイン実装を使おうとして見つからなかった場合は自動でShady DOMに切り替えられます。しかし、基本的にはこの2つの実装の違いをユーザーが気にする必要はありません。

<html>
<head>
  <meta charset="utf-8">
  <script src="components/webcomponentsjs/webcomponents-lite.js"></script>
  <script>
    //明示的にビルトインShadow DOM実装を指定する
    window.Polymer = window.Polymer || {};
    window.Polymer.dom = 'shadow';
  </script>
  <!-- import a component that relies on Polymer -->
  <link rel="import" href="elements/my-app.html">
</head>
<body>

Local DOMはCSSのScopeを作り、内部からのStylingの漏洩を防ぐことができます。この恩恵によりどこで再利用しても外部のアプリケーションに影響を与えないコンポーネントを作成することができます。これはコンポーネントに更新があってもアプリケーションを壊さないということが保証されるWebComponentsの重要なファクターです。

Local DOM内部でのStyling

まずLocal DOMをどのようにStylingするのかをまとめます。

Local DOMのStyling

Local DOM内部では<dom-module>の子として<style>タグを使うことでStyleを適用することができます。内部の子要素に関しては普通のCSSと同様に#child-elementのようにセレクタを使うことができます。<template>の位置、つまりLocal DOM自身にStyleを適用するには特別な:hostセレクタを使います。(これはShadow DOMの仕様です)

<dom-module id="my-element">
  <style>
    :host {
      display: block;
      border: 1px solid red;
    }
    #child-element {
      background: yellow;
    }
  </style>

  <template>
    <div id="child-element">In local DOM!</div>
  </template>

  <script>
    Polymer({
      is: 'my-element'
    });

  </script>
</dom-module>

image

分散ノード

Custom Elementsのテンプレート内に書かれる子要素ではなく、使用される際に挿入される要素を分散ノードと呼びます。

分散ノードの例
<my-element>
  <p>Content</p>
</my-element>

しかし前項の<my-element>には分散ノードを表示する仕組みがないので、これは何も変化しません。
分散ノードをCustom Elementsで使うには<content>タグを使用します。

  <template>
    <div id="child-element">In local DOM!</div>
    <content></content>
  </template>

これで分散ノードがテンプレートの一部として描画されるようになりました。

image

このcontentはCustom Elementsの一部として描画されますが、属しているDOMはグローバルなので、Local DOMのCSS Scopeの適用外です。

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

<dom-module id="my-element">
  <style>
    :host {
      display: block;
      border: 1px solid red;
    }
    div {
      color: red;
    }
  </style>

  <template>
    <div id="child-element">In local DOM!</div>
    <content></content>
  </template>

  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>
index.html
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="elements/my-element.html"/>
</head>
<body>
  <my-element>
    <div>Inner Content</div>
  </my-element>
</body>
</html>

image

Polymer 0.5では分散ノードに対してStylingを行うためには::contentセレクタを使いましたが、Polymer 1.0が使用しているShady DOMでは::content擬似要素はレンダリング時に削除されてしまうため、::contentによるStylingはできなくなりました。分散ノードへLocal DOM側からStylingするには何かしらのラッパー要素を定義する必要があるでしょう。

  <style>
    :host {
      display: block;
      border: 1px solid red;
    }

    .content-wrapper {
      background: orange;
    }

  </style>

  <template>
    <div id="child-element">In local DOM!</div>
    <div class="content-wrapper">
      <content></content>
    </div>
  </template>

image

外部からのStyling

ところで、本来Shadow DOMに定められた仕様では、Shadow DOMは内からのCSSの漏洩だけでなく、外からの侵入も遮断します。Polymer 0.5ではこの仕様に則り、グローバルのDOM( Light DOM とも呼ばれる)からCustom ElementsのStyleを変更するには/deep/セレクタや、::shadowセレクタを使っていました。そのため、例えばTwitter BootstrapなどのテーマCSSはPolymerと同時に使うことが困難でした。

Polymer 1.0を開発するにあたり、開発チームはこれらの再実装を保留しています。複雑なCSSセレクタによる内部Styleの変更はコンポーネント内部の変更に対して脆弱であり、Polymerの実用性・有用性に悪影響があると判断されたためです。現在のPolymer 1.0では外部からのStylingの遮断を取り払い、 Local DOMは外部に対して暴露されています 。Light DOMでのStylingはLocal DOMよりも 低い 優先度で内部のレンダリングに影響します。

index.html
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="elements/my-element.html"/>
  <style>
    div, p {
      color: orange;
    }
  </style>
</head>
<body>
  <my-element>
    <div>Light Div</div>
    <p>Light Paragraph</p>
  </my-element>
</body>
</html>
my-element.html
<link rel="import" href="../bower_components/polymer/polymer.html"/>

<dom-module id="my-element">
  <style>
    :host {
      display: block;
      border: 1px solid red;
    }

    div {
      color: red;
    }
  </style>

  <template>
    <div>Shadow Div</div>
    <p>Shadow Paragraph</p>

    <content></content>
  </template>

  <script>

    Polymer({
      is: 'my-element'
    });

  </script>
</dom-module>

image

本来Themingを行うのであればLight DOMのCSS ScopeはLocal DOMよりも上位でなければならないですが、現在の仕様では内部で決められているStyleに関しては変更できません。これは過渡的な処置であり、WebComponentsの仕様から外れていることも問題です。

CSS Variablesを用いた解決策

外部からLocal DOMよりも高い優先度でStylingしたい場合、コンポーネント開発者はCSS Variablesによる独自CSS変数を変更手段として用意するのが現在のスマートな手法です。

次の例では外部からCSS変数--my-element-div-colorを設定し、コンポーネント側でvar(--my-element-div-color, red)によって呼び出しています。(redはデフォルト値です)

index.html
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="elements/my-element.html"/>
  <style is="custom-style">
    div, p {
      color: orange;
    }

    :root {
      --my-element-div-color: blue;
    }
  </style>
</head>
<body>
  <my-element>
    <div>Light Div</div>
    <p>Light Paragraph</p>
  </my-element>
</body>
</html>
my-element.html
<link rel="import" href="../bower_components/polymer/polymer.html"/>

<dom-module id="my-element">
  <style>
    :host {
      display: block;
      border: 1px solid red;
    }

    div {
      color: var(--my-element-div-color, red);
    }
  </style>

  <template>
    <div>Shadow Div</div>
    <p>Shadow Paragraph</p>

    <content></content>
  </template>

  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>

image

<style is="custom-style">はとても重要な宣言です。Custom Elementsではない場所(=<dom-module>外)でPolymerによるCSS Variablesのshimを使うためにはこの宣言が必須です。逆に言えば、Custom Elementsの中では暗黙的にCSS Variablesを使うことができます。

この例ではルートセレクタ:rootを用いています。このセレクタはそのドキュメントの最上位ルートのスコープでStyleを設定します。CSS VariablesによるThemingを行うのであれば:rootを使うのがベターでしょう。もちろんmy-elementをセレクタに指定しても大丈夫です。

Mixins

PolymerのCSS Variables shimでは独自機能としてCSS VariablesのMixinが可能です。個別の変数をvar()で呼び出すのではなく、ひとかたまりのStyleを適用することができます。
次の例ではさっきと同じStylingをMixinを用いて行っています。Mixinを作成するには変数名でネストされたStyleを記述し、利用する側は@apply()関数で変数を呼び出します。

index.html
  <style is="custom-style">
    div, p {
      color: orange;
    }

    :root {
      --my-element-div-theme: {
         color: blue;
       };
    }
  </style>
my-element.html
  <style>
    :host {
      display: block;
      border: 1px solid red;
    }

    div {
      @apply(--my-element-div-theme)
    }
  </style>

外部CSSファイルを読み込む

ここまでのStylingはすべて<style>タグをドキュメントやコンポーネントに直接書いていましたが、実際に開発を行う際はCSSファイルは分離していたほうがバージョン管理やデザイナーとの分業を考えた時に便利です。

Custom ElementsからCSSファイルを読み込む

Custom Elementsから外部のCSSファイルを読み込むには<link rel="import" type="css" href="my-element.css">のように、HTML Importsと同じように<link>タグを使います。

<link rel="import" href="../bower_components/polymer/polymer.html"/>
<dom-module id="my-element">
  <link rel="import" type="css" href="my-element.css"/>
  <template>
    <div>Shadow Div</div>
    <p>Shadow Paragraph</p>
    <content></content>
  </template>
  
  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>
:host {
  display: block;
  border: 1px solid red;
}

div {
  @apply(--my-element-div-theme)
}

外部CSSの中であっても呼び出し先がLocal DOMであればそのままCSS VariablesもMixinsも使用可能です。

ドキュメントからCSS Variablesを用いたスタイルシートを読み込む

Light DOM(index.html)へCSSを読み込むには普通<link rel="stylesheet" href="style.css"/>のように書きますが、Light DOMにはCSS VariablesやMixinのshimは適用されていません(そのため先述のis="custom-styleが存在する)。
Light DOMでCSS Variablesを外部ファイル化するにはHTML Importsを利用し、<style is="custom-style">をルート要素とするHTMLを読み込みます。

index.html
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <link rel="import" href="elements/my-element.html"/>
  <link rel="import" href="my-styling.html"/>
</head>
<body>
  <my-element>
    <div>Light Div</div>
    <p>Light Paragraph</p>
  </my-element>
</body>
</html>
my-styling.html
<style is="custom-style">
  div, p {
    color: orange;
  }

  :root {
  --my-element-div-theme: {
     color: blue;
   };
  }
</style>

まとめ

コンポーネント開発者がStylingに関してやるべきこと

  • CSS Variablesを使って外部からのStylingを可能な窓口を作る
  • どうしても絶対に変えられたくない部分に関してはハードコーディングする
  • CSS変数の命名規則を統一し、ユーザーがドキュメントを読まなくても推測しやすいようにする(--<要素名>-<要素|クラス>-<プロパティ>がスタンダードになりそう)
  • コンポーネントのCSS変数のドキュメントを整備する(リポジトリのREADMEなど)

コンポーネント利用者がStylingに関してやるべきこと

  • ドキュメントを読んで使えるCSS変数を知る
  • ソースを読んで使えるCSS変数を知る
  • 変更できてしかるべきプロパティがハードコーディングされていたら開発者を殴りに行くか、フォークして自分でカスタマイズする(PR飛ばしてあげればなおよし)
28
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
30

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?