Chromeでボタンをクリックしたときのフォーカスリング

geckotangが「Chromeでボタンをマウスでクリックしたときにフォーカスリングが出たり出なかったりするのなんで」と言っていて、なんかお前調べろよ的な感じだったので調べることにした。

使うのはcs.chromium.org<button>なので"HTMLButtonElement"で検索して、それっぽいファイルを探す。あった。

"focus"で検索するとSupportsAutoFocus()というメソッドが見つかる……がこれはautofocus属性の対象になるかとかそういう感じがするのでこれではない。というかそっか、autofocus指定できるんだ……<input>にしか使ったことなかったからなんか使えないって思ってしまってた。

HTMLButtonElementにはなさそうなので、参照しているHTMLFormControlElementを見る。

"focus"で検索……ShouldShowFocusRingOnMouseFocus()とかShouldHaveFocusAppearance()とか、それっぽいのがある!

メソッドを選択して、Call Hierarchyを見てみる。layout_theme.ccShouldDrawDefaultFocusRing()というメソッドが見える。これかーーーー!?

フォーカスリングが出る条件

ShouldDrawDefaultFocusRing()はこうなっていた。

bool LayoutTheme::ShouldDrawDefaultFocusRing(const Node* node,
                                             const ComputedStyle& style) const {
  if (ThemeDrawsFocusRing(style))
    return false;
  if (!node)
    return true;
  if (!style.HasAppearance() && !node->IsLink())
    return true;
  // We can't use LayoutTheme::isFocused because outline:auto might be
  // specified to non-:focus rulesets.
  if (node->IsFocused() && !node->ShouldHaveFocusAppearance())
    return false;
  return true;
}

if文を読み解く。

  1. テーマがフォーカスリングを出す → false
  2. ノードじゃない → true
  3. アピアランスががない、かつリンクでない → true
  4. フォーカスされている、かつフォーカスを表示すべきじゃないと判断してる → false

ふんふん(わかってない)

テーマのフォーカスリング

テーマがフォーカスリングを出すのかは、ThemeDrawsFocusRing()を見ればいいらしい。メソッドをクリックするとlayout_theme.hに飛んだ。

  virtual bool ThemeDrawsFocusRing(const ComputedStyle&) const = 0;

さらにクリック。layout_theme_default.ccなるものに飛ぶ。

bool LayoutThemeDefault::ThemeDrawsFocusRing(const ComputedStyle& style) const {
  if (UseMockTheme()) {
    // Don't use focus rings for buttons when mocking controls.
    return style.Appearance() == kButtonPart ||
           style.Appearance() == kPushButtonPart ||
           style.Appearance() == kSquareButtonPart;
  }

  // This causes Blink to draw the focus rings for us.
  return false;
}

基本的にはfalseらしい。trueになりそうなのもテストで使う系なのかな。

プラットフォームによって上書きされてるかもしれないなーと思ったけど、layout_theme.ccの上の方にThe methods in this file are shared by all themes on every platform.なんてコメントがあった。しかしこのメソッド、テスト以外になにか使い所あるのかなあ。

アピアランスがあるかないか

アピアランスがない、というのは-webkit-appearanceプロパティの話らしい。computed_style.hHasAppearance()が定義されていた。

  // -webkit-appearance utility functions.
  bool HasAppearance() const { return Appearance() != kNoControlPart; }

kNoControlPartとは、theme_types.hのControlPartというenumに定義されていた-webkit-appearance: noneに相当する定数っぽい。

CSSProperties.json5(CSSプロパティの定義を生成するもとのファイル)を見ると、-webkit-appearanceの初期値がkNoControlPartと定義されてたので、認識はあってそう。

フォーカスを見せるのか

フォーカスすべきでないという判断は、ShouldHaveFocusAppearance()を見ればいい。クリックしたらhtml_form_control_element.ccに定義されていた。

bool HTMLFormControlElement::ShouldHaveFocusAppearance() const {
  return !was_focused_by_mouse_ || ShouldShowFocusRingOnMouseFocus();
}

ShouldShowFocusRingOnMouseFocus()はすぐ上にあった

bool HTMLFormControlElement::ShouldShowFocusRingOnMouseFocus() const {
  return false;
}

マウスでフォーカスされていない場合にのみ、フォーカスすべきという話らしい。


以上をふまえると、基本的にbutton要素は以下の場合にフォーカスリングを出すらしい。

  • -webkit-appearanceがない場合
  • マウス以外でフォーカスした場合

なので、通常のボタンをマウスでクリックした場合はフォーカスリング出ない。

appearanceプロパティのcomputed styleが変わるとき

geckotangによるとborderやらbackground-colorなどを指定すると、マウスクリック時にもフォーカスリングが出るとのこと。

先程のShouldDrawDefaultFocusRing()trueになる条件をふまえると、-webkit-appearanceがない場合にフォーカスリングが出るということなので、-webkit-appearanceが変わる条件を探せばよい。

さてどうしたもんかなーと思っていたのだけど、layout_theme.ccの上の方にこんなコードがあった。

void LayoutTheme::AdjustStyle(ComputedStyle& style, Element* e) {
  DCHECK(style.HasAppearance());

  ControlPart part = style.Appearance();

  // ...

  if (IsControlStyled(style)) {
    if (part == kMenulistPart) {
      style.SetAppearance(kMenulistButtonPart);
      part = kMenulistButtonPart;
    } else {
      style.SetAppearance(kNoControlPart);
      return;
    }
  }

  // ...

}

アピアランスを見て、特定のアピアランスでない場合はアピアランスを変更するというコード。

これどこで使われるのかなーと思ったのだけど、layout_theme.hに関数の説明が書いてあった

  // This method is called whenever style has been computed for an element and
  // the appearance property has been set to a value other than "none".
  // The theme should map in all of the appropriate metrics and defaults given
  // the contents of the style. This includes sophisticated operations like
  // selection of control size based off the font, the disabling of appearance
  // when certain other properties like "border" are set, or if the appearance
  // is not supported by the theme.
  void AdjustStyle(ComputedStyle&, Element*);

スタイルが計算された、もしくはappearancenone以外になったときにコールされると。おっけ。これだ。

どんなときにアピアランスが変わるのか

もっかいさっきのコードを。

  if (IsControlStyled(style)) {
    if (part == kMenulistPart) {
      style.SetAppearance(kMenulistButtonPart);
      part = kMenulistButtonPart;
    } else {
      style.SetAppearance(kNoControlPart);
      return;
    }
  }

IsControlStyledという状態の場合でかつ、アピアランスがメニューリストでないものはkNoControlPartに切り替えるらしい。

IsControlStyled()の定義を見てみる。

