Master PG

プログラムし続ける...

【Polymer 1.0】スタイリング


目次


導入

Polymerは、Shadow DOMスタイリングのルールに従い、カスタムエレメントのLocal DOMに対するScopedスタイル(範囲を限定して適用されるスタイル)を提供します。Scopedスタイルは<style>タグをLocal DOMテンプレート(<template>タグ)の中に配置します。

以下はシンプルなスタイリングのサンプルです。:hostはカスタムエレメント自身のスタイルを定義する場所です。::content擬似エレメントは<content>タグに挿入されるLight DOMのエレメントに対するスタイルを定義します。

⇒ ソースコード

<dom-module id="my-element">
  <template>
    <style>
      :host {
        display: block;
        border: 1px solid red;
      }
      #child-element {
        background: yellow;
      }
      .content-wrapper ::content > .special {
        background: orange;
      }
    </style>
    
    <div id="child-element">In local DOM!</div>
    <div class="content-wrapper">
      <content></content>
    </div>
  </template>
  
  <script>
    Polymer({ is: 'my-element' });
  </script>
</dom-module>
<my-element>
  <div class="special">In light DOM!</div>
</my-element>

f:id:masterpg:20160923104157p:plain

カスタムエレメントの外にスタイルを配置したい、またはカスタムエレメント間でスタイルを共有するには、共有スタイルと外部スタイルシートを参照ください。


<content>タグに挿入されるエレメントのスタイリング

このセクションでは<content>タグに挿入されるエレメントにスタイルを適用する方法を示します。CSSでは<content>タグを::content擬似エレメントで表します。


  • ::content擬似エレメントの左側には親を示すセレクタが必要です:
:host ::content div

これは次のような意味になります(実際にこうなるわけではありません):

my-element div

Shady DOM環境では、Local DOMの表示用DOMツリーに<content>タグは現れません。このため、実行時には::content擬似エレメントはCSSから削除され、また::contentのすぐ左にCSSコンビネータ(>, +, ~)がある場合も削除されます。


  • 次は<content>タグ直下に挿入されるエレメントにスタイル適用を限定します。::contentの右にはもちろんCSSコンビネータ(>, +, ~, 子孫セレクタ など)も指定できます:
:host ::content > .special

これは次のような意味になります(実際にこうなるわけではありません):

my-element > .special


次は上記をふまえたサンプルプログラムです:

⇒ ソースコード

<dom-module id="my-element">
  <template>
    <style>
      /* スタイル適用を<content>タグ直下に挿入されるエレメントに限定する */
      :host ::content > .special {
        background: greenyellow;
      }
    </style>

    <content></content>
  </template>

  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>
<my-element>
  <!-- このエレメントには.specialのスタイルが当たる  -->
  <div class="special">私はグリーンイエローです。</div>
  <div>
    <!-- このエレメントには.specialを設定してもスタイルは当たらない  -->
    <div class="special">私は透明です。</div>
  </div>
</my-element>

f:id:masterpg:20160923104249p:plain


スコープを横断するスタイリング

カスタムCSSプロパティ

Polymerは、W3Cの仕様であるCSS VariablesにおけるカスタムCSSプロパティとPolymerの差異を埋めるためのshim(緩衝材)を提供します。

カスタムCSSプロパティは、スタイルをカスタムエレメントのAPIの一部として外部に公開する際に使用します。例えばアプリケーションに"テーマ"を適用するような場合、カスタムエレメントの内部詳細を公開してテーマを適用するのではなく、カスタムエレメントでカスタムCSSプロパティを公開し、このプロパティに対してテーマを適用するといった場合に使用します。

カスタムCSSプロパティの値設定は次のように行います。プロパティの名前は先頭を--で始めます:

--my-toolbar-title-color: #cc0000;

設定された値はvar()関数を使用して取得できます:

color: var(--my-toolbar-title-color);

またvar()関数はデフォルト値を設定することもできます。利用者がカスタムCSSプロパティの値設定を行わなかった場合はこの値が使用されます:

color: var(--my-toolbar-title-color, blue);


次のサンプルでは、my-toolbarの作成者は利用者のニーズとしてタイトルを変更したい要望があることを認識しています。作成者は--my-toolbar-title-colorというカスタムCSSプロパティを定義し、このプロパティの値はツールバーのタイトルエレメントのcolorプロパティに設定されます。

