ChromeのExperimental WebKit/JavaScript Features

追記(2018年4月16日):現在はWebKitではなくBlinkになったため“Experimental Web Platform features”というセクションになっている。さらにはもっと簡単に、runtime_enabled_features.json5というファイルを見ればどんな機能があるのかがわかるようになっている。


chrome://flagsにはいろんなランタイムフラグがある。CSS ShadersやらVP9 playbackやら、実験中の機能を有効にできるようになっている(動くかどうかはしらない)。

その中にこんなフラグがある。

Enable experimental WebKit features.

Enable experimental WebKit features that are in development.

Enable Experimental JavaScript

Enable web pages to use experimental JavaScript features.

気になる。けど何の機能が入ってるのかわからない。
というわけで探してみる。

Experimental WebKit Features

WebKitの方は、検索したらそのフラグの誕生経緯が見つかった。

WebKitで実装中の機能を試すための仕組みがほしい→WebKitの機能ごとにランタイムフラグつくってちゃしょうがない、という流れでこのフラグができたらしい。

フラグの導入時にはRegions, Exclusions, Scoped stylesheets, Shadow DOM, CSS Variablesが含まれている。ただShadow DOMなんかはM25で有効にされたはずだ。今は何がどうなっているのか。

導入された時点のソースを読むと、switches::kEnableExperimentalWebKitFeatures というスイッチが導入されて、それを元に処理の有効無効が切り分けられているように読めた。というわけで、それをキーに検索してみたところ、ふたつのファイルが引っかかった。content/renderer/render_thread_impl.cccontent/browser/web_contents/web_contents_impl.cc

ファイルを開いて kEnableExperimentalWebKitFeaturesを検索して…見つかった。
いまのところ、Chromium trunkでExperimental WebKit Featuresとされているのはこれらしい。

  • Scoped Stylesheets
  • Custom DOM Elements
  • Experimental Content Security Policy
  • HTML <dialog> Element
  • HTML <iframe seamless> Attribute
  • CSS Regions
  • CSS Exclusions
  • CSS Grid Layout
  • CSS Sticky Positioning (position: -webkit-sticky)
  • CSS Variables

ちなみにランタイムフラグで制御できる(?)機能の一覧は、WebKit側の WebRuntimeFeatures.h にまとまっているみたい。

Experimental JavaScript Features

JavaScriptの機能はV8を参照することになる。とりあえずフラグから辿ることにする。

{
  "enable-javascript-harmony",
  IDS_FLAGS_ENABLE_JAVASCRIPT_HARMONY_NAME,
  IDS_FLAGS_ENABLE_JAVASCRIPT_HARMONY_DESCRIPTION,
  kOsAll,
  SINGLE_VALUE_TYPE_AND_VALUE(switches::kJavaScriptFlags, "--harmony")
},

どうやら --harmony フラグをつけて起動するようになっているらしい。じゃあ、そのフラグを定義するV8のファイルを見つければいい。

てきとーな単語で検索したらすぐ見つかった。

このなかで “harmony” を検索すると、それっぽい行が。

DEFINE_bool(harmony_typeof, false, "enable harmony semantics for typeof")
DEFINE_bool(harmony_scoping, false, "enable harmony block scoping")
DEFINE_bool(harmony_modules, false,
            "enable harmony modules (implies block scoping)")
DEFINE_bool(harmony_symbols, false,
            "enable harmony symbols (a.k.a. private names)")
DEFINE_bool(harmony_proxies, false, "enable harmony proxies")
DEFINE_bool(harmony_collections, false,
            "enable harmony collections (sets, maps, and weak maps)")
DEFINE_bool(harmony_observation, false,
            "enable harmony object observation (implies harmony collections")
DEFINE_bool(harmony, false, "enable all harmony features (except typeof)")
DEFINE_implication(harmony, harmony_scoping)
DEFINE_implication(harmony, harmony_modules)
DEFINE_implication(harmony, harmony_symbols)
DEFINE_implication(harmony, harmony_proxies)
DEFINE_implication(harmony, harmony_collections)
DEFINE_implication(harmony, harmony_observation)
DEFINE_implication(harmony_modules, harmony_scoping)
DEFINE_implication(harmony_observation, harmony_collections)

DEFINEなんたらの読み方がわからないけれど、harmony フラグでは次のが有効になるのかな?

  • Block scoping (let)
  • Modules
  • Symbols
  • Proxies
  • Collections (Set, Map, WeakMap)
  • Observation (object.observe)

Rest paramsやDefault paramsほしいんだけどな。まあそれはおいとこう。

Sassでlinear-gradient()のmixinをつくる その5

CSS Preprocessor Advent Calendar 2012の記事の続編…というか完結編です。
3月まで持ち越すつもりはなかったのに……

Part 7: あとはもう、まとめる

今回はPart 4, Part 5, Part 6で作ったmixinをひとつのmixinにします。どのmixinも type-of() 関数で、mixinの最初の引数を調べて処理を分岐する作りになっているので、分岐先の処理を統合するだけですね。

// config
$lg_support_prefixes: '-webkit-';
$lg_support_webkit_gradient: true;
$lg_support_svg_gradient: true;