bool LayoutTheme::IsControlStyled(const ComputedStyle& style) const {
  switch (style.Appearance()) {
    case kPushButtonPart:
    case kSquareButtonPart:
    case kButtonPart:
    case kProgressBarPart:
      return style.HasAuthorBackground() || style.HasAuthorBorder();

    case kMenulistPart:
    case kSearchFieldPart:
    case kTextAreaPart:
    case kTextFieldPart:
      return style.HasAuthorBackground() || style.HasAuthorBorder() ||
             style.BoxShadow();

    default:
      return false;
  }
}

<button>アピアランスkButtonPartなので、style.HasAuthorBackground() || style.HasAuthorBorder()を満たす場合、そのコントロールにはスタイルがついていると認識される。

HasAuthorBackground()の定義を見てみる。

bool StyleResolver::HasAuthorBackground(const StyleResolverState& state) {
  const CachedUAStyle* cached_ua_style = state.GetCachedUAStyle();
  if (!cached_ua_style)
    return false;

  FillLayer old_fill = cached_ua_style->background_layers;
  FillLayer new_fill = state.Style()->BackgroundLayers();
  // Exclude background-repeat from comparison by resetting it.
  old_fill.SetRepeatX(EFillRepeat::kNoRepeatFill);
  old_fill.SetRepeatY(EFillRepeat::kNoRepeatFill);
  new_fill.SetRepeatX(EFillRepeat::kNoRepeatFill);
  new_fill.SetRepeatY(EFillRepeat::kNoRepeatFill);

  return (old_fill != new_fill || cached_ua_style->background_color !=
                                      state.Style()->BackgroundColor());
}

だるくなってきたので深く追ってないけど、background-repeat以外のbackground関連プロパティが変わってたら指定されてるという判断なのかな。

続いてHasAuthorBorder()

bool StyleResolver::HasAuthorBorder(const StyleResolverState& state) {
  const CachedUAStyle* cached_ua_style = state.GetCachedUAStyle();
  return cached_ua_style &&
         (cached_ua_style->border_image != state.Style()->BorderImage() ||
          !cached_ua_style->BorderColorEquals(*state.Style()) ||
          !cached_ua_style->BorderWidthEquals(*state.Style()) ||
          !cached_ua_style->BorderRadiiEquals(*state.Style()) ||
          !cached_ua_style->BorderStyleEquals(*state.Style()));
}

border関連が変わってたらだめと。


なので、背景とborder関連プロパティ(border-image, border-radius含む)が変更されたら、そのコントロールにスタイルがあたってると認識され、マウスクリック時でもフォーカスリングが当たるようになる。

フォーカスリングを出したくない?

出したくない場合、borderとbackground関連のプロパティは使えない……ちょっと現実的でない。とはいえ:focus { outline: 0 }はダメゼッタイ。ジレンマ。

というわけで:focus-visibleなんて擬似クラスができた。Chromeで実装中なのでもうちょいでよさげな感じになる。

そういえば、Chrome以外でマウスクリック時にフォーカスリングを出すブラウザってあったっけ…?macOSだとFirefoxSafariは出さないみたいだけど、WindowsLinuxとかはどうだっただろうか。

Autoprefixerのブラウザ指定をpackage.jsonの`browserlist`からする

さっきAutoprefixerの設定でいろいろ阿呆なことをして、まあそれは解決して、ついでにリリースノートを見ていたら6.6.0でこんな機能が入っていたのを知る。

With Autoprefixer 6.6 you can specify browsers in package.json to reduce config files:

{
  "private": true,
  "devDependencies": {
    "autoprefixer": "^6.6.0",
    "postcss": "^5.2.6"
  },
  "browserslist": [
    "last 2 versions",
    "ie 9"
  ]
}

Note that we highly recommend specifying browsers in browserslist config or in package.json instead of Autoprefixer option.

これまでAutoprefixerのコードに書いてた対応させたいブラウザの範囲をpackage.jsonbrowserslistに書けるようになったとのこと。Browserslistcaniuseのデータをダンプしたやつを文字列でクエリできるやつで、いつからかAutoprefixerが使っていたのは知っていたけど、いろいろ使いやすくなってたんだね。不勉強であった。うあー

6.6.0時点でAutoprefixer以外にも、Stylelintやbabel-env-presetでも使われているとのこと。Autoprefixerは設定はコードじゃなくてpackage.jsonとかのがおすすめと書いてるので、今後はこう書いたほうがいいのかな。

書いてみる

手元のpackage.jsonとgulpfileをいじってみた。

  "private": true,
  ...
  "browserslist": [
    "last 1 version"
  ],
  ...

