topやleftなど「距離や長さ」に関するCSSプロパティで値にパーセント値を指定したとき、それが「何に対する割合」なのか私は理解があやふやだったので、この記事では理解するためのポイントを整理したいと思います。例として次の4つのケースを取り上げ、プロパティ値に100%を指定したときに要素の配置や寸法が何を基準にどのように変化するかを図で確認してみましょう。
- position: absoluteのときのtop, left
- position: relativeのときのtop, left
- transform: translate関数の引数X | Y
- padding-top (ブロック要素の高さを確保するのに用いる)
記事中のサンプルのソースコードはこちら。
DEMO: https://codepen.io/kaz_hashimoto/pen/LYPLdPy
例1. position: absoluteのときのtop, left: 100%
最初の例は、position: absolute
を設定した子要素をtop: 100%; left: 100%
で配置した場合です(下図)。親要素div.box-1の中に2つの子要素div.child-1, div.child-2を入れ、どちらの子要素もwidth, heightを50%に設定しました。そして、.child-1のみposition: absolute
を設定し、.child-2は通常のフローで配置しtranslate
を使って再配置しています。再配置によって子要素がどこからどこまで移動したのかわかるようにアニメーションにしてあります。.box-1の左上角あたりが元の位置(top:0, left:0, translate(0,0))ということになります。
図で.box-1のコンテンツ領域を破線で示してあります(.content-box)。
<div class="box-1">
<div class="child-1 child-box"><p>width, height 50%<br>top, left 100%</p></div>
<div class="child-2 child-box"><p>width, height 50%<br>translate 100%</p></div>
</div>
CSS (要点のみ)
.box-1 {
width: 300px;
height: 100px;
padding: 20px;
border: 16px solid rgba(0,0,0,0.3);
background-color: #B3E5FC;
position: relative;
}
.child-box {
box-sizing: border-box;
width: 50%;
height: 50%;
border: 1px solid #000;
}
.child-1 {
background-color: #E91E63;
color: #fff;
position: absolute;
top: 100%;
left: 100%;
}
.child-2 {
background-color: #3F51B5;
color: #fff;
transform: translate(100%, 100%);
}
- width, heightがそれぞれ.box-1のパディング領域のwidth, heightの50%になっている。
- 元々の位置(top: 0, left: 0)は.box-1のパディング領域の左上角になっている。
- top,leftを100%にした結果、元の位置から.box-1のパディング領域の幅と高さの分だけ離れた位置に配置されている。
一方、通常フローで配置した.child-2の方は、
- width, heightがそれぞれ.box-1のコンテンツ領域のwidth, heightの50%になっている。
- 元々の位置(translate(0,0))は.box-1のコンテンツ領域の左上角になっている。
ことがわかります。(※translateについては例3に書きます)
position: absolute
の場合の計算根拠をCSSの仕様書で確認してみましょう。たとえばwidthに関して、10.2 Content width: the 'width' propertyの注記によると確かに、
Note: For absolutely positioned elements whose containing block is based on a block container element, the percentage is calculated with respect to the width of the padding box of that element.
ここで包含ブロック(containing block)の定義を確認しておくと、10.1 Definition of "containing block"の説明では
If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
(中略)
2. Otherwise, the containing block is formed by the padding edge of the ancestor.
例1a. 親と子要素の間にinner divがある場合
例1で、.box-1のposition: relative
はそのままに、.box-1の内側にラッパーとなるdiv.innerを追加して、.child-1と.child-2を.innerの子要素(.box-1の孫)にした場合はどうなるでしょうか?(下図)
<div class="box-1">
<div class="inner">
<div class="child-1 child-box"><p>width, height 50%<br>top, left 100%</p></div>
<div class="child-2 child-box"><p>width, height 50%<br>translate 100%</p></div>
</div>
</div>
CSS (要点のみ)
.box-1 { /* 例1と同じ */ }
.child-box { /* 例1と同じ */ }
.child-1 { /* 例1と同じ */ }
.child-2 { /* 例1と同じ */ }
.box-1 .inner {
width: 260px;
height: 80px;
outline: 3px solid red;
outline-offset: -3px;
background-color: transparent;
}
結果は、.child-2はサイズが.innerに合わせて小さくなりましたが、.child-1の方はサイズ・位置ともに例1から変化しないことがわかります。理由は、上述の包含ブロックの定義によると、.child-1から見て「the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed'」(position の値が static 以外の直近の祖先要素)は.box-1のままなので、.child-1は.innerのサイズに影響を受けないためです。
例2. position: relativeのときのtop, left: 100%
次に、例1で.child-1のposition
の値をabsolute
からrelative
に変えてみます(下図の.child-3)。
<div class="box-1">
<div class="child-3 child-box"><p>width, height 50%<br>top, left 100%</p></div>
<div class="child-4 child-box"><p>width, height 50%<br>top, left 100%</p></div>
</div>
CSS (要点のみ)
.box-1 { /* 例1と同じ */}
.child-box { /* 例1と同じ */}
.child-3,
.child-4 {
color: #fff;
position: relative;
top: 100%;
left: 100%;
}
.child-3 { background-color: #004d40; }
.child-4 { background-color: #009688; }
- width, heightがそれぞれ.box-1のコンテンツ領域のwidth, heightの50%になっている。
- 元々の位置(top: 0, left: 0)は.box-1のコンテンツ領域の左上角になっている。
- top,leftを100%にした結果、元の位置から.box-1のコンテンツ領域の幅と高さの分だけ離れた位置に配置されている。
図で、.child-4は.child-3の後続要素であり、同じくposition: relative
を設定しています。.child-3と相対的な位置関係を維持したまま再配置されています。
上記の計算根拠をCSSの仕様書で確認してみましょう。widthに関して、10.2 Content width: the 'width' propertyによると、
The percentage is calculated with respect to the width of the generated box's containing block.
topに関して、9.3.2 Box offsets: 'top', 'right', 'bottom', 'left'によると、
The offset is a percentage of the containing block's width (for 'left' or 'right') or height (for 'top' and 'bottom').
で、position: relative
の要素に対する包含ブロックの定義を確認すると、10.1 Definition of "containing block"にあるとおり、
2.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.
例3. translate(X%, Y%)
例1で示したように、translate(X%, Y%)のX, Yは、要素自身のボーダー含む幅・高さに対する割合です。ちなみに、親要素のwidth, heightがautoでないとき、子要素のpositionとtranslateのパーセントに50%を指定することにより、親要素の縦横中央にブロック要素を配置することができます(下図)。
DEMO: https://codepen.io/kaz_hashimoto/pen/vYBWLZz
.center {
width: 200px;
height: 80px;
position: absolute;
top: 50%; /* 包含ブロックのパディング領域の縦中央*/
left: 50%; /* (同)横中央*/
transform: translate(-50%, -50%); /* 要素自身の幅・高さの半分だけ位置をずらす */
}
transformプロパティのパーセント値についてCSSの仕様書で確認してみましょう。4. The transform Propertyによると、Percentagesは
refer to the size of reference box
reference boxとは6. Transform reference box: the transform-box propertyによると、transform-boxプロパティの値がたとえばborder-box
の場合、
Uses the border box as reference box.
ブラウザの開発ツールからtransform-boxの初期値を調べると、border-box (Firefox, Safari), view-box (Chrome, Opera)になっていました。仕様書の上記説明の補足に以下の記載があるので、Chrome/Operaの動作もborder-boxとみなしてよいのかも?
For elements with associated CSS layout box, the used value for fill-box is content-box and for stroke-box and view-box is border-box.
例4. padding-top: 100%
padding-topにパーセント値を指定した場合は、包含ブロックのコンテンツ領域の幅に対する割合になります(下図)。幅が可変の親要素の中に、縦横比が一定の領域を確保してブロック要素を配置したいときなどにこのテクニックが役立ちます。(例:width: 100%; padding-top: 75%;
)
<div class="box-2">
<div class="child-5"><p>padding-top<br>100%</p></div>
</div>
CSS (要点のみ)
.box-2 {
width: 200px;
height: 100px;
padding: 10px 70px;
border: 16px solid rgba(0,0,0,0.3);
background-color: #B3E5FC;
}
.child-5 {
box-sizing: border-box;
padding-top: 100%;
width: 50%;
line-height: 1.4;
background-color: #FF5722;
border: 1px solid #000;
}