@mixin linear-gradient( $first, $rest... ) {

    $prefixes: $lg_support_prefixes;
    $support_wk: $lg_support_webkit_gradient;
    $support_svg: $lg_support_svg_gradient;

    $direction: false;
    $legacy_direction: false;
    $webkit_direction: false;
    $svg_direction: false;

    $colorstops: false;

    // for -webkit-gradient()
    $wk_LT: 0 0;
    $wk_LB: 0 100%;
    $wk_RT: 100% 0;
    $wk_RB: 100% 100%;
    $wk_to_B: $wk_LT, $wk_LB;
    $wk_to_L: $wk_LT, $wk_RT;
    $wk_to_T: $wk_LB, $wk_LT;
    $wk_to_R: $wk_RT, $wk_LT;
    $wk_to_TL: $wk_RB, $wk_LT;
    $wk_to_TR: $wk_LB, $wk_LT;
    $wk_to_BR: $wk_LT, $wk_RB;
    $wk_to_BL: $wk_RT, $wk_LB;
    
    // for SVG data: URL
    $x1_0: ''; // '%20x1%3D%220%25%22';   // ' x1="0%"'   // default
    $x1_1: '%20x1%3D%22100%25%22';        // ' x1="100%'
    $x2_0: '%20x2%3D%220%25%22';          // ' x2="0%"'
    $x2_1: ''; // '%20x2%3D%22100%25%22'; // ' x2="100%"' // default
    $y1_0: ''; // '%20y1%3D%220%25%22';   // ' y1="0%"'   // default
    $y1_1: '%20y1%3D%22100%25%22';        // ' y1="100%'
    $y2_0: ''; // '%20y2%3D%220%25%22';   // ' y2="0%"'   // default
    $y2_1: '%20y2%3D%22100%25%22';        // ' y2="100%'
    $svg_to_B: $x2_0 + $y2_1; // ' x2="0%" y2="100%"'
    $svg_to_L: $x1_1 + $x2_0; // ' x1="100%" x2="0%"'
    $svg_to_T: $y1_1 + $x2_0; // ' y1="100%" x2="0%"'
    $svg_to_R: $x1_0 + $x2_1; // ' x1="0%" x2="100%"'
    $svg_to_TL: $x1_1 + $y1_1 + $x2_0 + $y2_0;
    $svg_to_TR: $x1_0 + $y1_1 + $x2_1 + $y2_0;
    $svg_to_BR: $x1_0 + $y1_0 + $x2_1 + $y2_1;
    $svg_to_BL: $x1_1 + $y1_0 + $x2_0 + $y2_1;

    // utilities
    $type_first: type-of( $first );
    $veryfirst: nth( $first, 1 );
    $type_veryfirst: type-of( $veryfirst );

    // colorstop (no direction specified)
    @if $type_first == 'color' or $type_veryfirst == 'color' {
        // gradient goes from top to bottom
        $direction: null;
        $legacy_direction: null;
        $webkit_direction: $wk_to_B;
        $svg_direction: $svg_to_B;

        // join $first and $rest to form $colorstops
        // simply join() or append() doesn't work: they split $first into color and stop
        $colorstops: append( (), $first, comma );
        @each $colorstop in $rest {
            $colorstops: append( $colorstops, $colorstop, comma );
        }
    }
    // keywords
    @else if $type_first == 'list' and $veryfirst == 'to' {
        // which direction?
        $standard_keywords:
            to bottom, to left, to top, to right,
            to top left, to top right, to bottom right, to bottom left,
            to left top, to right top, to right bottom, to left bottom;
        $legacy_keywords:
            top, right, bottom, left,
            bottom right, bottom left, top left, top right,
            right bottom, left bottom, left top, right top;
        $webkit_keywords:
            ($wk_to_B), ($wk_to_L), ($wk_to_T), ($wk_to_R),
            ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL),
            ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL);
        $svg_keywords:
            ($svg_to_B), ($svg_to_L), ($svg_to_T), ($svg_to_R),
            ($svg_to_TL), ($svg_to_TR), ($svg_to_BR), ($svg_to_BL),
            ($svg_to_TL), ($svg_to_TR), ($svg_to_BR), ($svg_to_BL);

        // where to go
        $idx: index( $standard_keywords, $first );

        // if a valid keyword(s) passed
        @if $idx >= 1 {
            $direction: if( $idx == 1, null, nth( $standard_keywords, $idx ) );
            $legacy_direction: if( $idx == 1, null, nth( $legacy_keywords, $idx ) );
            $webkit_direction: nth( $webkit_keywords, $idx );
            $svg_direction: nth( $svg_keywords, $idx );

            $colorstops: $rest;
        }
        @else {
            // error
            @warn 'passed direction is not a valid parameter: #{$first}';
        }
    }
    // <angle>
    @else if $type_first == 'number' and unit( $first ) == 'deg' {
    // ISSUE: what about `rad` or `turn` ?
    
        $direction: $first;
        
        // <angle> is neither supported in -webkit-gradient() nor in SVG
        // but some values can be converted to keywords

        // if the gradient goes down
        @if index( (-900, -540, -180, 180, 540, 900), $first ) {
            $direction: null;
            $legacy_direction: null;
            $webkit_direction: $wk_to_B;
            $svg_direction: $svg_to_B;
        }
        // goes left
        @else if index( (-810, -450, -90, 270, 630, 990), $first ) {
            // $direction: to left;
            // $legacy_direction: right;
            $webkit_direction: $wk_to_L;
            $svg_direction: $svg_to_L;
        }
        // goes up
        @else if index( (-1080, -720, -360, 0, 360, 720, 1080), $first ) {
            // $direction: to top;
            // $legacy_direction: bottom;
            $webkit_direction: $wk_to_T;
            $svg_direction: $svg_to_T;
        }
        // goes right
        @else if index( (-990, -630, -270, 90, 450, 810), $first ) {
            // $direction: to right;
            // $legacy_direction: left;
            $webkit_direction: $wk_to_R;
            $svg_direction: $svg_to_R;
        }
        @else {
            // convert <angle> for prefixed linear-gradient()
            $legacy_direction: ( $first + 450 ) % 360;

            // no fallback for legacy webkit and SVG, turning it off
            @if $support_wk {
                @warn 'invalid argument: #{$first}. -webkit-gradient() declaration dropped.';
                $support_wk: false;
            }
            @if $support_svg {
                @warn 'invalid argument: #{$first}. SVG declaration dropped.';
                $support_svg: false;
            }
        }

        $colorstops: $rest;
    }
    @else {
        // error.
        @warn 'invalid argument: #{$first}. it must either be <color> (+ <stop>), keywords, or <angle>.';
    }

    // output gradients
    @if $colorstops {

        // SVG gradient image
        @if $support_svg {

            $svg_colorstops: '';

            $i: 1;
            $len_colorstops: length( $colorstops );
            $okay: is_compatible_colorstop( nth( $colorstops, $i ) );

            @while $i <= $len_colorstops and $okay {
                $current_colorstop: nth( $colorstops, $i );
                $okay: is_compatible_colorstop( $current_colorstop );
                $svg_colorstops: $svg_colorstops + convert_colorstop( $current_colorstop, $format: 'svg' );
                $i: $i + 1;
            }

            @if $okay {
                $svg_data: 'data:image/svg+xml,' +
                    '%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E' +
                        '%3ClinearGradient%20id%3D%22g%22' + $svg_direction + '%3E' +
                            $svg_colorstops +
                        '%3C%2FlinearGradient%3E' +
                    '%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%23g)%22%2F%3E%3C' +
                    '%2Fsvg%3E';
                background-image: url( $svg_data );
            }
            @else {
                @warn 'invalid argument: #{nth( $colorstops, $i )}. declaration dropped.';
            }
        }
        
        // -webkit-gradient
        @if $support_wk {

            $webkit_colorstops: null;

            $i: 1;
            $len_colorstops: length( $colorstops );
            $okay: is_compatible_colorstop( nth( $colorstops, $i ) );

            @while $i <= $len_colorstops and $okay {
                $current_colorstop: nth( $colorstops, $i );
                $okay: is_compatible_colorstop( $current_colorstop );
                $webkit_colorstops: append( $webkit_colorstops, convert_colorstop($current_colorstop, $format: 'webkit' ), comma );
                $i: $i + 1;
            }

            @if $okay {
                background-image: -webkit-gradient(linear, $webkit_direction, $webkit_colorstops);
            }
            @else {
                @warn 'invalid argument: #{nth( $colorstops, $i )}. declaration dropped.';
            }
        }

        // prefixed linear-gradient()s
        @each $prefix in $prefixes {
            background-image: #{$prefix}linear-gradient( ( $legacy_direction, $colorstops ) );
        }
        // standard syntax
        background-image: linear-gradient( ( $direction, $colorstops ) );
    }
}

// check if a <colorstop> can be converted to WebKit or SVG ones
@function is_compatible_colorstop( $colorstop ) {
    @return (length( $colorstop ) == 2 and
             type-of( nth( $colorstop, 1 ) ) == 'color' and
             type-of( nth( $colorstop, 2 ) ) == 'number' and
             unit( nth( $colorstop, 2 ) ) == '%');
}