⇒ ソースコード

作成者側の例:

<dom-module id="my-toolbar">
  <template>
    <style>
      :host {
        padding: 4px;
        background-color: gray;
      }
      .title {
        /* タイトル色のカスタムCSSプロパティを定義 */
        color: var(--my-toolbar-title-color);
      }
    </style>
    
    <span class="title">{{titleName}}</span>
  </template>
  
  <script>
    Polymer({
      is: 'my-toolbar',
      properties: {
        titleName: String
      }
    });
  </script>
</dom-module>

利用者側の例:

<dom-module id="my-element">
  <template>
    <style>
      /* このカスタムエレメント内のmy-toolbarのタイトルはデフォルトで白 */
      :host {
        --my-toolbar-title-color: white;
      }
      /* .warningクラスが設定されたmy-toolbarのタイトルは赤 */
      .warning {
        --my-toolbar-title-color: red;
      }
    </style>
    
    <my-toolbar title-name="This one is white."></my-toolbar>
    <my-toolbar title-name="This one is white too."></my-toolbar>
    <my-toolbar class="warning" title-name="This one is red."></my-toolbar>
  </template>
  
  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>

f:id:masterpg:20160923104346p:plain

カスタムCSSプロパティの値設定に関する制限

自身のカスタムエレメントが持つカスタムCSSプロパティへの値設定は:hostでしか行うことはできません。これ以外の場所での値設定は無効です。この内容を示す次のサンプルプログラムを確認してください:

⇒ ソースコード

<dom-module id="my-element">
  <style>
    :host {
      /**
       * 自身のカスタムエレメントのカスタムCSSプロパティの設定は:hostでのみ可能です。
       */
      --custom-color: red;
    }
    .container {
      /**
       * 自身のカスタムエレメントのカスタムCSSプロパティの設定は無効です。
       * (他のカスタムエレメントのカスタムCSSプロパティは設定可能です)
       */
      --custom-color: blue;
    }
    .child {
      /**
       * このカスタムCSSプロパティは常に赤になります。
       * (:hostでredが設定されているので。.containerで設定された値は無効です。)
       */
      color: var(--custom-color);
    }
  </style>
  
  <template>
    <div class="container">
      <div class="child">I will be red</div>
    </div>
  </template>
  
  <script>
    Polymer({is: 'my-element'});
  </script>
</dom-module>

f:id:masterpg:20160923104424p:plain


カスタムCSS Mixin

カスタムエレメントの作成者が、テーマの作成で必要となりそうな全てのCSSプロパティを洗い出し、定義することは難しいでしょう。

この問題の解決策として、Polymerが提供するカスタムプロパティのshimは、設定したいCSSプロパティ一式をパッケージ化し、パッケージされた全てのCSSプロパティをカスタムエレメントに適用するmixinという機能を提供します。

カスタムエレメントに対してmixinを適用するには@applyを使用します:

@apply(--mixin-name);

mixinの定義はカスタムCSSプロパティと似ていますが、設定する値はオブジェクトで、この中に1つ以上のスタイルを定義します:

.warning {
  --mixin-name: {
    color: red;
    font-weight: bold;
  };
}


次にmixinのサンプルを示します:

⇒ ソースコード

作成者側の例:

<dom-module id="my-toolbar">
  <template>
    <style>
      :host {
        padding: 4px;
        background-color: gray;
        /* mixinを適用 */
        @apply(--my-toolbar-theme);
      }
      .title {
        /* mixinを適用 */
        @apply(--my-toolbar-title-theme);
      }
    </style>

    <span class="title">{{titleName}}</span>
  </template>

  <script>
    Polymer({
      is: 'my-toolbar',
      properties: {
        titleName: {type: String}
      }
    });
  </script>
</dom-module>

利用者側の例:

<dom-module id="my-element">
  <template>
    <style>
      /* 通常テーマのmixinを定義し、my-toolbarに適用する */
      :host {
        --my-toolbar-theme: {
          background-color: green;
          border-radius: 4px;
          border: 1px solid gray;
        };
        --my-toolbar-title-theme: {
          color: white;
        };
      }
      /* 警告用テーマのmixinを定義し、.warningクラスが設定されたmy-toolbarに適用する */
      .warning {
        --my-toolbar-title-theme: {
          color: red;
          font-weight: bold;
        };
      }
    </style>
    
    <my-toolbar title-name="This one is white."></my-toolbar>
    <my-toolbar title-name="This one is white too."></my-toolbar>
    <my-toolbar class="warning" title-name="This one is red."></my-toolbar>
  </template>
  
  <script>
    Polymer({
      is: 'my-element'
    });
  </script>