こんな感じで書いて、gulpfileでAutoprefixerをかましてるコード

  .pipe(postcss([
    autoprefixer({ browsers: ['last 1 version'], cascade: false }),
  ...

ここからbrowsers: ['last 1 version'],を削るだけ。
さて実行…うごいた…多分。エラーが出てもしかしてデフォルトにフォールバックされてるかもしれないけど、たぶん動いてる。わーい。

もうちょい読むと、package.jsonだけじゃなくて、$BROWSERSLISTっていう環境変数でもいいし、ルートに設定を書いたbrowserslistファイルでもいいらしい。READMEのQueriesなるセクションに優先順位とかが書いてあった。

Google Analyticsのデータを使う

これはBrowserlistというかcaniuseの機能なんだけど、caniuse.comのSettingsからGoogle Analyticsのアカウントを連携させてやると、グローバルなブラウザのシェアでなく、管理下のサイトのそれに連動した情報を出してくれる。

そのデータをなかなかなワンライナーでファイルとして出力してやると、そいつをもとにしてくれるとのこと。ファイル出力はもうすこしスマートにできるといいな。

するするさせたい:サイボウズ採用情報のアニメーション(その2)

サイボウズの採用情報ページにあるアニメーションがするするしていない。ので前回は何がおこってるのか見てみた。

今回はするするさせられないか、がんばってみる。

  • その1 ― 何が起こっているのか調べる
  • その2 ― 調べながら直しながらするするさせる
  • その3 ― ちょっとしたことや他のブラウザでもするするさせる

アニメーションを止める

まず、いまのjQueryベースなアニメーションを止める。
コンソールに以下をぶっこめばアニメーションが止まって、背景も初期位置に戻る。

jQuery('.icon').stop().css('backgroundPosition', '')

チェーンもできるしjQueryべんりだね。

CSSアニメーションにしてみる

jQueryのアニメーションを別の方法に書き換えるわけだけど、今は2010年代も後半だ。使うならCSSアニメーションしかない。とくに右から左へ一方向っていう単純な動きならなおさらだ。

さっきのjQueryのコードは、こう変換できるかな。

@keyframes loop-icon {
  from { background-position: 0 }
  to { background-position: -1680px }
}
.icon {
  animation-name: loop-icon;
  animation-duration: 60480ms; /* 1680×36 */
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  /* animation: loop-icon 60480ms linear infinite; でもOK */
}

で、これを document.head.innerHTML += <style>...</style> とかでくるんでコンソールにぶっこむとアイコンが動き出す。おー、なんとなくするする動いてる感じ。

描画の詳細を見てみる

なんとなくじゃだめなので、またタイムラインを録ってみる。

f:id:myakura:20160513131612p:plain

およ…?

CPUの使用量が下がった感じはするけど、引き続きジャンクな感じ。
拡大すると、たしかに下がってはいる。

f:id:myakura:20160513131746p:plain

あと、Summaryから“Scripting”の項目が消えている。これはjQueryのアニメーションが setInterval() ベースだったんだけど、それがなくなったからだ。

さて、多少の成果が出たけれど、これではするするとは言えない。もっと調べよう。
拡大して、太い緑色のところをクリックすると、その描画についての詳細を見られる。

f:id:myakura:20160513132737p:plain

描画範囲(Dimensions)が「1362 × 6935」となっている。なんと、アニメーションはページの一部しかないのに、どうやらページ全体を描画しているらしい。だから負荷が高いまま。

will-change で別レイヤーに移す

ページの一部(高さ120px)を動かすためだけにページ全体(6935px)が毎回描画されるのはだいぶつらい。再描画の範囲を最小にしたい。そこで注目するのが、さっきのSummaryにあった「Layer Root」という項目だ。

ブラウザの描画領域は一枚なときもあるけど、合成レイヤー(compositing layer)という複数のレイヤーからなっていることもある。今回のように一部だけ動くとか、そういう際にブラウザが自動的にレイヤーを切って管理してくれる。OHPシート重ねて動かすみたいなやつだ。

というわけで、ブラウザに「動くのここだけだからレイヤー切ってよー」というのを示して全体を描画させないようにする。これには will-change なるプロパティを使う。値にプロパティ名を指定すると、ブラウザはその要素上のそのプロパティが変化するのに適した処理をしてくれるようになる。

レイヤーを新たに作りたい場合、値に transform を指定するのがおまじないだ。

.icon {
  animation-name: loop-icon;
  animation-duration: 60480ms;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}

古の -webkit-transform: translate3d(0, 0, 0) と変わらないじゃんと思うかもしれない。ハック感は否めないけど、でもいちおうオプトインだから。名前も「will-change」であくまで意図だしブラウザが今後もこの挙動とは限らないから(もごもご)……

レイヤーを確認する

レイヤーを切ったのはいいけれど、どう確認すればいいか。もいちどDevToolsのRenderingメニューを開く。そこにある「Layer Borders」をチェックすれば、レイヤーの境界にボーダーが引かれるので、どこにレイヤーがあるかがわかる。

以下はもともとのページのレイヤー。レイヤーの境界は薄いオレンジで、ページの端についている。ページ全体が一枚のレイヤーみたいだ。

f:id:myakura:20160513140733p:plain

水色の水平線が見える。これはタイルというレイヤーのいち領域の区切りを示している。
右と下に緑色の矩形が見えるけど、どうやらスクロールバーの領域もレイヤーの一種みたいだ。

以下は .iconwill-change: transform を与えてレイヤー化したもの。アイコン周りに新しいオレンジの枠ができたのがわかる。

f:id:myakura:20160513140745p:plain

レイヤー分けた効果を確認

ではタイムラインを録ってみよう。

f:id:myakura:20160513142111p:plain

ふむ。ジャンクはまだあるけど、描画の負荷がだいぶ減っている。1フレームとばしで60FPSも達成している。またちょっと進んだね。

f:id:myakura:20160513142126p:plain

あんなに太かったPaintもほっそくなって、描画領域も「1362 × 120」に、レイヤールートも狙い通り #document から div.icon になった。

しかしまだジャンクがあるのが気になる。確かに見るとまだガタガタしている。
あと、Paintingが減ってはじめて気になったのが紫色のRendering。毎回なにをレンダリングしているのか?そう、背景画像だ。

transform でするする

実は、CSSアニメーションにしたから負荷が減るというのは限定的な話で、プロパティによってはレイアウト、スタイルの計算、描画がはしってしまう。レイアウトはスタイル計算を、スタイル計算は描画を引き起こすので、できるかぎり前の段階からの最小化が望ましい。

プロパティごとに引き起こされるレイアウト・スタイル計算・描画は、Paul Lewisらがまとめている。

最近Blink以外のエンジンも頑張っている。
さて、background-position の項をみると、it will cause painting to occur とある。なので描画は結局おこってしまう。あとスタイルの計算もはしってしまう。なのでjQueryまわりのスクリプト・レイアウトの負荷がなくなるだけで、コストの掛かる描画は残ったままになる。

どうすればいいか。アニメーションによるのでどうにもできないケースもあるだろうけど、今回のアニメーションであれば transformtranslateX() に置き換えるといい。transform は描画も起こさないので、この負荷も下げられる。というわけでこちらにしよう。

背景画像の繰り返しを利用してずっと流れているように見せているので、書き換えはちょっと工夫が必要。今回は .icon の幅を広げて、それを translateX() で動かすようにした。

@keyframes loop-icon-transform {
  from { transform: translateX(0px) }
  to { transform: translateX(-1680px) }
}
.kintone {
  overflow: hidden;
}
.icon {
  width: 3360px !important;
  animation: loop-icon-transform 60480ms linear infinite;
}

ウインドウの幅が大きいと端が見えちゃうだろうけれど、だいたいカバーできるだろう。
もうちょっといいつくりがあるのかもしれない。translateX() で動かせて、切れ端が見えないのならなんでもいい。

なお、今回は will-change を指定していない。というのも、指定しなくてもレイヤーを分けてくれたから。will-change もこのアニメーションの自動レイヤーわけもブラウザ依存なので、対応環境によってはつけないといけないかもしれない。

ではタイムラインを録ってみよう。

f:id:myakura:20160513143936p:plain

ててーん。ジャンクなし。だいたい60FPS。RenderingとPaintingも消えた。
見た目もするするしている。するするさせられた!


というわけで、するするさせたいという目標はいちおう達成できたかな。どうぞご利用ください。

ただちょっとだけ気になることとか、あと少し書いときたいこともある。そいつはまた次回。

(追記):書いたよー

おまけ:試してみよう

どんなコードをコンソールにぶっこんだかちょっと書いとこう。おためしあれ。

background-position 版のCSSアニメーションに置き換え。

jQuery('.icon').stop().css('backgroundPosition', '')
document.head.innerHTML += `<style>@keyframes loop-icon { from { background-position: 0 } to { background-position: -1680px } } .icon { animation-name: loop-icon; animation-duration: 60480ms; animation-timing-function: linear; animation-iteration-count: infinite; }</style>`

will-change: transform はStyleパネルから追加している。クリックでオンオフさせやすいので。

以下は translateX 版のCSSアニメーション(最終版)に置き換えるもの。

jQuery('.icon').stop().css('backgroundPosition', '')
document.head.innerHTML += `<style>@keyframes loop-icon-transform { from { transform: translateX(0px) } to { transform: translateX(-1680px) } } .kintone { overflow: hidden; } .icon { width: 3360px !important; animation: loop-icon-transform 60480ms linear infinite; }</style>`

いま考えたらTemplate Strings使ってるんだからべつに改行取っ払うこともなかったな…

FirefoxのCSS Unprefixing Service

追記(2016-09-23)-webkit-接頭辞のサポートを一部のサイトのみ対象とする方針はその後方針転換し、すべてのサイトを対象とすべく必要な機能やエイリアスの実装が行われ、Firefox 49でリリースされた。

標準での対応については、もととなる機能を標準化したうえでCompatibility Standardエイリアスを定義している。ただwebkitMatchesSelector()CSS Animationsのイベントなどは、DOM仕様に組み込まれていたりも。

というわけで、ここで取り上げたlayout.css.unprefixing-service.enabledは意味をなさなくなる。接頭辞のエイリアスなしでも動くかどうかは、layout.css.prefixes.webkitという設定がCSSについては用意されている。


先日about:configにlayout.css.unprefixing-service.enabledなんてのを見つけて「なんだろう」と思ってたのだけど、Compatibility Teamのメーリングリストにアナウンスがあった。どうやら-webkit-CSSプロパティとかがあったら、それを標準のコードとして解釈するものらしい。

This feature is only active for the "top 10" broken Chinese mobile sites that Peipei provided[1] (e.g. several Baidu services and m.taobao.com). The whitelisted sites should have a much-improved user experience, due to Firefox converting legacy -webkit prefixed CSS into equivalent unprefixed CSS.

Mozillaは2012年にもWebKit接頭辞について検討していて、当時はCSSだけでは意味がないと判断していた。ただFirefox OS端末の投入とかで状況が多少変わったんだろうか。それともUA SniffingAPIについても似たようなことやってるのかな?

まだサービスの実装も途中らしく、GradientsやCSS Animationsはサポートされてないとのこと。CSS AnimationsについてはYahoo! JAPANもWebKitのみらしく、このままだと登録しないとかもねなんて話が出ている。ヤフーさん!!!Yトップなんとか!!

PrestoやProject Spartanではすべてのサイトを対象に接頭辞の処理をしている(みたいだ)けど、Mozillaの場合は特定サイトのみに限定させるみたい。特定サイトだけの対応はIEのCompatibility Listの膨れっぷりを見ると茨の道な気もするけれど、がんばってほしい。

-webkit-alt プロパティ

これを読んでいて、内容とはまったく関係ないのだけれど -webkit-alt プロパティのことを思い出した。昨年11月に実装されたgenerated contentの読みを提供するためのプロパティだ。

実装された時期的にSafari 8/iOS8 Safariに入っててもおかしくないのだけれど、どうなのかな。誰か確かめておくれ。

装飾的なテキストをつける場合に、擬似要素にぶっこむという方法がある。

[data-new]::after {
  content: "\2730”; /* a.k.a. ✰ , literally “shadowed white star”  */
}

これでOKとおもいきや、どうやらVoiceOverはgenerated contentも読み上げてしまって、この星印を“shadowed white star”と読んでしまうらしい。DOMに存在してたらARIAの属性つけられるけれど、擬似要素なのでそうもいかない。そこで読みを提供するプロパティを導入したいと。

[data-new]::after {
  content: "\2730”;
  -webkit-alt: "New";
}

attr() も使えるので、ローカライズもしやすいとのこと。

[data-new]::after {
  content: "\2730”;
  -webkit-alt: attr(data-new);
}

ふうむ。擬似要素にテキストぶっこむというケースがどれくらいあるのかわかんないのだけど、広くサポートされる時代はすごく先になりそうだなあ。


これもまた全然関係ないのだけれど、絵文字を見る機会が増えてきた。ただ、グリフのデザインがよくないのか「これなんだろう?」と思ってしまう文字が多いのだよね。とくにぼくはケータイ持つのが遅くて絵文字の読解に慣れてないというのもあり、ハンディキャップというかなんかそういうのを感じている。絵文字イリテラシーというのかね。

表現のコンポーネントとエレメント指向

Google I/Oに行ってきたのでそれ関連の話題を。

Paper Elementsのエフェクト要素

Material Designというデザイン言語が出て、そのWebへの適用手段としてPolymerPaper elementsが発表された。

CodeLabsでもPolymerとPaper ElementsでTodoリストアプリをPolymerとPaper Elementsで作るというコースがあった。この中に、エフェクトを指定するステップがある。ここでほーと思ったのが、影をキャストする際にはpaper-shadowを、波紋の効果を出すpaper-rippleを使うこと。エフェクトが要素になっているのだ。

表現が必ず要素化されているわけでもない。固定化されたツールバーがあって、スクロールし始めた際に下のコンテンツから浮き出るような表現をMaterial Designではwaterfallと呼んでるのだけど、ツールバーにその表現を与えるにはmode="waterfall"という属性をつけてやるらしい。

エフェクトのコンポーネント

影や波紋がひとつの要素として定義されているのはどのUIでも汎用的に使われる視覚効果で、それをコンポーネントとして使いたいからなのだろう。ドキュメンテーションからは、自分たちが定義するコンポーネントにエフェクトを与えたい時にこれらの要素を使えと書かれている。waterfallが要素化されていないのは、いち資格効果というよりも、スクローラブルなUIのいち表現という面が強いからだろう。

なので、エフェクトも必ず要素にせよというわけではない。そう言われたとしても、それはPolymerのお作法という側面が強いはず。たぶん…

とはいえ、Polymerであれなんであれ、再利用可能なコンポーネントはインポートしちゃえばいろいろできるので、コンポーネント時代が来たらコスメティックな表現だけの「ユーティリティ」要素が増える可能性だってある。そうなるとページ内の各要素にpaper-shadowを与えて〜と、非効率なマークアップが増えるかもしれない。

エレメンタル、クラシカル

古い人間なので、表現まわりはCSSをはじめとする、表現の定義を要素に適用するアプローチのほうがいいんじゃないかと思ってしまう。大体の場合において、そちらのほうが高効率な気がするからだ。ところがWeb Componentsでは、コンポーネント内で使われる視覚表現であったり挙動であったりを分離して管理・適用する仕組みが弱い気がする。Decoratorsがひとつの解なのだろうけど、いまのところ用意されてない。

Decoratorsのプライオリティが低い理由として、コンポーネントを読み込む際にはself-containedなもののほうがパフォーマンスや依存関係の面で都合が良いということがあるのかなと思う。

もうひとつは、ワークアラウンドでなんとかできるというのもあるんだろう。現在のPolymerもそうしている。たとえばさっきのpaper-shadowも、タグを子として書くだけという方法のみを提供してるわけではない。

The paper-shadow element is a helper to add shadows to elements. Paper shadows are composed of two shadows on top of each other. We mimic this effect by using two elements on top of each other, each with a different drop shadow. You can apply the shadow to an element by assigning it as the target. If you do not specify a target, the shadow is applied to the paper-shadow element's parent element or shadow host element if its parent is a shadow root. Alternatively, you can use the CSS classes included by this element directly.

併記してDOMで関連づけることもできれば、各ステートをclass属性値としてexposeしている。なので今のUIコンポーネントがやってるやり方も使える。HTMLもしくはスクリプトで適用することになるから、CSSでどうこうという話ではないんだけれど、直接書くよりはいくぶんか効率よいのではないか。

でもpaper-rippleにはドキュメンテーションを見る限りはそういうのがないんだよね。なんでだろ。

DeclarativeとImperative

Web Componentsを語るときにたまに出る単語として“Declarative”, “Imperative”というものがある。HTMLのタグはdeclarativeで、JavaScriptでDOM操作をするのはimperativeだ。SVGはdeclarativeでCanvasはimperative、AppCacheはdeclarativeでService Workersはimperativeだ。そんな対立というよりは対比がある。

PolymerはDeclarativeな性質をかなり推している。というよりもエレメンタル指向なのかもしれない。だからといってImperativeなものが否定されるわけではない。ただ、declarativeはそのわかりやすさから“magic”を生みだし、時たま不都合や混乱を生み出す。古いもので<select>、まあまあ新しいものでAppCacheとか。

わかりやすさの責任がとれないと、僕らが不幸になる。Magicではなく、どう振る舞うのかが説明されて、はじめて強力なものになる。Extensive Web Manifestoが目剤しているのはそこだ。Polymerが要素でなんでもしようとしてるけど、それはWeb Componentsが下にあって、モデルを提供しているからになる。こうすると、エレメンタルだけではなくクラシカルなアプローチも提供できる。影を要素として日の下に出すことも、今までのようにクラスに潜ませることもできる。


DeclarativeとImperative、エレメンタルとクラシカル、相互に補完することで、コンポーネントのデザインはより柔軟になるんだろう。ただ設計をするひとは、どこでimperative(スクリプトをごりごり書くか)それともdeclarative(タグ書きまくるか)にするのかということをそれないり意識しないといけないのかも。

コンポーネントならいいけれど、文書のマークアップとして要素を作るときはさらによく分からんくなるのかも。要素なのかそれともクラスなのか。

実際にコンポーネント書く前に、そんなことばっかり気になってしまっている。

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

このエントリはCSS Preprocessor Advent Calendar 2012の記事の続編です。

察せ。


Sass 3.3がついにリリースされました。リリースされないものだと思っていましたがよかったです。
アルファ版からインストールしていましたが、いまいち使っていませんでした。というわけで、これまで作ったMixinからSass 3.3の新機能を実際に使えないか試してみます。

Part 8: マップライクな処理はマップに置換え

linear-gradient()-webkit-linear-gradient()など接頭辞つきのバージョンとでは方向を示すキーワードが違います。対応関係があるためマップやハッシュで表現したいところですが、これまでのSassにはそういったものがなく、Sass 3.2の次に実装するとされていました。
そしてSass 3.3でついにマップが実装されました。わーい。

Sassのマップは(key1: value1, key2: value2)のように、ラベルつきのカンマ区切りリストみたいなかたちになってます。マップから値を取り出したりマップに値を追加したりする際には、新たに定義されたマップの関数を使います。

というわけで、キーワードの変換部分に使っていたマップライクな処理をマップに置換えてみます。
まずはキーワード版の処理を。こうなってました。

@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);
    }
}