// convert CSS <colorstop> to WebKit's or SVG's
@function convert_colorstop( $colorstop, $format ) {
    // invalid condition
    @if is_compatible_colorstop( $colorstop ) {
        @if $format == 'webkit' {
            // e.g. color-stop(50%, #fc0)
            @return color-stop(nth( $colorstop, 2 ), nth( $colorstop, 1 ));
        }
        @if $format == 'svg' {
            // e.g. <stop offset="50%" stop-color="rgb(255,204,00)"/>
            @return '%3Cstop%20offset%3D%22' + nth( $colorstop, 2 ) +
                '25%22%20stop-color%3D%22' + convenc_rgba( nth( $colorstop, 1 ) ) +'%22%2F%3E';
        }
    }
    @else {
        @warn 'invalid argument: #{$colorstop}.';
        @return 'error';
    }
}

// converts <color> to rgb() or rgba() then percent-encode
@function convenc_rgba( $color ) {
    $r: red( $color );
    $g: green( $color );
    $b: blue( $color );
    $a: alpha( $color );
    @if $a == 1 { // rgb()
        @return 'rgb%28' + $r + '%2C' + $g + '%2C' + $b + '%29';
    }
    @else { // rgba()
        @return 'rgba%28' + $r + '%2C' + $g + '%2C' + $b + '%2C' + $a + '%29'; 
    }
}

ふう。
Mixin統合だけでは面白くなかったので、もうちょっといろいろやってます。

  • サポートしたいvariants(接頭辞、-webkit-gradient(), SVG)を設定可能に
  • <angle> の場合でもキーワードに変換可能な場合はそうするように
  • -webkit-gradient(), SVG で使えないカラーストップが出た場合はふたつを出力させないように

あとは内部的なところですが、カラーストップを -webkit-gradient()SVG<stop> 要素(パーセントエンコード済)にする関数もなんとなく統合したり、SVG 用に使う色変換の関数を独自の関数にしたりしてます。

誰が嬉しいかわかりませんが、GitHubに置いときました。

バージョンが0.5になってますが、これは書くの5回目だからです……

感想1: でっかいmixinは避けよう

さて、いけてないと思うところ多々ありますが、とりあえず目的達成です。やー、しんどかった……

書いてみて思いましたが、大規模なmixinはデバッグやメンテが大変ですね。書いている途中に意図しない挙動に出くわすこと幾度となくありました。簡単なテストを書くもあまり使えず、開発に慣れてないなあと悲しくなったりもしました。

大きなmixinにしてしまったのは、「ひとつのmixinにまとめるほうがポータブルかな」とか思ったからなのですが、あとあと関数が必要になったりしたので、もう少し前に気づいて細かいものにすべきだったと思ってます。

まあそもそも、Sassだけでどうにかしようというのもアレですね。

そんなわけで、変に限られたコンテキストから頑張って凝ったmixinを定義するより、ちゃっちゃとfunctionとして自由なコンテキストで処理するほうがラクだったりするケースもあるんじゃないでしょーか。

まったくその通りだと思います……

感想2: 型あると便利

ただ、RubyJavaScriptを使えたとしても、グラデーションのmixinを書くのは面倒かなと思います。というのも、linear-gradient() 構文はかなり自由なので、いろんな書き方に対応しようとすると複雑になるのは避けられない気がしています。Compassなどのライブラリでもグラデーションが満足に対応できていないですし、普通に複雑なのでしょう。

SassでここまでやれちゃったのはSassの値が型を持ってたというのが大きいのかなと思いました。あ、Sass固有ってわけではたぶんなくて、Stylusのtypeof()LESSのGuardsを使えばたぶん移植できるかなと。

感想3: いろいろ

あとはメモです。

  • Sassのinteractive shellが便利だった
    • nullやリスト絡みはCSSの出力とシェルの出力が違うのでちょっと戸惑ったりも
  • ''trueに評価されてハマったりした
    • これはRubyの仕様なのですかね。WAT感。
  • リストを操作する関数が欲しい
    • 分割系の関数がないんですよね
  • エラーを投げたい
    • @warnは処理が止まらないので
  • マップ(ディクショナリ)的なものが欲しい

というわけで、これにて自分のCSS Preprocessor Advent Calendar 2012は終了とします。 誰がために書いたのかまったくわからないこのシリーズに「ヤバい」「引いた」「尖ってる」などのコメントをいただけました。ほんとごめんなさい。

あ、Mixinについて何かあったらGitHubの方によろしくお願いいたします。

WebKit for Developers (by Paul Irish) を訳した

PaulがWebKitのportとかについてポストしていた。

ちょっと書きたいことがあってアイデアまとめてるところにタイムリーなエントリーが!
というわけで訳した。

portやビルドフラグ、ランタイムフラグについて説明してるポピュラーなものがなかったので、すばらしい。

最後のQ&Aでinteropが確保できてるよ、というのは、古いWebKitや機種依存のバグ無視できない「現場」を対象としてるものではないなあと。とはいえ、WebKit開発者側にはどうにもできないところでもあるし。どうすればいいのかねこの問題。

あと、断片化についてはテストが多いからだけで解決できることではたぶんない。ビルドフラグがその要因なんだけど、これはまた書こう。

 

Sassでlinear-gradient()のmixinをつくる その4

2013年2月も終わりかけですが、CSS Preprocessor Advent Calendar 2012の記事の続編です。

今回は linear-gradient() から、SVGのグラデーション画像を生成するmixinをつくります。なんでそんなことをするのかというと、IE9linear-gradient() を実装しておらず、またグラデーションを生成できる独自フィルタもIE9標準モードで使えないからです。面倒ですねえ。Windows 7版のIE10の登場とそれへの移行が早く済むとよいのですが。

Part 6 ― SVGはパーセントエンコードされたdata: URLで表現

今回やるのは、異なるCSS構文の変換ではなく、CSSSVGというフォーマットの変換が主です。IE9からサポートされたSVGを利用し、グラデーションを含んだSVGファイルを、background-image で参照させます。

background-image: url(SVGファイルへのパス);

これでSVGファイルを適用できますが、Sassにはファイル生成をする関数なんて用意されてません。なので今回はファイルではなくdata: URLを使い、CSSファイルに直にデータを埋め込みます。

data: URLですが、こんな仕組みになっています。

data:image/png;base64,iVBORw0KGgoAAAANS...

data: スキームに続けてフォーマットのMIME(この場合はPNGのもの), それに続けてエンコードされたデータをつなげると、それがリソース自体のURL表現になります。

SVGMIMEimage/svg+xml です。さて、データですが、先ほどのPNGの場合は base64, という文字が示す通り、データをBase64エンコードし埋め込んでいます。今回のSVGでもそうできればよいのですが、SassにはBase64エンコードするような関数はやはりありません。

そこに登場するのがパーセントエンコードです。URLでコンポーネントに使えない文字列は%xxといった形にエンコードされます。Googleなどで検索した時、q=%E7%9F%A2%E5%80%89%E7%9C%9E%E9%9A%86Mなどという文字列が検索結果のURLにくっついてるかと思いますが、あれです。

要はURLで使える文字列になればOKなので、パーセントエンコーディングでもdata: URLがつくれます。というわけでSVGファイルをパーセントエンコードしdata: URLにしましょう。

ふう。ようやくSVGファイルの構造までたどり着きました。上から下のグラデーションを定義するSVGはこんな感じになります。

<svg xmlns="http://www.w3.org/2000/svg">
    <linearGradient id="g" x2="0%" y2="100%">
        <stop offset="0%" stop-color="#fff"/>
        <stop offset="100%" stop-color="#fc0"/>
    </linearGradient>
    <rect width="100%" height="100%" fill="url(#g)"/>
</svg>