</dom-module>

f:id:masterpg:20160923104514p:plain


カスタムCSSプロパティAPI

PolymerのカスタムCSSプロパティのshimは、カスタムエレメントの作成時に一度カスタムCSSプロパティの評価と適用を行います。ただし、テーマの変更などによってCSSクラスが動的に変更されるような場合、このカスタムCSSプロパティの再評価をエレメント(とそのサブツリー)にさせるために、this.updateStyles()を実行してください。ページ上の全てのエレメントを更新させるには、Polymer.updateStyles()を実行してください。

また、利用者はカスタムエレメントによって提供されるcustomStyleにkey-valueペアを設定することで、カスタムCSSプロパティを直接編集することができます。その後updateStyles()を実行してください。


次のサンプルでは、変更ボタンの押下によって、カスタムCSSプロパティを含んだクラスの付加/削除と、customStyleでカスタムCSSプロパティを直接編集しています。また、この編集内容を反映するためにthis.updateStyles()を実行しています:

⇒ ソースコード

作成者側の例:

<dom-module id="my-toolbar">
  <style>
    :host {
      padding: 4px;
      background-color: var(--my-toolbar-bg);
    }
    .title {
      color: var(--my-toolbar-color);
    }
  </style>
  
  <template>
    <span class="title"><content></content></span>
  </template>
  
  <script>
    Polymer({
      is: 'my-toolbar'
    });
  </script>
</dom-module>

利用者側の例:

<dom-module id="my-element">
  <style>
    :host {
      --my-toolbar-bg: blue;
      --my-toolbar-color: white;
    }
    .warning {
      --my-toolbar-color: red;
    }
  </style>
  
  <template>
    <my-toolbar id="toolBar">My awesome app</my-toolbar>
    <button on-tap="changeOnTap">変更</button>
  </template>
  
  <script>
    Polymer({
      is: 'my-element',
      _warn: false,
      changeOnTap: function () {
        this._warn = !this._warn;
        if (this._warn) {
          // my-toolbarにwarningクラス付加
          this.$.toolBar.classList.add('warning');
          // my-toolbarの背景色を緑に変更
          this.customStyle['--my-toolbar-bg'] = 'green';
        } else {
          // my-toolbarのwarningクラス削除
          this.$.toolBar.classList.remove('warning');
          // my-toolbarの背景色を青に変更
          this.customStyle['--my-toolbar-bg'] = 'blue';
        }
        // 変更したスタイルの反映(これを実行しないと変更が反映されない)
        this.updateStyles();
      }
    });
  </script>
</dom-module>

f:id:masterpg:20160923104553p:plain


メインドキュメントでのスタイリング

custom-styleをメインドキュメントで使用することで、アプリケーション全体に関わるスタイルを定義することができます:

  • ブラウザがShadow DOMをサポートしていなくても、custom-styleで定義さたスタイルはカスタムエレメントのLocal DOMへ影響しないことを保証します。
  • ブラウザがShadow DOMをサポートしていなくても、Shadow DOMの仕様である/deep/::shadowコンビネータを使用できます。
  • custom-styleでもカスタムCSSプロパティを定義できます。全てのカスタムエレメントに適用するカスタムCSSプロパティは:rootセレクタで定義してください。

⇒ ソースコード

<!DOCTYPE html>
<html>
<head>
  <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="../bower_components/polymer/polymer.html">

  <!-- custom-styleを使用してスタイルを定義 -->
  <style is="custom-style">
    /* カスタムエレメントのLocal DOMに影響をあたえずに、アプリケーション全体の設定ができる */
    div {
      color: white;
      font-style: italic;
      background-color: green;
    }
    /* /deep/ と ::shadow コンビネータを使用することができる */
    body /deep/ my-element::shadow .title {
      color: red;
    }
    /* アプリケーション全体で使用可能なカスタムCSSプロパティを定義できる */
    :root {
      --my-element-title-background: blue;
    }
  </style>

</head>
<body>