標準のキーワード、接頭辞版のキーワードを2つのリストにして、引数に与えられたキーワード(標準版)のインデックスを取得。出力時にそのインデックスを使い、接頭辞版のキーワードを引っ張ってきています。
これがマップを使うとこうなります。

@mixin lg-keyword($keyword, $colorstops...) {
    $prefixes: '-webkit-';

    $keywords_map: (
        to bottom:       top,
        to left:         right,
        to top:          bottom,
        to right:        left,
        to top left:     bottom right,
        to top right:    bottom left,
        to bottom right: top left,
        to bottom left:  top right,
        to left top:     right bottom,
        to right top:    left bottom,
        to right bottom: left top,
        to left bottom:  right top
        );

    // キーワードが不正な場合は何も出さない
    @if map-has-key($keywords_map, $keyword) {        
        @each $prefix in $prefixes {
            background-image: #{$prefix}linear-gradient(map-get($keywords_map, $keyword), $colorstops);
        }
        background-image: linear-gradient($keyword, $colorstops);
    }
}

(標準版: 接頭辞版, ...)というマップに作り直しました。引数が標準版のキーワードになっているかを調べるにはmap-has-key()関数を使えばOK。マップから値を取り出すときはmap-get()関数を使います。

ソースを整形してるため行数的にそんな変わってませんが、インデックスを取得するとかをしなくなったので、だいぶわかりやすくなったのではないかと思っています。
ただマップがマップとして読めない感はありますね…キーは内部的にリストとして扱われているので、括弧でくくってやるとちょっとはわかりやすくなるかな…うーん。