軽く説明すると、こんなことをしています。

  • グラデーション(<linearGradient> 要素)を定義し、それを <rect> 要素に適用(塗りつぶし)する
  • グラデーションの始点と終点は <linearGradient> 要素の属性 (x1, x2, y1, y2) で表す
  • カラーストップは <linearGradient> 要素の子要素に <stop> 要素をストップの数ぶん指定する
  • <stop> 要素には、オフセット (offset) と色 (stop-color) というふたつの属性を指定する

というわけで、Sass側ではlinear-gradient()の引数から次の3つを変換し生成すればよいわけです。

  • グラデーションの方向(<linearGradient>x1, x2, y1, y2 属性の組み)
  • カラーストップの位置(<stop> 要素の offset属性)
  • カラーストップの色(<stop> 要素の stop-color属性)

方向ですが、CSSのキーワードに対応するSVGの方向(属性の組み)を変数にし、それをリストにでもしときます。-webkit-gradient() を生成した時とだいたい同じです。

カラーストップの位置ですが、offset 属性は -webkit-gradient() と同じく、0から1までの数値もしくはパーセンテージしか受け付けません。ですので -webkit-gradient() と同じ制約が必要になってしまいます。今回はパーセンテージのみに絞ります。

カラーストップの色はCSS<color> となっています。SVG 1.1はCSS 2.1を参照していますが、CSS3で定義された rgba() なども利用可能です。これは特に何も変換が必要なさそうです…あっ。

そうでした。生成しないといけないのはパーセントエンコード後の値です。
では、見づらいですが、エンコードされた <linearGradient> 要素(の開始タグ)と <stop> 要素を見てみましょう。

<linearGradient id="g" x2="0%" y2="100%">
%3ClinearGradient%20id%3D%22g%22%20x2%3D%220%25%22%20y2%3D%22100%25%22%3E
<stop offset="100%" stop-color="#fc0"/>
%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%23fc0%22%2F%3E

強調箇所で、パーセントエンコードされた文字は次のとおりです。

半角スペース %20
" %22
# %23
% %25
= %3D

まず、方向はパーセントエンコード済の文字列のリストを持っておけばOKそうです。

ただ、あとの2つが困ったことになりました。0%0%25 に、#fff%23fff にしないといけませんが、Sassには文字列置換をする関数がありません。うーん……

やっぱだめかなーと悩んでいたのですが、ちょっと考えたところ、迂回策が見つかりました。

  • %25 は文字列として付加するようにして、変換に使う変数には % を取り除いた数値を使えばよい
  • stop-color 属性はCSS<color> を取るので、# を使わない(文字列置換を必要としない)別の <color> に変換すればよい

ずるい!けどこれでなんとかなりそうです。

パーセンテージの除去は、1% で割ればよいです。

@function removePercentage($percentage) {
    @return $percentage / 1%;
}

色は、rgb() もしくは rgba() 表記に変換します。Sassには色関連の関数が沢山用意されているので、それを駆使します。

@function convertToRGBa($color) {
    $r: red($color);
    $g: green($color);
    $b: blue($color);
    $a: alpha($color);

    // rgb()/rgba()表記をエンコード
    @return if($a == 1, 'rgb%28'+$r+'%2C'+$g+'%2C'+$b+'%29', 'rgba%28'+$r+'%2C'+$g+'%2C'+$b+'%2C'+$a+'%29');
}

おお、いけそうです。あとはちょっと長いmixinを書くだけです。書くだけです……

@mixin lg-svg($first, $rest...) {

    $direction: false;
    $svg_direction: false;

    $colorstops: false;

    // SVGの位置キーワード(x1, x2, y1, y2属性)
    $x1_0: ''; // '%20x1%3D%220%25%22';   // ' x1="0%"'   // default
    $x1_1: '%20x1%3D%22100%25%22';        // ' x1="100%'
    $x2_0: '%20x2%3D%220%25%22';          // ' x2="0%"'
    $x2_1: ''; // '%20x2%3D%22100%25%22'; // ' x2="100%"' // default
    $y1_0: ''; // '%20y1%3D%220%25%22';   // ' y1="0%"'   // default
    $y1_1: '%20y1%3D%22100%25%22';        // ' y1="100%'
    $y2_0: ''; // '%20y2%3D%220%25%22';   // ' y2="0%"'   // default
    $y2_1: '%20y2%3D%22100%25%22';        // ' y2="100%'

    $svg_down:  $x2_0 + $y2_1; // ' x2="0%" y2="100%"'
    $svg_left:  $x1_1 + $x2_0; // ' x1="100%" x2="0%"'
    $svg_up:    $y1_1 + $x2_0; // ' y1="100%" x2="0%"'
    $svg_right: $x1_0 + $x2_1; // ' x1="0%" x2="100%"'

    $svg_to_TL: $x1_1 + $y1_1 + $x2_0 + $y2_0;
    $svg_to_TR: $x1_0 + $y1_1 + $x2_1 + $y2_0;
    $svg_to_BR: $x1_0 + $y1_0 + $x2_1 + $y2_1;
    $svg_to_BL: $x1_1 + $y1_0 + $x2_0 + $y2_1;

    // 色だった場合
    @if type-of(nth($first, 1)) == 'color' {
        $direction: $first;
        $svg_direction: $svg_down;

        $colorstops: $rest; // ISSUE
    }
    // 'to' からはじまるキーワードの場合
    @else if type-of($first) == 'list' and nth($first, 1) == 'to' {

        $standard_keywords:
            to bottom, to left, to top, to right,
            to top left, to top right, to bottom right, to bottom left,
            to left top, to right top, to right bottom, to left bottom;

        $svg_keywords:
            ($svg_down), ($svg_left), ($svg_up), ($svg_right),
            ($svg_to_TL), ($svg_to_TR), ($svg_to_BR), ($svg_to_BL),
            ($svg_to_TL), ($svg_to_TR), ($svg_to_BR), ($svg_to_BL);

        $idx: index($standard_keywords, $first);

        @if $idx {
            $direction: $first;
            $svg_direction: nth($svg_keywords, $idx);

            $colorstops: $rest;
        }
    }
    // 角度かそれ以外か
    @else {
        $msg: 'SVGに変換できないよ: #{$first}';
        @warn $msg;
        /* #{$msg} */
    }

    // いよいよ出力
    @if ($colorstops) {
        $svg_colorstops: '';

        // いけてないけど……
        @if type-of(nth($first, 1)) == 'color' {
            $svg_colorstops: conv_svg_colorstop($first);
        }

        // カラーストップを変換してはくっつけ、を繰り返す
        @each $cs in $colorstops {
            $svg_colorstops: $svg_colorstops + conv_svg_colorstop($cs);
        }
        // data: URL
        $svg_data: 'data:image/svg+xml,' +
            '%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E' +
                '%3ClinearGradient%20id%3D%22g%22' + $svg_direction + '%3E' +
                    $svg_colorstops +
                '%3C%2FlinearGradient%3E' +
                '%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%23g)%22%2F%3E%3C' +
            '%2Fsvg%3E';
        background-image: url($svg_data);
        background-image: linear-gradient($direction, $colorstops);
    }
}