<!--
  メインドキュメントでアプリケーション全体で使用するdivに対してスタイルが定義されており、
  これが適用されてHeaderとFooterの文字は白でイタリック、背景色は緑になる。
  ただしmy-elementにはこのスタイルは適用されない。
-->
<div>Header</div>
<my-element></my-element>
<div>Footer</div>

<dom-module id="my-element">
  <template>
    <style>
      /**
       * メインドキュメントで /deep/ と ::shadow を使用して.titleにスタイルが定義されており、
       * それが適用されてタイトルの文字色が赤になる。
       */
      .title {
        /* メインドキュメントで設定された青が背景色に設定される */
        background-color: var(--my-element-title-background);
      }
    </style>

    <div class="title">My Element!</div>
  </template>

  <script>
    document.addEventListener('WebComponentsReady', function () {
      Polymer({
        is: 'my-element'
      });
    });
  </script>
</dom-module>

</body>
</html>

f:id:masterpg:20160923104637p:plain

カスタムエレメントでも上記のようなcustom-styleの機能を利用できます。ただし:rootセレクタは例外で、このセレクタはドキュメントレベルでしか利用できません。


共有スタイルと外部スタイルシート

<dom-module>の中にスタイル一式をパッケージすることより複数のカスタムエレメントでスタイル定義を共有することができます。このように<dom-module>が保持するスタイルはスタイルモジュールと呼びます。

スタイルモジュールはパッケージされたスタイル一式を名前付けし、カスタムエレメントまたはcustom-styleエレメントで利用できます。

スタイルモジュールは<dom-module>エレメントの中に定義してください:

shared-styles.html

<dom-module id="shared-styles">
  <template>
    <style>
      .red { color: red; }
    </style>
  </template>
</dom-module>

id属性はスタイルモジュールを参照するための名前を指定します。スタイルモジュールの名前はカスタムエレメントと同じ名前空間を使用するので、カスタムエレメントとスタイルモジュールでidが競合しないようにしてください。

定義されたスタイルモジュールを使用するには2つの作業が必要です。まず<link>タグでスタイルモジュールをインポートし、次に<style>タグでスタイルをインクルードします。

次の例ではカスタムエレメントでスタイルモジュールを使用しています:

<!-- スタイルモジュールをインポート -->
<link rel="import" href="../shared-styles/shared-styles.html">
<dom-module id="my-element">
  <template>
    <!-- スタイルモジュールの名前を指定してスタイルをインクルード -->
    <style include="shared-styles"></style>
    <style>
      :host {
        display: block;
      }
    </style>
    <span>My Element!</span>
  </template>
  <script>Polymer({is: 'my-element'});</script>
</dom-module>

上記例では、スタイルモジュールをインクルードする<style>と、カスタムエレメント自身のスタイルを定義する<style>のように分けていましたが、分けずに1つの<style>で記述することもできます:

<style include="shared-styles">
  :host {
    display: block;
  }
</style>

このように1つの<style>で記述した場合、カスタムエレメント自身のスタイルが定義された後にスタイルモジュールが適用されるので、スタイルモジュールの方が優先(上書き)されることになります。ただしこの動作は将来的に変更されるかもしれません。

また、custom-styleエレメントの中でもスタイルモジュールを使用できます:

<!-- スタイルモジュールをインポート -->
<link rel="import" href="../shared-styles/shared-styles.html">
<!-- スタイルモジュールの名前を指定してスタイルをインクルード -->
<style is="custom-style" include="shared-styles"></style>


次にスタイルモジュールを使用したサンプルを示します:

⇒ ソースコード

shared-styles.html

<dom-module id="shared-styles">
  <template>
    <style>
      .title { color: white; }
    </style>
  </template>
</dom-module>

index.html

<!DOCTYPE html>
<html>
<head>
  <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="../bower_components/polymer/polymer.html">
  <!-- スタイルモジュールをインポート -->
  <link rel="import" href="shared-styles/shared-styles.html">
</head>
<body>

<my-element></my-element>

<dom-module id="my-element">
  <template>
    <!-- スタイルモジュールのスタイルをインクルード -->
    <style include="shared-styles">
      .title {
        background-color: blue;
      }
    </style>

    <div class="title">My Element!</div>
  </template>

  <script>
    document.addEventListener('WebComponentsReady', function () {
      Polymer({
        is: 'my-element'
      });
    });
  </script>
</dom-module>

</body>
</html>

f:id:masterpg:20160923104713p:plain