まとめ

とりあえずマップライクな処理を置換えても問題ないことがわかりました。他にもマップが使えるところがありますが、そちらはこれほどストレートフォワードではないので、また次回に。
えっ、次回…?

補足:-prefixed-linear-gradient()いるかい?

今回はlinear-gradient()から-webkit-linear-gradient()などの接頭辞版の構文互換の記述を吐き出すmixinを書きましたが、標準の構文がサポートされてからけっこう経ちます。

一番サポートが遅かったのはWebKitなのですが、こちらはSafari 7.0からサポートされています。Safari 6.0以前のサポートによって入れるか入れないかを決めてもよいのではないかと。あるいは-webkit-gradient()を吐き出すなら、特に入れなくてもいいのではないかと思います。

システムの文字サイズを取得できないものか

80歳の方にiPad Airを使わせてみた方のエントリを読んだ。

母とNexus 7

ちょうど年末年始の帰省時に母(60くらいだったかな?)にNexus 7(2012年モデル)を渡したのだけど、このエントリにある「ボタンはしっかりと押さないとね!」というのがあって困った。画面上じゃなくて電源ボタンも無意識に長押しになるので電源オフにするダイアログが出る。実家から戻るときにはだいぶ良くなってたけど、タップはぎこちなかった。慣れてくれるといいな。

あと面白かったのが、アプリアイコンとブックマークを混同していたというか同一視してたこと。「ニュースを見たい」とアプリ一覧を指して言って「ほえ?」となった。アプリ一覧はできないけど、ちょうどChromeがメニューからホームスクリーンに登録できるようになったので、それを教えてあげた。ホームスクリーンがアイコンだらけにならないといいけれど。