// color-stop() に変換する関数
@function conv_svg_colorstop($colorstop) {
    // $colorstop は <color> と <percentage> のリスト
    $color: nth($colorstop, 1);
    $stop: nth($colorstop, 2);

    @if unit($stop) != '%' {
        $msg: 'パーセンテージで指定しよう: #{$colorstop}';
        @warn $msg;
    }
    $svg_stop: $stop / 1%;

    $r: red($color);
    $g: green($color);
    $b: blue($color);
    $a: alpha($color);
    $svg_color: if($a == 1, 'rgb%28' + $r + '%2C' + $g + '%2C' + $b + '%29', 'rgba%28' + $r + '%2C' + $g + '%2C' + $b + '%2C' + $a + '%29');

    @return '%3Cstop%20offset%3D%22' + $svg_stop + '%22%20stop-color%3D%22' + $svg_color + '%22%2F%3E'
}

できてしまいました。

パーセントエンコードのため、Base64エンコードなdata: URLよりも長くなってしまうところがあまりよろしくないですね。とはいえ、パーセントエンコードが基本的には文字列置換だったから今回のMixinが実現できたというのもあるので、これはOKにしておきましょう。

悔しいのが、色の出力をrgb(), rgba()形式に固定してしまったところですね。ただ、いま気づいたんですが、10進数を16進数に変換する関数を自分で用意すれば、いったんred(), green(), blue()で10進数の値を取って、そこから16進数表記に戻すこともできますね。

ふう。SVGのへの変換もできました。このままもう終えたいのですが、これまで定義したmixinではlinear-gradient()-webkit-linear-gradient(), linear-gradient()-webkit-gradient(), linear-gradient()SVGと出力を分けてしまっているので、実用性がありません。

というわけで、もうだいぶ書くのしんどいのですが、次回はlinear-gradient()から他のvariantすべてを生成するmixinをつくります。次でたぶん終われる…と思う。

OperaのWebKit移行

各所で既報だろうけれど、OperaWebKitへの移行を発表した。

WebKitベースのモバイルブラウザを発表という噂があって、ああモバイルだけかと思ったら全部だと。驚いた。そりゃ去年夏ごろから人が離れたり、PrestoのWeb標準実装ドキュメントが更新されなかったりするわけだ。ふむ。

WebKit”とあるけれど、Operaソースの発表を読む限り、どうやらChromium portっぽい。Chromiumベースに独自のOpera portを作ったりはないよねえ……

ChromiumベースでどれくらいOperaフレーバーが出るのだろう。拡張もChromeベースとなるとUIもそんなに変えられないだろうし、Chromeに似たものになるのかな。デスクトップでの存在意義が薄くなってしまいそう。

W3Cでの影響

Daniel Glazmanが書いてる通り、標準の実装としてブラウザがひとつ減ってしまうことは大きい。

標準化団体や標準への影響はどうなるか。どうだろう。W3Cで標準化に関わっているOperaの人って、ちゃんと自身の意見を持っているように思うので、使うエンジンが変わることで意見が変わることはなさそう。あったとして、WebKitの内部構造的に実装が難しくなるとか、そういうところなのかな。

WebKitプロジェクト内でどうなるのか

気になるのが、WebKitプロジェクトのプレーヤーになること。

Prestoは他のエンジンと違って提案・実装する機能がユニークだった。Device Adaptationやらobject-fit, tab-sizeとかとか。昔で言うとWeb Forms 2やメディアクエリーとかはだいぶ早くから実装していた。FlexboxもCR版の実装は一番早かった。

今回の移行で、Operaが持っている知見をWebKitに活かせれば的なことが語られている。とりあえずMulticolのパッチを投げるらしい。

Some of our best programmers have been working on the WebKit code for a while, and today we have announced that we will be using the WebKit engine in the future [1]. We will also submit our code; switching from Presto to WebKit frees up resources and allows us to contribute to the WebKit platform.

The first contributions from our side will be in multi-column layout [2]. We have experimented with combining multicol layout with page floats and column spans [3]; in 10 lines of CSS code one can create amazingly beautiful, scaleable and responsive paged presentations [4].

ただ、今後Operaが新しい機能をWebKitに追加しようとして、すんなりできるのか。

webkit-devを読むとなんとなく、何か機能をコアに追加する際はプロジェクト内のラフコンセンサスをとるような流れになっている。なので、FXTFのWeb Animations 1.0Pointer Eventsなどは、追加したい人がいるものの議論になって宙ぶらりになってたりする。

Operaが持ち込みたい機能が、WebKit内で通るのか。WebKit内に通ったとしてiOS portに入るのか。Operaが持ってたユニークネスが活かされるには、WebKit内の政治というかそういうところにかかってる気がする。

 

Sassでlinear-gradient()のmixinをつくる その3

2013年1月も後半ですが、CSS Preprocessor Advent Calendar 2012の記事の続編です。

さて、数日前にWebKitが接頭辞なしのグラデーションをサポートしました。Chrome 26やSafari 7?になれば幸せになりそうです。

でも、GingerbreadのAndroidブラウザーなど、-webkit-gradient() しかサポートしてない環境に対応しないといけないこともあるでしょう。というわけで、今回は linear-gradient() から -webkit-gradient()を生成するmixinを作ります。

Part 5 ― 構文の違いは制約と関数でなんとかする

-webkit-gradient()linear-gradient()と構文が大きく違います。

-webkit-gradient( linear, start, end, colorstops ... )

第一引数っぽいものに linearという、線形グラデーションを示すキーワードを指定します。そのあとに始点と終点、そしてカラーストップを続けます。

始点と終点ですが、定義済みのキーワードもしくはパーセンテージのみが使えます。linear-gradient()のように角度を使うことはできません。

カラーストップですが、色と位置を並べるだけの linear-gradient() と異なり、color-stop()という関数表記が使われます。

color-stop( stop, color )

linear-gradient() と色と位置の書き順が逆になります。stopには0から1までの数値、もしくはパーセンテージが指定できます。省略はできません。

まとめると、linear-gradient() に比べて -webkit-gradient()では次のことができません。

  • 角度が使えない
  • カラーストップの位置には数値もしくはパーセンテージのみ
  • カラーストップの位置が省略できない

というわけで、mixinとしてふたつの構文を満足させるためには、以下の制約が必要です。

  • グラデーションの方向に角度は使わない
  • カラーストップはパーセンテージで指定し、必ず書く

これさえ守ってくれるなら、変換はそんな難しくはありません。面倒ですが。
ということで、こんな感じになりました。

