読者です 読者をやめる 読者になる 読者になる

CSSのcanvasとviewportとposition:fixedとpinch zoom

  • specを元にした概念の整理
  • 間違いあったら教えて欲しい

CSS 2.1におけるviewport

CSS 2.1におけるviewportを説明するにあたり、以下のterminologyが必要となる

canvas

For instance, user agents rendering to a screen generally impose a minimum width and choose an initial width based on the dimensions of the viewport.

viewport

User agents for continuous media generally offer users a viewport (a window or other viewing area on the screen) through which users consult a document.

When the viewport is smaller than the area of the canvas on which the document is rendered, the user agent should offer a scrolling mechanism.

initial containing block

The containing block in which the root element lives is a rectangle called the initial containing block. For continuous media, it has the dimensions of the viewport and is anchored at the canvas origin; it is the page area for paged media. The 'direction' property of the initial containing block is the same as for the root element.

  • ブラウザ(=screen mediaのUA)のスクロールバーを伴った描画領域 = viewport
  • レイアウトはcanvasに描画される
  • initial containing block(デフォルトのcanvas領域)のサイズはviewportの幅と高さに依存する.

viewportが1000pxの場合にhtml要素のwidthを100000pxにしたらどうなるのか?については、overflowプロパティによって定義されていて、

UAs must apply the 'overflow' property set on the root element to the viewport. When the root element is an HTML "HTML" element or an XHTML "html" element, and that element has an HTML "BODY" element or an XHTML "body" element as a child, user agents must instead apply the 'overflow' property from the first such child element to the viewport, if the value on the root element is 'visible'. The 'visible' value when used for the viewport must be interpreted as 'auto'. The element from which the value is propagated must have a used value for 'overflow' of 'visible'.

のように,

  • root要素のoverflowプロパティがviewportに対しても適用される
  • viewportのoverflowプロパティがvisibleである場合、autoとして解釈されるので、スクロール機能の有無はUA依存となる

position:fixedの挙動

In the case of handheld, projection, screen, tty, and tv media types, the box is fixed with respect to the viewport and does not move when scrolled.

For other elements, if the element's position is 'relative' or 'static', the containing block is formed by the content edge of the nearest ancestor box that is a block container or which establishes a formatting context.

要は、対象のボックスがviewportに対しての固定座標に配置されるということ.

CSS Device Adoptationでの拡張

CSS Device Adaptationでは、meta[name="viewport"]でのviewportサイズの指定を可能にしたiOS Safariなどの実装を追認を行う形で仕様が策定されており、ここでは、initial viewportとacutual viewportの二種類が定義されることになった。

initial viewport

This refers to the viewport before any UA or author styles have overridden the viewport given by the window or viewing area of the UA. Note that the initial viewport size will change with the size of the window or viewing area.

actual viewport

This is the viewport you get after the cascaded viewport descriptors, and the following constraining procedure have been applied.

たとえば、meta[name="viewport"][content="width=device-width"]を指定した場合は以下のようになる

  1. initial viewportがブラウザのウィンドウサイズや描画領域に基づき決定される
  2. UAスタイルおよびユーザースタイルによるmeta[name="viewport"]@viewportの計算が行われる

    1. meta要素によるwidth=device-width@viewport { width: 100vw; }として取り扱われる
    2. ここでの100vw1で計算されたものが取り扱われる
    3. width: 100vw;max-widthmin-widthマップされる
    4. 3でマップされた結果を元に、actual viewportの幅を計算する
  3. 計算の結果、deviceの幅=ウィンドウの幅を自身の幅としたactual viewportが算出される

  4. actual viewportを元にinitial containg blockを再度計算する

ここでもUAスタイルがあるために、仮にviewportの指定が行われていない場合は、従来のサイトとの互換性のために、モバイルブラウザでは自動的にviewportの横幅が1000px前後に設定されたりするようになる。

デスクトップブラウザについては@viewportUAスタイルを持たないと解釈すればいいのだろうが、dbaronによるissue表記があるので、完全に定義が確定しているわけではないのだろう。

では、メディアクエリはどこで計算されるのか? それは以下のように定義されている:

  1. Cascade all @viewport rules using the initial viewport size for values and evaluations which rely on viewport size
  2. Compute the actual viewport from the cascaded viewport descriptors
  3. Cascade all other rules using the actual viewport size

つまり、viewportの計算が全て終わらないとmedia queryをはじめとした計算は起こらない

zoom

ズームについては、まず、CSSOM Viewにて二種類のズームがあることが述べられている:

There are two kinds of zoom, page zoom which affects the size of the initial viewport, and pinch zoom which acts like a magnifying glass and does not affect the initial viewport or actual viewport.

この詳細についてはCSSOM View側では「CSS Device Adaptationを参照」とあるのだが、CSS Device Adaptationには明確なterminologyが設定されているわけではなく、断片的に文章中に記述されているだけである。

When the actual viewport cannot fit inside the window or viewing area, either because the actual viewport is larger than the initial viewport or the zoom factor causes only parts of the actual viewport to be visible, the UA should offer a scrolling or panning mechanism.

This is a magnifying glass type of zoom. Interactively changing the zoom factor from the initial zoom factor does not affect the size of the initial or the actual viewport.

position:fixedとpinch zoomを組み合わせるとどうなるのか

今までに述べた内容をまとめると、

  • viewportはブラウザ(=screen mediaのUA)のスクロールバーを伴った描画領域
  • position: fixedは、対象のボックスをviewportに対する固定座標に配置する
  • pinch zoomはactual viewportに影響を与えない

となるのだが、pinch zoomをした場合に、Android BrowserやiOS Safariでは常にUAの描画領域の枠の固定位置にposition: fixedが配置されるのは、仕様上正確なのかどうか分かり難い…… pinch zoomではviewportに影響を与えないとあるが、zoomをしている以上は、UAとして表示している領域としてのviewportの横幅は相対比で小さくなっているはず。ちなみにW3C Bugzillaでは特に何も見つからず……

pinch zoomとpostion: fixedへの各UAの対応

IE11 Mobileや最近のChromiumでは、この問題に対して、「pinch zoomを行っても、position: fixedを常にUAとして表示している領域に配置しないようにした」(私の知りうる限り、Firefox修正を検討中である)

どのような変更を行ったのかはChromiumの変更を開設したスライドがわかりやすい。

このスライドでは、viewportとされるものは正確には、

  • visual viewport: UAとして表示している領域という意味でのviewport
  • layout viewport: レイアウト計算に用いられるviewport

の二種類が存在するとしている(正確には、この二種類が存在しているとすることで、問題を解決することに成功した)。

pinch zoomの存在しなかった時代(CSS 2.1での定義)では両者は統一して扱われるものであったのだが、モバイルブラウザ(およびUIとしてのpinch zoom)の登場により、分割して扱う必要が出てきた。しかし、現行のCSS Device Adaptationではそこにまで踏み込んだ定義がされていないために話がややこしくなっている。いや、この一件に限らず、CSS Device Adaptationは出来がいいspecとは思えないですけどね……

ちなみにWindow.scrollX/Ywindow.innerWidth/Heightなどのviewportのスクロールに絡む座標は、Chrome Beta for Android 40.0.2214.69で確認したところ、visual viewportを基準に算出されるようになる。まあそうするしかないですよねcompat的にも。なので、

The innerWidth attribute must return the viewport width including the size of a rendered scroll bar (if any), or zero if there is no viewport.

The scrollX attribute attribute must return the x-coordinate, relative to the initial containing block origin, of the left of the viewport, or zero if there is no viewport.

以上の定義中にある"viewport"は, visual viewport。ややこしい。

まとめ

図にするとこんな感じ。仕様+実装を元に、viewportというものは、このような概念であると解釈が出来る。

尚、visual viewportとlayout viewportという単語は、Chromiumのスライドのやつが都合がいいから使ってるだけね。

   +---------------------+----------+
   |                     |          |
   |  *--------*         |          |
   |  |        |         |          |
   |  |  visual viewport |          |
   |  |        |         |          |
   |  |        |         |          |
   |  |        |         |          |
   |  *--------*         |          |
   |                     |          |
   |                     |          |
   |   layout viewport   |          |
   |                     |          |
   |                     |          |
   +---------------------+          |
   |                                |
   |       canvas                   |
   |                                |
   +--------------------------------+

  • directionがltrの場合, 左上からactual viewportが始まる
  • layout viewport = actual viewport
  • layout viewportはinitial contaning blockの大きさを決める
  • visual viewportはpinch zoomなどにより虫眼鏡のようにサイズが 可変したり、スクロールしたりする
    • overflow:hiddenがviewportに適用される場合はinitial containg blockを超えて動けない
    • Window.scrollX/Y, Window.innerWidth/Heightなどのスクロール関係値は、visual viewportを元に算出されていると解釈できる
  • IE11 Mobileや最近のChromiumを除く従来の実装では、position:fixedはvisual viewportを元にレイアウトされていると考えられる