しかし、Windowsのデスクトップを見たところWebサイトへのショートカットがばかすかあるわけでもなく、なんでNexus 7でそんな認識になったのかわからない。Androidはまたアプリ一覧とホームスクリーンがあるのでややこしいよね。iPadだったらまた違う反応をしてたのかな。

 他にも面白かった…というかめんどかったのが、半透明のグレーのやつをオーバーレイさせる系のチュートリアルを読みもせずすっとばすこと。「えっえっ」って言って一番目立つ色のボタンを押しちゃってチュートリアル終了という。わかんないと言われてもこっちだって見てないし…アプリ詳細からデータ削除してもう一回やりなおしたりとか。あれ系を採用する場合にはぜひともチュートリアルをやり直すオプションがほしい。

システムの文字サイズが反映されないアプリがかなりある

母の観察記はほどほどにして本題を。件のエントリでも触れられていた点が特に気になった。

文字の表示サイズを大きくできる

「太字」「文字を2まわりくらい大きく」な設定にしてきました。だってiPadの文字を虫眼鏡を通して読んでいたんですもん…。
私が普段コーディングをしながら「ギャアア」と思っている文字サイズ変更機能も、こういう人向けに必要なのですね。知識として知ってはいましたが、「ああ、こういうことなんだー」と思いました(直接的には少々違いますが、概念として)。

こちらもAndroidの設定から文字サイズを指定できたので、てきとーに大きくしておいた。Chromeにも設定あるんだけどそっちはどうだったかな…でも-webkit-text-size-adjust設定されてたら意味ない気がするけど。

システムの設定はできても、拡大できるのはシステムのサイズを継承してくれるUI周りで、コンテンツ周り?は拡大してくれないアプリが多かった。ピンチズームも効かなくてうーんと思った。

ちょっと見てみて、WebView使ってそうなアプリにそういうのが多い感じがした。システムのサイズを取ってくるAPIとかないのだろうか。

IndieUI User Context

Web標準側にもそういうのは今のところないはずで、構想がIndieUI WGでつくってるIndieUI 1.0: User Contextにあるくらいか。“feature key”というメディアクエリーの媒体特性みたいなの(たまに媒体特性としても使えるらしくよくわからない)にuser-font-sizeというのがあるのでそれを使えばよいらしい。

// example of settings keys defined within the IndieUI User Context specification.
window.userSetting('user-font-size'); // returns computed value in CSS pixels, e.g. '16px'

とはいってもまだ構想段階な感じ。TPACのときにあったCSS WGとのジョイントミーティングWebApps WGとのジョイントミーティングでは、ハイコントラストモードの検出とか一部の機能については支持があったけど、セキュリティ・プラバシー的な懸念とかもやっぱりあって、まだまだという印象を受けた。


使えたら使えたで「システムの文字サイズ継承とか…」みたく思ってしまうのだろうけど、読めないよりは崩れてても読めたほうがいいよねえ。

all, initial, unsetでCSSのリセットと継承回避をする

追記(2018年4月13日):紹介した機能の実装が進みました(Can I use:allプロパティinitialunset)。

一方で文中で取り上げたScoped Stylesheetsは仕様から削除されてしまいました。


このエントリはCSS Property Advent Calendar 2013の10日目のものです。
すみませんすみません日付勘違いしてました。ほんと申し訳ありません……

今回はradial-gradient()のMixin…は作りません。プロパティじゃないしね。ふつうのプロパティと値についてご紹介しようかと。

CSS Cascading & Inheritanceのall, initial, unset

Firefox 27あたりからallプロパティとunset値なんてものが実装されました。CSSCascading and Inheritanceモジュールで定義されてるんですが、これを使うとスタイルを局所的にリセットできます。実装したMozillaのCameronのエントリが詳しいです。

allは「すべてのプロパティ(※ただしdirectionunicode-bidiを除く)」を指します。すべて(※)です。これから定義・実装されるプロパティもすべてallになります(※)。ターンAみたいですね。

initial, unsetCSSワイドな値です。initialは初期値(を指定値にする)値で、結構前からいろんなブラウザで実装されています。inheritというのもあります。これは継承値を指定値かつ算出値にするものです。unsetというのはその間の子で、継承するプロパティ(font-sizeやらcolorやら)についてはinherit, そうでないものにはinitialという扱いになります。unsetユースケースがいまいちわからず、昔何かで読んだ気がするのですが忘れました。とりあえず次に進みます。

スタイルをリセットしたい?

スタイルをリセットしたい場合って、今のところそんなあるようなないようなですが、かなり個人的な背景からそう思うときは多々あります。

たとえば、HTMLとCSSの講演スライド。スライド作成ソフトと比べるとまだあんま良くないと思っているのですが、スライド中にWebコンテントを直接埋め込める利点があります。とくにHTMLやCSSについてお話させてもらうので、実際のコンテンツを埋め込めると楽です。

埋め込みというと、外部コンテンツを<iframe>で読み込むという手もありますが、複数ファイル作るのが面倒な例などは、インラインで直接書きたいものです。

<div class="example">
  <!-- サンプルコードがここに入る -->
</div>

とはいえ、ここで面倒なのが、div.example内のコンテンツが親のスタイルを継承してしまうこと。スライドのスタイルは10-feet UIといいますか、背景と前景のコントラストをなるべくとる、文字は大きくするなど、デスクトップスクリーンとはまた違う流儀があります。サンプルがどんなものかにもよりますが、そういったスタイルが継承されると上書きすることが面倒です。サンプルはシンプルにゼロから始めたい。ここでallinitialが役立ちます。

<div class="example">
  <style scoped>
    * { all: initial }
  </style>
  <!-- サンプルコードがここに入る -->
</div>

<style scoped>を入れてdiv.exampleだけに対応するスタイルを書けるようにします。そして最初でall: initialでスタイルをキャンセルすることにより、div.exampleが親からのスタイルを継承しないようにします。

親のスタイルを一部継承したい場合はall: unsetでもいいかもしれません。

displayinlineになるのがだるいですね…プロパティごとにinitialのがいいかもなあ。

Scoped Stylesheetsの危機

HTML+CSSなスライドで例をインラインで書きたい、というあまりにもニッチなユースケースですが、all, initial unsetの便利さをお分かりいただけたでしょうか。無理かなあ。

さて、実装が進めば素敵なHTML+CSSなスライドライフをおくれそうですが、現在Scoped Stylesheetsを消さないかという話が出ていたりします。もともとHTML5でat riskな機能で、現在もFirefoxでしかリリースされてません。そんな中blink-devで削除しないという話がでてしまい、あまりいい具合ではありません。Hixieは制作者の要望が大きいことを述べていますが、大きく反対しているわけでもなく、しばらく放置されそう。HTML5からは削除されるかな。

<style scoped>を使わずとも、スコープさせたい要素の親にidでもふってやればいいのですが、サンプルコードまわりをひとまとめにしときたいんだよなあ。

Chromium DashboardのCSS usage metricsとぼくらのusage metrics

火曜にGoogle JapanであったChrome Tech Talk Night #6でライトニングトークをしてきた。
イベント中ずっと資料とコードを書いてて内容をほとんど聞いてないという残念っぷり…こういうのやめたいね。