@mixin lg-webkit($first, $rest...) {
    
    $direction: false;
    $wk_direction: false;

    $colorstops: false;

    // 始点/終点キーワードの変換
    $wk_LT: 0 0;
    $wk_LB: 0 100%;
    $wk_RT: 100% 0;
    $wk_RB: 100% 100%;

    $wk_down:  $wk_LT, $wk_LB;
    $wk_left:  $wk_LT, $wk_RT;
    $wk_up:    $wk_LB, $wk_LT;
    $wk_right: $wk_RT, $wk_LT;

    $wk_to_TL: $wk_RB, $wk_LT;
    $wk_to_TR: $wk_LB, $wk_LT;
    $wk_to_BR: $wk_LT, $wk_RB;
    $wk_to_BL: $wk_RT, $wk_LB;

    // 色だった場合
    @if type-of(nth($first, 1)) == 'color' {
        $direction: $first;
        $wk_direction: $wk_down;

        $colorstops: $rest; // ISSUE
    }
    // 'to' からはじまるキーワードの場合
    @else if type-of($first) == 'list' and nth($first, 1) == 'to' {

        $standard_keywords:
            to bottom, to left, to top, to right,
            to top left, to top right, to bottom right, to bottom left,
            to left top, to right top, to right bottom, to left bottom;

        $webkit_keywords:
            ($wk_down), ($wk_left), ($wk_up), ($wk_right),
            ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL),
            ($wk_to_TL), ($wk_to_TR), ($wk_to_BR), ($wk_to_BL);

        $idx: index( $standard_keywords, $first );

        @if $idx {
            $direction: $first;
            $wk_direction: nth( $webkit_keywords, $idx );

            $colorstops: $rest;
        }
    }
    // 角度かそれ以外か
    @else {
        $msg: '-webkit-gradient() に変換できないよ: #{$first}';
        @warn $msg;
        /* #{$msg} */
    }

    // いよいよ出力
    @if ($colorstops) {
        $wk_colorstops: null;

        // いけてないけど……
        @if type-of(nth($first, 1)) == 'color' {
            $wk_colorstops: convert_colorstop($first);
        }

        // カラーストップを変換してはくっつけ、を繰り返す
        @each $cs in $colorstops {
            $wk_colorstops: join($wk_colorstops, convert_colorstop($cs), comma);
        }

        background-image: -webkit-gradient(linear, $wk_direction, $wk_colorstops );
        background-image: linear-gradient($direction, $colorstops);
    }
}

// color-stop() に変換する関数
@function convert_colorstop($colorstop) {
    @if unit(nth($colorstop, 2)) != '%' {
        @warn 'パーセンテージで指定しよう: #{$colorstop}';
    }
    @return color-stop(nth($colorstop, 2), nth($colorstop, 1));
}
 

ふう。

キーワードのあたりが何やら騒がしいですが、マッピングの考えはパート2と同じです。準備が必要なだけです。むつかしくないです。

個々のカラーストップを color-stop() へ変換するために convert_colorstop() という関数を定義しました。@eachで回して都度変換し、出力用の変数にくっつけると。だいぶコスト高そうですが、map() 関数みたいなものがないので……

ちょっといけてないのが、最初の(最初の)引数が color 型だったときの処理でしょうか。$colorstop$first, $rest という感じにできるとよいのですがそうもいきません。可変長引数の型は arglist という、それ自体がひとつのリストになっています。なので、$first, $rest とすると長さが2つのリストになっちゃいます。$rest@eachで回して…というのもできなくはない気がしますが、だるいのでここは個別に処理します。

あと、もしも角度が入ってしまった場合に、こういうことができます。

    // もし角度だった場合
    @else if type_of($first) == 'number' and unit($first) == 'deg' {

        // ちょっとは受け入れて、deg をキーワードに変換
        @if index((-900, -540, -180, 180, 540, 900), $first) {
            $direction: null;
            $wk_direction: $wk_down;
        }
        @else if index((-810, -450, -90, 270, 630, 990), $first) {
            // $direction: to left;
            $wk_direction: $wk_left;
        }
        @else if index((-1080, -720, -360, 0, 360, 720, 1080), $first) {
            // $direction: to top;
            $wk_direction: $wk_upward;
        }
        @else if index((-990, -630, -270, 90, 450, 810), $first) {
            // $direction: to right;
            $wk_direction: $wk_right;
        }
    }

キーワードに変換してあげるやさしさ。リストで指定した範囲より広い角度はダメですが、まあだれが 1080degとか書くんだって話です。

使う側の制約は大きいですが、いちおう -webkit-gradient() にも変換可能だとわかりました。

Sassでlinear-gradient()のmixinをつくる その2

誰がうれしいのかわからない、CSS Preprocessor Advent Calendar 2012の記事の第2弾です。
今回は -webkit-gradient() への変換について書こうと思ったのですが、ひとつ忘れてたことがあったので、それについて書きます。

Part 4 ― type_of() で処理を振り分け

前回は「方向なし」「キーワード」「角度」別に3つのmixinを定義しました。分けるとそれぞれ何をやってるかがわかりやすいのですが、やはり使うことを考えると一つにまとめたいもの。そこで、ひとつのmixin内で処理を振り分けることにします。

やり方ですが、Sassの type_of() 関数を使います。mixinの引数を第一引数と可変長引数のふたつにわけ、第一引数の型を判定します。

@mixin lg-compound($first, $rest...) {
    $prefixes: '-webkit-';

    // 出力用の変数
    $direction: false;
    $legacy_direction: false;
    $colorstops: false;
    
    // $first が color を含む → 方向がない → パート1
    @if type_of(nth($first, 1)) == 'color' {
        // direction って名前よくないね
        $direction: $first;
        $legacy_direction: $first;
        
        $colorstops: $rest;
    }
    // $first がリスト → キーワードかも → パート2
    @else if type_of($first) == 'list' {

        // 標準のキーワード
        $standard_keywords:
            to bottom, to left, to top, to right,
            to top left, to top right,
            to bottom right, to bottom left,
            to left top, to right top,
            to right bottom, to left bottom;
            
        // 接頭辞版のキーワード
        $legacy_keywords:
            top, right, bottom, left,
            bottom right, bottom left, top left, top right,
            right bottom, left bottom, left top, right top;
        
        // キーワードのインデックスを取得
        $idx: index( $standard_keywords, $first );

        @if $idx {
            $direction: $first;
            $legacy_direction: nth($legacy_keywords, $idx);
            $colorstops: $rest;
        }
    }
    // $first が number → 角度かも
    @else if type_of($first) == 'number' {
        // unit() 関数で角度 (deg) かを確認
        // deg の場合 → パート3
        @if unit($first) == 'deg' {
            $direction: $first;
            $legacy_direction: (450 - $first) % 360;
            $colorstops: $rest;
        }
    }
    // それ以外はエラー
    @else { /* エラーですよん */ }
    
    // カラーストップが確認できたら出力
    @if ($colorstops) {
        @each $prefix in $prefixes {
            background-image: #{$prefix}linear-gradient(
                $legacy_direction, $colorstops);
        }
        background-image: linear-gradient($direction, $colorstops);
    }
}

前回はあまり引数のチェックをしてなかったのですが、ちょっと細かくしてみました。

まず color のチェックは、#fff 0% など、カラーストップに位置指定があることを考えて type_of(nth($first, 1)) と、「最初の最初」の型をチェックしています。

キーワードのチェックでは、list であるかをまず確認します。その上でリストの最初に 'to' があるかを確認しています。

角度の場合は、まず number かをチェックし、その上で unit() 関数を使い単位が 'deg' かを確認しています。これは、Sassにおいて数値と単位からなるものは number に属することを利用しています。

まとめ

type_of() を使って引数の型を調べることで、型にあわせて処理を振り分けられるようになりました。また、値のチェックを詳しくしてみました。

というわけで、つぎこそ -webkit-gradient() の出し方について書こうと思います。

Sassでlinear-gradient()のmixinをつくる

このエントリはCSS Preprocessor Advent Calendar 2012、13日目のエントリです。

Sassを本格的に使い始めてもう6ヶ月経つのですが、まだまだだなあと思うこと多々です。
というわけで、勉強のためCSSのグラデーションを出力するmixinを書いてみました。

Part 0 ― linear-gradient() の構成

CSS Image Values仕様linear-gradient() は、グラデーションの「方向」に、色と位置の組からなる「カラーストップ」が続くという構成になってます。

linear-gradient( direction, colorstop1, colorstop2, ... )

