CSSのボックスモデルと単位
幅640pxの文書に固定幅の親と箱を置き、box-sizingと単位を変えながら計算済みの値・clientWidth・境界の幅を測ります。dimensionsはgetComputedStyleとgetBoundingClientRectから測定値を取得する関数です。数値はCSS pxで扱います。測定表示は小数2桁へ丸めます。表示倍率と画素への丸めにより、指定値からの単純計算と測定値には小さな差が出る場合があります。
説明
CSSのwidthに書いた値が、そのまま枠全体の幅になるとは限りません。content-boxではpaddingとborderが外側へ加わり、border-boxでは指定幅の内側に含まれます。%・em・rem・vwは、それぞれ参照する寸法も異なります。
幅640pxの文書に固定幅の親と箱を置き、box-sizingと単位を変えながら計算済みの値・clientWidth・境界の幅を測ります。dimensionsはgetComputedStyleとgetBoundingClientRectから測定値を取得する関数です。数値はCSS pxで扱います。測定表示は小数2桁へ丸めます。表示倍率と画素への丸めにより、指定値からの単純計算と測定値には小さな差が出る場合があります。
1content-boxの外側へ加わる幅
内容幅200px、左右padding各20px、border各5px、margin各10pxの箱を作ります。
(globalThis.doc=createFormDocument('<!doctype html><html lang="ja"><head><style>html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px内容・内側余白・枠線 solid #80c4d6;margin:10px;box-sizing:content-boxwidthは内容領域の幅;background:#22434b}</style></head><body><div id="container"><div id="box">内容</div></div></body></html>'),globalThis.box=doc.getElementById("box"),globalThis.containerEl=doc.getElementById("container"),dimensions("#box")実際の各寸法)指定値だけを足すと200+20×2+5×2=250pxです。実測の境界幅は約249.32px、片側のborderは約4.66pxになっています。borderにはデバイス画素へ合わせる丸めがあり、CSS pxの指定値と一致しない場合があります。clientWidthはborderを除いた幅を整数で返すため240です。marginは境界の外側にあり、rectWidthには含まれません。
2border-boxへ変更
同じwidthを保ち、box-sizingだけを変えます。
(box.style.boxSizing="border-box"指定幅にpaddingとborderを含める,dimensions("#box"))境界の幅は約200pxです。paddingと実測borderを引いた内容幅は約150.67pxで、clientWidthはborderを除いた幅を整数にした191です。border-boxではpaddingとborderを指定幅の内側へ含めます。指定した5pxのborderだけで計算した内容幅150pxとの差は、直前に見たborderの丸めに対応します。
3親の幅に対する割合
箱の幅を50%にします。
(box.style.width="50%"割合で幅を指定,{parentWidth:round2(containerEl.getBoundingClientRect().width基準となる親の幅),box:dimensions("#box")})親の幅400pxに対して50%なので、箱は約200pxです。この通常フローのブロックでは、割合幅の基準は親の内容領域の幅です。
4親の幅だけを変える
親を300pxへ縮めます。
(containerEl.style.width="300px"割合の基準を変更,dimensions("#box"))箱の幅は約150pxへ変わります。50%という指定は同じでも、基準となる親の幅が変われば算出される値も変わります。
5emは要素の文字サイズに関わる
箱の文字サイズを1.5em、paddingを1emにします。
(box.style.fontSize="1.5em"親の文字サイズを基準にする,box.style.padding="1em"箱自身の文字サイズを基準にする,{font:styleOf("#box","font-size"),padding:styleOf("#box","padding-left"),width:dimensions("#box").rectWidth})font-sizeの1.5emは親の16pxを基準に24pxになります。paddingの1emは箱自身の文字サイズ24pxです。emが使われるプロパティによって、どの文字サイズを参照するかを確認します。
6remはルートの文字サイズが基準
幅を10remにします。
(box.style.width="10rem"ルートの文字サイズを基準にする,{rootFont:doc.defaultView.getComputedStyle(doc.documentElementhtml要素).fontSize,width:dimensions("#box").rectWidth,padding:styleOf("#box","padding-left")})ルートの20px×10で幅は約200pxになります。paddingはemのままなので24pxです。同じ相対単位でも、remとemは参照先が異なります。
7ルートの文字サイズを変更
htmlの文字サイズだけを10pxにします。
(doc.documentElement.style.fontSize="10px"remの基準を変更,{width:dimensions("#box").rectWidth,font:styleOf("#box","font-size"),padding:em側の寸法は維持styleOf("#box","padding-left")})幅は約100pxへ変わります。一方、bodyに16pxを明示し、その子から計算した箱の文字サイズ24pxとpadding24pxは変わりません。ルートの変更がどの指定へ波及するかは、参照関係で決まります。
8vwは表示領域の幅が基準
箱の幅を25vwにします。
(box.style.width="25vw"表示領域の幅の25%,{viewport:doc.defaultView.innerWidth文書の表示領域,width:dimensions("#box").rectWidth})この文書の表示領域は640pxなので25vwは計算上160pxです。実測は159.98pxで、レイアウト計算の精度による小さな差もあります。親の300pxではなく、表示領域の幅を参照しています。教材のプレビューも640px幅に固定して、表示とこの測定条件を一致させています。
9calcで割合から固定幅を引く
箱の幅をcalc(100% - 40px)にします。
(box.style.width="calc(100% - 40px)親の幅から固定値を引く",dimensions("#box"))親の300pxから40pxを引き、幅は約260pxです。calcは異なる単位を含む計算をCSS側で行います。ここではbox-sizingがborder-boxのため、算出した260pxにpaddingとborderが含まれます。
10clampで上下限を設ける
幅をclamp(120px, 50%, 200px)として、親を300・100・500pxに変えます。
(box.style.width="clamp(120px, 50%, 200px)下限・希望値・上限",globalThis.sizes=[300,100,500]親の幅を順に変更.map(w=>(containerEl.style.width=w+"px",{parent:w,width:round2(box.getBoundingClientRect().width)})),sizes)計算上は親300pxで150px、100pxで下限120px、500pxで上限200pxです。実測にもほぼ同じ値が現れ、下限側の119.99pxなど小さな丸め差があります。下限を持つため、親が100pxのときは親より箱が大きくなります。上下限の指定が必ずあふれを解消するとは限りません。
11縦方向のpaddingに使う割合
親幅を400px、高さを200pxにし、箱のpadding-topを10%にします。
(containerEl.style.width="400px"割合paddingの基準幅,containerEl.style.height="200px"親の高さ,box.style.width="100px",box.style.padding="0",box.style.paddingTop="10%"上側の割合padding,{parentWidth:round2(containerEl.getBoundingClientRect().width),parentHeight:round2(containerEl.getBoundingClientRect().height),paddingTop:styleOf("#box","padding-top")})padding-topは20pxではなく約40pxです。この横書きの通常ブロックでは、上下を含むpaddingの割合は包含ブロックの幅を基準にします。縦方向の余白だから親の高さの10%になる、とは限りません。
まとめ
content-boxとborder-boxでwidthに含む領域が異なり、marginは境界の外側です。
%、em、rem、vwが何を基準にするかを確かめ、条件変更後の寸法を測ります。
calcやclampにも基準と上下限があり、割合paddingは方向だけから基準を推測できません。
操作履歴と解説を振り返れます。
ブラウザ / HTML・DOM
(globalThis.doc=createFormDocument('<!doctype html><html lang="ja"><head><style>html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}</style></head><body><div id="container"><div id="box">内容</div></div></body></html>'),globalThis.box=doc.getElementById("box"),globalThis.containerEl=doc.getElementById("container"),dimensions("#box")){
"width": "200px",
"padding": "20px",
"border": "4.66px",
"margin": "10px",
"boxSizing": "content-box",
"rectWidth": 249.32,
"clientWidth": 240
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container"><div id="box">内容</div></div></body></html>(box.style.boxSizing="border-box",dimensions("#box")){
"width": "200px",
"padding": "20px",
"border": "4.66px",
"margin": "10px",
"boxSizing": "border-box",
"rectWidth": 200,
"clientWidth": 191
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container"><div id="box" style="box-sizing: border-box;">内容</div></div></body></html>(box.style.width="50%",{parentWidth:round2(containerEl.getBoundingClientRect().width),box:dimensions("#box")}){
"parentWidth": 400,
"box": {
"width": "200px",
"padding": "20px",
"border": "4.66px",
"margin": "10px",
"boxSizing": "border-box",
"rectWidth": 200,
"clientWidth": 191
}
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container"><div id="box" style="box-sizing: border-box; width: 50%;">内容</div></div></body></html>(containerEl.style.width="300px",dimensions("#box")){
"width": "150px",
"padding": "20px",
"border": "4.66px",
"margin": "10px",
"boxSizing": "border-box",
"rectWidth": 150,
"clientWidth": 141
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: 50%;">内容</div></div></body></html>(box.style.fontSize="1.5em",box.style.padding="1em",{font:styleOf("#box","font-size"),padding:styleOf("#box","padding-left"),width:dimensions("#box").rectWidth}){
"font": "24px",
"padding": "24px",
"width": 150
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: 50%; font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(box.style.width="10rem",{rootFont:doc.defaultView.getComputedStyle(doc.documentElement).fontSize,width:dimensions("#box").rectWidth,padding:styleOf("#box","padding-left")}){
"rootFont": "20px",
"width": 200,
"padding": "24px"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: 10rem; font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(doc.documentElement.style.fontSize="10px",{width:dimensions("#box").rectWidth,font:styleOf("#box","font-size"),padding:styleOf("#box","padding-left")}){
"width": 100,
"font": "24px",
"padding": "24px"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{font-size: 10px;}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: 10rem; font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(box.style.width="25vw",{viewport:doc.defaultView.innerWidth,width:dimensions("#box").rectWidth}){
"viewport": 640,
"width": 159.98
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{font-size: 10px;}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: 25vw; font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(box.style.width="calc(100% - 40px)",dimensions("#box")){
"width": "259.99px",
"padding": "24px",
"border": "4.66px",
"margin": "10px",
"boxSizing": "border-box",
"rectWidth": 259.99,
"clientWidth": 251
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{font-size: 10px;}</style></head><body inert><div id="container" style="width: 300px;"><div id="box" style="box-sizing: border-box; width: calc(100% - 40px); font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(box.style.width="clamp(120px, 50%, 200px)",globalThis.sizes=[300,100,500].map(w=>(containerEl.style.width=w+"px",{parent:w,width:round2(box.getBoundingClientRect().width)})),sizes)[
{
"parent": 300,
"width": 150
},
{
"parent": 100,
"width": 119.99
},
{
"parent": 500,
"width": 200
}
]<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{font-size: 10px;}</style></head><body inert><div id="container" style="width: 500px;"><div id="box" style="box-sizing: border-box; width: clamp(120px, 50%, 200px); font-size: 1.5em; padding: 1em;">内容</div></div></body></html>(containerEl.style.width="400px",containerEl.style.height="200px",box.style.width="100px",box.style.padding="0",box.style.paddingTop="10%",{parentWidth:round2(containerEl.getBoundingClientRect().width),parentHeight:round2(containerEl.getBoundingClientRect().height),paddingTop:styleOf("#box","padding-top")}){
"parentWidth": 400,
"parentHeight": 200,
"paddingTop": "40px"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}html{font-size:20px}body{font:16px/1.5 system-ui;margin:16px;background:#111;color:#eee}#container{width:400px;background:#202020}#box{width:200px;padding:20px;border:5px solid #80c4d6;margin:10px;box-sizing:content-box;background:#22434b}
:root{font-size: 10px;}</style></head><body inert><div id="container" style="width: 400px; height: 200px;"><div id="box" style="box-sizing: border-box; width: 100px; font-size: 1.5em; padding: 10% 0px 0px;">内容</div></div></body></html>演習完了