Chromium Dashboardの概要と、9月に追加されたCSS metricsの紹介と、自分がアクセスしたページならchrome://histogramsからデータとってこれるよという話をした。

資料はこちら。

以下ちょっといろいろ。

CSS usage metricsで気になるところ

すこしある。

  • Chromeが解釈できる機能に限定されること
  • 現在のところプロパティのみということ
  • (たぶん)プロパティの「書かれた回数」をもとにしてないこと

ひとつ目は他のベンダー接頭辞つきプロパティの利用状況がわからないこと。たとえば-moz-border-radius-moz-opacityGeckoはもう解釈しないけど、使われてるページはまだ多いだろうから、そういうのもわかるとうれしいなと。DevToolsでは他のベンダー接頭辞つきプロパティも表示してるので情報は取れると思うんだけど、むつかしいのかなあ。

ふたつ目は、linear-gradient()input関連の独自セレクタ@ruleについてはわからない。ただ値についてはissueが上がっているので、やるひとがいたらなんとかなるかも。

みっつ目はややこしい。ちゃんとソース読んでないので保証できないんだけど、たぶんUseCounterの仕様からプロパティの「書かれた回数」ではなく「出現したか否か」を元にデータを出している。なのでこのmetricsで上位にあるプロパティはWebで多く「書かれている」プロパティではない。「使われている」とは言えると思うけど。そこらへんトリッキー。

ぼくらのCSS metrics

UseCounterを使う関係上、そのデータはGoogleしかわかんないことになる。オープンデータ!とか強く言うつもりはないけど、ちょっとくやしい。

グローバルなデータはないけれど、「ローカル」なデータは手に入る。UseCounterをはじめChromeが集めてるデータはchrome://histogramsで見られる。アクセスするとファンシーなアスキーアートが並んでると思う。DNS関連のあれこれとか、パフォーマンス関連のものが多い。

UseCounterはこの仕組みを使ってるので、このページから探し出せばいい。「ヒストグラム」としては失格だけど、データが手に入るからいいんだ。

で、データの取得がちょっと面倒。なんせ<pre>アスキーアートなので。

はじめはXHRで自身をDocumentとして取得して、目的の<pre>を見つけて、そのtextContent正規表現をかけて…ということをやっていた。そしたら先週末くらいにchrome://histograms/FeatureObserver.CSSPropertiesとかで絞り込めることがわかって、XHRのところは省けた。すっきり。

Chrome拡張にしようかと思ったのだけど、今のところchrome://なURLにXHRができない。フラグがあるけど有効にしたら使えるのかな。

そんな中途半端なところでおわった。4分オーバー。すいません。英語むつかしいです。

Autoprefixer ― CSSのベンダー接頭辞をいろいろする

以前CSS-Tricksの記事で知ったAutoprefixerというの、ようやく試してみた。

CSS-Tricksの記事読んだほうがいいと思うけど、月一のブログ更新ノルマのためここでも書いとく。

かしこい「ポスト」プロセッサ

Autoprefixerはその名前からある程度想像できる通り、ベンダー接頭辞を自動的につけたりしてくれるユーティリティ。CSSファイルを作るのではなく、CSSファイルを処理するので、ポストプロセッサと言っている。

接頭辞まわりは、すでにCompassとかのライブラリでmixinなどが用意されてるので、べつに必要ないじゃんと思うかもしれない。ただ、プリプロセッサのmixinや野良mixinってちょっと雑で、吐き出されるCSSにあまり2013年感がない(個人の感想です)。

あと、とくに野良mixinに多いけど、ただ単に接頭辞つけただけで、仕様の構文が変わった場合に対処できてないものが多いかなと。無駄もしくは無意味なプロパティがCSSに書き出されてるのをちまちま見る。処理前のファイルはクールに書けても、出力されたCSSはクールじゃない。

Autoprefixerはcaniuse.comのデータをもとに、必要だろうとされるバージョンを判定して、そういう無駄な接頭辞を書き出さないようにしている。Compassも接頭辞の出力をカスタマイズできるけど、設定しないといけないのでちょっと煩わしい。自動でやってくれると楽だよね。

試してみた

ではどんなものかということで、試そう。コマンドラインはもとよりGruntやSublime Textなど、いろいろな環境で使える。今回はnpmからインストールして、コマンドラインで試してみた。

まずはborder-radiusbox-shadowの組み合わせ。こんなファイルを用意した。

.button {
    border-radius: 3px;
    box-shadow: 0 0 5px 1px #999;
}

雑な定義なのは気にしない。コマンドラインからAutoprefixerをかけた新しいファイルを出力。

% autoprefixer button.css -o button_a.css

autoprefixerって打つのだるい。あと-oってOperaの接頭辞つけるのかって気分になる。
どうでもいいね。結果はこうなった。

.button {
  border-radius: 3px;
  -webkit-box-shadow: 0 0 5px 1px #999;
  box-shadow: 0 0 5px 1px #999;
}

border-radiusは接頭辞なし、box-shadowはGingerbread用に-webkit-box-shadowが付け足されて出力されている。おお。

構文の変更もがんばってくれてる

接頭辞ついた版をつけてくれるのは分かった。では、構文の変更にはどれくらい対処してくれるのか。Autoprefixerのソースを見るとhacksというディレクトリがあって、構文の変更があるプロパティとか表記系を処理するファイルは、ここに個別に用意してあるようだ。

では、ぼくが愛を超えた憎しみみたいなガンダムにありそげな感情を持ってる(持ってない)linear-gradient() はどうなるか。こんなファイルを用意した。