さて、今日のImage Values仕様の linear-gradient() と、接頭辞つきの -prefixed-linear-gradient() は、構文が違います。ひと通りブラウザで実装されたあとに、なんと仕様が変わってしまったのです。困ったものです。

そんなわけで、単純に接頭辞だけが違う構文を並べたmixinを作るんでは解決できないのです。

構文がどうが違うかというと「方向の書き方」が違います。ならば、それをなんとかすれば残りは同じ。というわけで、なんとかできないかやってみます。

Part 1 ― 構文が同じなら @each ですっきり

構文が違うと言っておいていきなりですが、同じ場合もあります。方向を省略したときです。
方向が省略されたグラデーションは上から下に向かいます。これならとても簡単です。

@mixin lg-simple($colorstops...) {
    // 出力させたい接頭辞を並べる
    $prefixes: '-webkit-';
    
    @each $prefix in $prefixes {
        background-image: #{$prefix}linear-gradient($colorstops);
    }
    background-image: linear-gradient($colorstops);
}

ただ並べて書いてもいいのですが、出力させたい接頭辞を変数に入れて @each で回すことにしました。これだと重複して書かなくて済みます。

カラーストップの指定には、Sass 3.2から導入された可変長引数を利用しています。何色あっても大丈夫。

Part 2 ― キーワードのマッピングはリスト関数で

上から下ではなく「左から右へ」「右下から左上」といったグラデーションにしたい場合は、方向をキーワードで指定します。

linear-gradient( to top left, #fc0, #fff )

to top left” と「向かう方向」を書くことになります。

さて、ここから接頭辞つきのものと構文が違ってきます。-prefixed-linear-gradient() では、「〜〜から」と「始点」を書くようになってます。

-webkit-linear-gradient( bottom right, #fc0, #fff )

うーん、面倒ですね。ただ、新旧のキーワードをマップできれば解決できます。
ここではリストとリスト関数を使ってなんとかしてみましょう。

@mixin lg-keyword($keyword, $colorstops...) {
    $prefixes: '-webkit-';
    
    // 標準のキーワード
    $standard_keywords:
        to bottom, to left, to top, to right,
        to top left, to top right, to bottom right, to bottom left,
        to left top, to right top, to right bottom, to left bottom;
        
    // 接頭辞版のキーワード
    $legacy_keywords:
        top, right, bottom, left,
        bottom right, bottom left, top left, top right,
        right bottom, left bottom, left top, right top;
    
    // キーワードのインデックスを取得
    $idx: index( $standard_keywords, $keyword );
    
    // キーワードが不正な場合は何も出さない
    @if $idx {
        // マッチする接頭辞版のキーワードを取得
        $legacy_keyword: nth( $legacy_keywords, $idx );
        
        @each $prefix in $prefixes {
            background-image: #{$prefix}linear-gradient(
                $legacy_keyword, $colorstops);
        }
        background-image: linear-gradient($keyword, $colorstops);
    }
}

おえってなりますね。

まず、標準のキーワードと、それに対応する接頭辞版のキーワードのリストを定義します。Sassではカンマもしくは空白で区切られた値がリストとして扱われます。そして、Sassにはこういうリストを扱うリスト関数が用意されています。

というわけで次に、index() 関数でインデックスを取得します。index() はリスト内に値がある場合はそのインデックスを、ない場合は false を返します。
あ、Sassではリストのインデックスが0ではなく、1から始まります。ちょっと注意ですね。

次に、取得したインデックスを使って、該当する接頭辞版のキーワードを取得します。リスト中のあるインデックスに置かれた値をとるには nth() 関数を使えばOKです。

これでキーワードのマッピングができ、mixinが完成です。わー

しかしリストのインデックスを使うというのは泥臭いですね。マップ/ハッシュみたいなものがSassにあれば少しは洗練された感が出るのですが……
と、マップ的なものを追加するという決定はされているようですので、楽しみですね。

Part 3 ― 角度は単純に計算

キーワードで十分だろうとおもいきや、linear-gradient() にはもうひとつ、角度で方向を指定できる機能があります。

linear-gradient( 90deg, #fc0, #fff )

さて、-prefixed-linear-gradient() の構文はどうかというと、なんと、同じです。

-webkit-linear-gradient( 90deg, #fc0, #fff )

しかし、同じでも違います。なんと角度の「解釈」が違うのです。

linear-gradient() では、0deg は上を指し、値が増えると時計回りに回転します。時計と同じ動きです。
いっぽう、-prefixed-linear-gradient() では、0deg は右を指し、値が増えると反時計回りに回転します。IllustratorPhotoshopのグラデーションツールと同じですね。

と、構文は同じでも解釈が違うというひどさです。なんとかしなくてはいけません。

これは簡単で、ただ計算してやればいいだけです。

@mixin lg-angle($angle, $colorstops...) {
    $prefixes: '-webkit-';
    
    // 接頭辞版の角度の解釈にあわせた値に変換
    $legacy_angle: (450 - $angle) % 360;
    
    @each $prefix in $prefixes {
        background-image: #{$prefix}linear-gradient(
            $legacy_angle, $colorstops);
    }
    background-image: linear-gradient($angle, $colorstops);
}

引き算と剰余演算です。単純に 90 - $angle でもいいですが、なんとなく、なるべく負の値を出したくなかったので剰余演算を使ってみました。無駄ですね。

まとめ……?

というわけで、Sassの持ってるディレクティブや関数を使って、linear-gradient()-prefixed-linear-gradient() をmixinにまとめられることが分かりました。

でも、これじゃ足りないですよね。なぜなら世の中には -prefixed-linear-gradient() に対応してないブラウザ(のバージョン)があるからです。具体的に言うとAndroid 2系とIE9です。

はあ、実用的じゃないですよね……

しかーし、実はいろいろやったら、いちおう対応できることがわかりました。
ただもうえらく長くなってしまったので、別途エントリを書こうと思います。では。

HTMLに提案中のmain要素

12月になった。HTML5Canvas APIのCRが出る。

MicrodataはRDFaコミュニティのキャンペーンにより遅れそう。わからんけど。altのやつとかもそうかな。やれやれだ。

さて、一緒に出るかはわからないけれど、<main>要素の提案がextension specとして公開されることは決まった。

で、ここ数日でいろいろ動いてるので、ちょっと書いとこうかなと。

<main>ARIAとともに

<main>の仕様は、Steve Faulknerがいろいろ頑張って書いているもの。

アクセシビリティ畑(?)の方なので、role="main"にマップされると定義されている。というかsyntactic sugarに近い。

利点などはSteveの書いたユースケース中にある。

role="main"にマップされると、ARIAを知らないひとも間接的にARIAを使うことになるので、Landmark Rolesが普及するといったところがある。role="main"があると、ARIAをサポートしている環境に対しては、スキップリンクなんかよりもずっとスマートな解決策になる。

コンベンションとしての<main>

アクセシビリティ的な利点だけではない、とされている。要素の名前からも分かる通り、id="main"class="main"などもほぼ自動的に<mainで置き換えられるであろうという意図がある。

また、もともとmaincontentという名前だった経緯もあり、id="content"とかも<main>になるのかなと推測できる。<header>とかに近いかな。<main>となって広まれば、Readability系のアプリも楽になるのかもしれない。

さまざまな反応

というわけで、前者よりも後者のところが受けて、デベロッパーからは支持を受けてる印象だけど、HTML WGやWHATWG, ベンダーの中の人の意見はさまざま。

たとえばHixieは、今回の<main>に限らず「主要な部分をマークアップする」要素が必要ないというスタンスをとっている。理由は、サイトの<header><footer>, サイドバーとして使われている<aside>をページから除けば、残るのは必然的に主要な部分だろうと考えているから。(このやり方は“Scooby Doo”アルゴリズムと呼ばれてたりする。ユーザースクリプトとかもある。)

すでに解決法があるのに、新たな方法を仕様に入れて冗長になるのは避けたいという思いが、仕様を書いている側としてあるのだろう。

ただ、明示的にマークアップする方法があってもそこまで問題ないと考えている人ももちろんいる。Maciejは、利が大きいとは思っていないけど利はある、実装コストは低いといった理由から、賛同ちっくな意見を寄せている。James Grahamのコメントには、すでにcowpathとなっているのではという指摘もある。

実装コストは低いけど…

Steveは仕様を書くだけではなく、WebKitにもパッチを投げている。ごりごり押している。

ただ、他のベンダーはどうなのかとか、WebKitプロジェクト内でゆるいコンセンサスがとれるのかといった議論がはじまっているので、するっと入るわけではない。というかwebkit-devですんなよ感……

今のところ、メリットがとても大きいわけではないけれど全くないわけじゃないし、導入しても害もそんなにない、実装コストも低いというのが絶妙なふんわり感を生み出し、ほわほわと<main>を浮かせている感じ。

どうなんだろう

現段階で自分の意見を言うなら、あってもなくてもいい。導入されたら<header>, <footer>と同じくらいの頻度で多分使うけれど、なくてもこまんない。

懸念があるとすれば、role="main"の仕様から、文書内で一度しか登場していけないという変な制約が入ってること。そんな知られてる気がしないけど、role="main", role="banner", role="contentinfo"は文書中にひとつ以上マークすべきでないとされているので、そこがちょいと変かなと。“main”なものが複数登場する可能性は、低いとは思うけど。

あとは、コンベンションといっても、やっぱり“main”や“content”の対象はひとによってばらつきが出るんじゃないかと。特に、サイドバーが入る入らないとかでぶれるんじゃないかなあと。もちろんページ内の情報とHTMLの書き方にもよるんだけど、サイトワイドなサイドバーとしての<aside>なのに、なぜか<main>に包含されてるHTMLが出てきそうだなと。

注文というか、要望をあげるとすれば、文書内の主要なコンテンツを囲ってるんだから、<main>がページ内にある場合はそれを主のsectioning rootにするとか、セクションとしての性質が与えられててもいいんじゃないかと思う。

:-moz-placeholderから::-moz-placeholderに

コロンに注目ね。Mozillaはこれまでplaceholder属性にスタイルをつけるために、:-moz-placeholderという擬似クラスを使っていたんだけど、Firefox 19からは::-moz-placeholderという擬似要素になる。

変更した理由については、プレースホルダまわりのバグを修正するにあたってのことらしい。

直接関係ないけど、フォーカス時にもプレースホルダが出るのを止めるためのprefもついたdom.placeholder.show_on_focusから変更可能。

さて、独自拡張だからなのか、これまでの:-moz-placeholderを残すわけでもなく::-moz-placeholderに置き換えられるので、プレースホルダのスタイルをいじってるひとは注意かな。コロンいっこ足すくらいでいいけど、プレースホルダが半透明になってるので、不透明にしたい場合はopacityを書き足さないといけない。

さて、Mozillaは擬似クラスになった。他はどうか。WebKitはもともと::-webkit-input-placeholderで、MicrosoftはIE10で:-ms-input-placeholderという擬似クラスを導入している。ややこしいね。www-styleだと擬似クラスの方に傾いていたような記憶があるけれど、ちゃんと標準化されるんだろうか。

さらばmozとoのdevice-pixel-ratio

そういえば、Firefox 16とOpera 12.10からメディアクエリーのresolutiondppxが使えるのを思い出した。
これらを使うと、Retina displayはじめ高密度なディスプレイに対応するコードがけっこう短くなる。

これまではこんな感じ。

@media (-webkit-min-device-pixel-ratio: 2),
    (min--moz-device-pixel-ratio: 2),
    (-o-min-device-pixel-ratio: 2/1) {
        .foo {
            background-image: url(image-2x.png);
        }
    }
}

device-pixel-ratioはもともとWebKitの拡張だったもの。それをMozillaOperaも取り入れたのだけど、ベンダー接頭辞の面倒さに加えてmin-/max-のつき方、値の書き方がばらばらというカオスになっていた。どちらも各々のパーサに都合のよい、もしくは「適切」だと考えた構文を実装した結果なのだろうけど、センスよい感じではない。

そんな時代も終わり。resolutiondppxでこんなにすっきり。

@media (-webkit-min-device-pixel-ratio: 2),
    (min-resolution: 2dppx) {
        ...
    }
}