.gradients {
    background-image: linear-gradient(#eee, #ccc 50%, #aaa);
}
.gradients-keyword {
    background-image: linear-gradient(to top left, #eee, #ccc 50%, #aaa);
}
.gradients-angle {
    background-image: linear-gradient(30deg, #eee, #ccc 50%, #aaa);
}

結果はこちら。

.gradients {
  background-image: -webkit-gradient(linear, top left, bottom left, from(#eee), color-stop(50%, #ccc), to(#aaa));
  background-image: -webkit-linear-gradient(#eee, #ccc 50%, #aaa);
  background-image: linear-gradient(#eee, #ccc 50%, #aaa);
}
.gradients-keyword {
  background-image: -webkit-gradient(linear, bottom right, top left, from(#eee), color-stop(50%, #ccc), to(#aaa));
  background-image: -webkit-linear-gradient(bottom right, #eee, #ccc 50%, #aaa);
  background-image: linear-gradient(to top left, #eee, #ccc 50%, #aaa);
}
.gradients-angle {
  background-image: -webkit-linear-gradient(60deg, #eee, #ccc 50%, #aaa);
  background-image: linear-gradient(30deg, #eee, #ccc 50%, #aaa);
}

おお、ちゃんと-webkit-linear-gradient()iOS 6まで)と-webkit-gradient()(Gingerbread用)に対応して、あとは接頭辞なしの物を使うようになっている。キーワードの変換も、角度の変換も、どちらもちゃんとできてるし、角度が使えない -webkit-gradient() については、ちゃんと無視されている。すばらしい。

ただ、IE9用にSVGのフォールバック画像をつくったり、IE8以下用にフィルタをつけてくれたりはしないようだ。hacks/gradient.coffeeなるファイルがあったので見てみたけど、そういう処理はなさげ。ふーむ。とはいえどちらも完全なハックだし、IE9は順調にIE10に置き換わってるようだし、IE8以下は「もうプレーンでよくない?」って言え…ないかな……

要らないものを消してもくれる

さて、ある程度試したところで、つけたすだけじゃなくて、要らないものを省いてくれることに気づいた。なんと。ではさっそく。

.so-200x {
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;
}

結果。

.so-200x {
  border-radius: 5px;
}

おおお。すっきりした。じゃあCompassから出力されたものも、それなりに綺麗にしてくれるのかも。

「ポスト」のよしあし

結構よさげ。仕様のとおりに書いとけばあとは勝手にやってくれるので、簡単なmixinさえ書く必要がない。仕様に変更があっても、あたまの良い人達がきっとすぐ対応してくれるだろうし、すこし複雑なmixinとかも書かなくてもいい。いったいぼくのあの日々はなんだったんだ。

あとは、ポストプロセッサなので、特定のプリプロセッサに依存しないのもよい。プリプロセッサを乗り換えるってことはそんなないだろうけれど、それでもポータビリティは上がるかなと。

とはいえ、ポストプロセッサなところが弱点でもあるかと。主に開発時に面倒に感じることが多そう。

たとえば、プリプロセッサとの併用。Chrome+Sassな環境なら、Source Mapsやデバッグ用の出力モードで、CSSパネルからSassを参照できる。ただ、ポストプロセッサが入ってしまうとマッピングがずれてしまう。Autoprefixer側でプリプロセッサが生成したSource Mapsを解釈して、新しいマッピングファイルを作ってくれればよいのだけど、まだ対応されてない

あとは、どうやら接頭辞つきのみのコードに、接頭辞なしのものを含めてくれるわけではないみたい。となると、たとえばChromeでアニメーションのコードを書いているときは、@-webkit-keyframesしか書かないことがほとんどだと思うけど、そこから@keyframesは生成されない。でもCSS Animationsの接頭辞が省かれるのはだいぶ先だろうし、でも開発中は接頭辞必要だろうし…というジレンマに陥る。

となると、開発中は接頭辞つき、展開時は接頭辞なしのものを吐き出すようにプリプロセッサやツール周りで対応…とかややこしいことになる。接頭辞はだいぶ減ってきたとはいえ、まだまだお世話?になると思うので、ここはなんとかして欲しいなあ。

もいっこ。CSSをパースして処理する関係上、インデントやらが独自のものにされてしまう。これは展開時にCSSOなりして再処理が走るなら、そんな気にならないだろうけど。

Blinkでメディアクエリーのwidth/heightがスクロールバーを含むように

Chrome 28がBetaに移った。表面上とくに違いはないだろうけれど、BlinkなChromeですよ。

さて、タイトルで言ったとおりの変更がBlinkに行われた。

M28に入るかなと思って試したんだけど、そう動かなかったのでこれはM29からになりそう。Stableまでは3ヶ月ほど猶予があるから、入らなくてよかったかも。

ええと、どういうことかというと、もし、

  1. RWDなどメディアクエリーを使うサイトを作っていて
  2. クエリで width (あるいは height) を使っていて
  3. ChromeもしくはSafariをメインに作っていて
  4. そのサイトをFirefox, Opera, IEでテストしていない

という場合、ブレークポイント前後で予期せぬ横スクロールバーが出る可能性があるのでお気をつけくださいというおはなし。

仕様のおさらいとWebKitのバグ

メディアクエリー仕様では媒体特性 width, height について、スクロールバーがある場合、その幅を含めた値が媒体特性のとる値と定義されている。

The ‘width’ media feature describes the width of the targeted display area of the output device. For continuous media, this is the width of the viewport (as described by CSS2, section 9.1.1 [CSS21]) including the size of a rendered scroll bar (if any).

Gecko, Presto, Tridentはこれにしたがって実装しているのだけれど、WebKitのメディアクエリー実装には width, height がスクロールバーの幅を含めないっていうバグがある。

これがBlinkにも引き継がれたのだけれど、最初に書いたようにちょっと前に修正されたと。

Macだと気が付きにくい

この挙動、Media query width and vertical scrollbars demo pageにて確認できる。ただ、Macだとめんどくさい。

スクロールバーの幅を含めると書いたのだけれど、Lionからはスクロールバーがオーバーレイなものになった。このスクロールバーは、ページや要素の幅/高さに影響していない。Chrome/Safariをメインに〜と書いたのはそういう理由。

System Preferences→General→“Show scroll bars”から“Always”を選択すればスクロールバーを表示させられるので、ちょっと面倒だけれどどうぞ。

もっとも、楽なのはFirefoxOpera (Presto)で試してみること。どちらも現時点で仕様に準拠していて、かつオーバーレイスクロールバーを実装していないのでどうなるかがわかる。あと、そろそろスクロールバー何とかしろよという話については、すでにAuroraだとオーバーレイスクロールバーになっているので、もうちょっと待たれいですよ。

直さなくてもいいような気をつけたいような

問題意識高いひと(めんどくさい)なのでくどくど書いたけれど(めんどくさい)、そんな大した問題でもないかなとも思う(めんどくさい)。

あまりに幅にピーキーなつくりにしてなければ、だいたい大丈夫じゃないかなと。あと、Firefoxとかでチェックしてたら、そう起こらないと思うので。
問題があったとしても、ブレークポイント前後でウインドウ幅びろびろさせたりする人はWeb制作者以外にはそういないだろうから、気にしなければそれまで、でいいんじゃないかなと。

ただ、JavaScriptでページの幅とって何かしたりするものがあると、15px前後(スクロールバーの幅ぶん)ずれが生じるかもしれない。

あと、そんなないと思うけど、matchMedia() にマッチさせたい幅からスクロールバーぶん引いた値をクエリとして渡してる場合は書き換えないといけない。

ぐうぐうたらら

viewport(metaのじゃないほう)においてはスクロールバーの幅を含めないほうがsensibleなんじゃないかとも思わなくないけど、CSSがsensibleであったことなんてないのでこれについては諦めてもらうしかない……。

“responsive”と呼ぶサイト作ってて、幅に敏感すぎるつくりは気が利いてないってことなので、ちょっとゆるめに。ピクセルセントリックだと、ちょっと難しいのかもだけど。

Opera 14デスクトップ版がいま出てしまうと、Chrome 26ベースなので、後退するのが切ないところ。早く出てほしいけれど……。

このパッチWebKit側でマージできたりしないだろうか。OS X/iOSでは影響しなくても、他のWebKitなブラウザでは影響するので。