-webkit-device-pixel-ratio: 2resolution: 2dppxだけ。すっきり。

というわけでみんな書きかえとこう。Firefoxはもう17が出るし、Opera Mobileも12.1が出てからひと月以上経つ。古いバージョンを気にする必要はそんなないでしょうう。device-pixel-ratioを使うコードなんてほとんどスマートフォンが対象だろうし、そこでのFirefoxOperaのシェアとかコンテンツ側の対応状況を想像するに…うん、いいでしょうよ。

device-pixel-ratioを標準化すればよかったんじゃ」っていう素朴な疑問については、CSS WG Blogのポストを。回答になってるかは怪しいけれど。

resolution10月にWebKitでも実装されたdppxは6月に実装)のだけれど、Mac portでもChromium portでも有効にはなってなさげ。

他にもいろいろ思い出してきたけど面倒なので書くのはやめておく。

HTML WG Status Report (TPAC 2012)

TPACへ行けず意気消沈している。
それはそれとして、3人のco-chairの連名でAdvisory Committee向けに出したスライドが公開されていた。

Status Reportでそんな新しい話はなかった。HTML 5.0 CRとHTML 5.1 FPWDを4Q中に出すというプランは変わらず。ただ、5.0 CRに至ってはもうCRドラフトが作られ始めている。

あ、CRのドラフトが11/8付になってるけど、そんな早くはでないはず。

At risk for 5.0

CRになると実装を考えないといけない。HTML5の場合、実装が進んでいるものもあればそうでないものもあって、そうでないものはat risk扱いにされる。CR exit criteriaを満たす実装が現れない場合、現バージョンからは削除されることになる。

HTML 5.0で、現状at riskとされているのは次の通り。

おおよそ納得できる。hgroupについてはISSUEがあること、アウトラインアルゴリズムもhgroupが関わることや、実装のかたちが見えてこないところなんかがat riskにされてるんだろう。inputcolorや日付関連はChrome, Operaが頑張ってはいるけど、5.0にはちょっと、というところなんだろうか。

.next?

今月半ばには新しいcharterのドラフトをACのレビューにかける予定らしいけれど、そのdraft charterには「.next」的な仕様への言及はなく、5.1やモジュール化による構成が提案されている。

なのでdelta spec的なアプローチになってしまうのかねえ。なにが正か分からないオレオレ拡張仕様の乱立と、仕様の分割による依存関係の解決に泣かされることにならなければいいんだけれど。