CSSのshape()関数で、曲線を含む形に切り抜く方法
clip-path に shape() 関数を書くと、波やアーチのような曲線を含む形に要素を切り抜けます。座標は % や calc() で書けるので、要素の幅が変わっても形が一緒に伸び縮みします。SVG のパスの文字列を書く path() では、座標が px に固定されるため、これができません。
検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、スクリーンショットの画素と getComputedStyle() の値で形を確かめました。Firefox と Safari、スマートフォンの実機での表示は試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。
shape() の書き方
shape() には、最初に from で書き始めの点を書き、そのあとに線を引く命令をカンマで区切って並べます。座標は「横 縦」の順で、要素の左上が 0 0 です。% は要素の幅と高さに対する割合になります。
.triangle {
clip-path: shape(from 0 0, line to 100% 0, line to 50% 100%, close);
}
この例では、左上から右上へ線を引き、下の辺の真ん中へ線を引いてから、close で書き始めの点へ戻ります。上の辺の両端と、下の辺の真ん中を結んだ三角形に切り抜かれます。shape() は clip-path のほか、要素を線に沿って動かす offset-path にも書けます。
使える命令は次のとおりです。どの命令も、前の命令が終わった点から始まります。
| 命令 | 引く線 |
|---|---|
line to x y |
その点まで直線 |
hline to x、vline to y |
横だけ、縦だけの直線 |
curve to x y with 制御点 |
ベジェ曲線 |
smooth to x y |
前の曲線になめらかにつながるベジェ曲線 |
arc to x y of 半径 |
円や楕円の一部(円弧) |
move to x y |
線を引かずに点を動かす |
close |
書き始めの点まで直線で戻る |
to を by に変えると、終わりの点を「今いる点からどれだけ動くか」で書けます。命令のあいだのカンマは省けません。検証環境で、from 0 0 のあとのカンマを抜いた値を CSS.supports() で調べると、false が返りました。
curve と smooth で、下の辺を波にする
見出しの下の辺を波の形にしてみます。
.hero {
clip-path: shape(
from 0 0,
hline to 100%,
vline to calc(100% - 30px),
curve to 50% calc(100% - 30px) with 75% 100%,
smooth to 0 calc(100% - 30px),
close
);
}

左上から右上へ横線を引き、右の辺を下から 30px のところまで下ろします。そこから curve で、下の辺の真ん中まで曲線を引きます。with のあとの 75% 100% は、曲線の形を決める制御点です。曲線は制御点を通りませんが、制御点のほうへ引っぱられて、下にふくらみます。
with のあとに制御点を1つ書くと2次ベジェ曲線、/ で区切って2つ書くと3次ベジェ曲線になります。
続く smooth は、制御点を自分で書かなくても、前の曲線となめらかにつながる曲線を引きます。CSS Shapes の仕様には、前の曲線の制御点を、今いる点を中心に反対側へ折り返した点を使うと書かれています。この例では、75% 100% を下の辺の真ん中で折り返した 25% calc(100% - 60px) です。そのため、右半分の谷と同じ大きさの山が、左半分にできます。

検証環境で、600×200px の要素に付けて下の辺の位置を調べました。両端と真ん中は上から 169px、右側の谷は 184px、左側の山は 154px でした。smooth の代わりに curve to 0 calc(100% - 30px) with 25% calc(100% - 60px) と書いた形とも比べると、9か所で調べた下の辺の位置がすべて同じでした。
高さを 120px に変えても、波の深さは変わりません。両端は 89px、谷は 104px、山は 74px で、上下に 15px ずつの波のままでした。calc(100% - 30px) で、下の辺から 30px の帯の中に波を描いているためです。
arc で、上の辺をアーチにする
円の一部を描くときは arc を使います。of のあとに半径を書きます。
.arch {
width: 200px;
height: 300px;
clip-path: shape(
from 0 100%,
vline to 100px,
arc to 100% 100px of 100px cw,
vline to 100%,
close
);
}
左下から、左の辺に沿って上から 100px の位置まで線を引き、右の辺の同じ位置まで、半径 100px の円弧を引きます。検証環境では、上の辺の位置は、真ん中の列で 0px、左右の端から 15px の列で 47px でした。上が丸いアーチになります。
円弧の向きは cw(時計回り)か ccw(反時計回り)で指定します。省略したときは ccw です。上の例で cw を ccw に変えたり省いたりすると、円弧が下にふくらみ、真ん中の列は上から 200px までが空白になりました。

半径が小さすぎて2つの点を結べないときは、結べる大きさまで半径が広がります。MDN の shape() のページにも、この動きが書かれています。400×100px の要素で、横に 200px 離れた2点を半径 60px の円弧で結ぶと、円弧の深さは 99px でした。半径 100px の半円と同じ深さです。
半径を % 1つで書くときの注意
of 50% のように半径を % で1つだけ書くと、要素の幅に対する割合にはなりません。CSS Shapes の仕様では、circle() と同じく、対角線の長さを √2 で割った大きさ(sqrt(幅² + 高さ²) / sqrt(2))に対する割合になります。
400×100px の要素では、この大きさは約 291.55px で、その 50% は約 145.77px です。この半径で、横に 200px 離れた2点を結ぶと、円弧の深さは計算上 39.7px になります。検証環境で測ると 39px でした。半径が幅の 50%(200px)なら、深さは計算上 26.8px で、測った値とは合いません。
横と縦の半径を of 50% 50% のように2つ書くと、横は幅の 50%、縦は高さの 50% になります。同じ2点を結んだ円弧の深さは 6px と浅くなりました。
path() との違いは、大きさに合わせて伸び縮みすること
曲線を含む形は、これまでも path() で書けました。path() は SVG の d 属性と同じ文字列を書く関数で、座標は px です。
/* 幅 300px・高さ 150px の要素に合わせて書いた波 */
.wave-path {
clip-path: path("M 0 0 H 300 V 120 Q 225 150 150 120 T 0 120 Z");
}
検証環境で、この path() と、上の節の shape() の波を、同じ大きさの要素に付けて比べました。幅 300px・高さ 150px の要素では、5か所で調べた下の辺の位置がすべて同じでした。
幅を 500px にすると、違いが出ました。shape() の波は幅いっぱいに広がりましたが、path() の形は幅 300px 用の大きさのままで、左から 375px と 499px の列には何も表示されませんでした。

MDN では、shape() の利点として、ふだんの CSS の書き方で書けることを挙げています。%、rem、em などの単位や、calc()、max()、abs() などの数学関数も使えます。path() は1つの文字列で、単位は px に限られます。幅の決まっていない要素を曲線で切り抜くなら、shape() が向いています。
形をアニメーションさせる条件
shape() の形は、transition で少しずつ変えられます。次の例では、.on が付くと、まっすぐだった下の辺が下にふくらみます。
.box {
transition: clip-path 1s linear;
clip-path: shape(from 0 0, hline to 100%, vline to 70%,
curve to 0 70% with 50% 70%, close);
}
.box.on {
clip-path: shape(from 0 0, hline to 100%, vline to 70%,
curve to 0 70% with 50% 100%, close);
}
検証環境で、.on を付けたあと、アニメーションを 500ms の位置で止めて getComputedStyle() で読むと、制御点は 50% 85% でした。70% と 100% のちょうど真ん中です。
途中の形が計算されるには、2つの形の命令の数と種類、並びがそろっている必要があります。CSS Shapes の仕様の補間の節では、to と by の違いや、curve と smooth の制御点の数もそろえることになっています。検証環境では、三角形(line が2つ)と四角形(line が3つ)のあいだや、line to と line by が入れ替わった形のあいだでは、途中の値が作られず、50% の時点ですでに終わりの形になっていました。
path() と shape() のあいだでも、命令がそろっていれば途中の形が作られます。検証環境では、途中の値は shape() の形で返り、px と % を合わせた calc(50% + 100px) のような座標になりました。
対応しているブラウザー
互換性データでは、shape() は次のバージョンから対応しています。
| ブラウザー | 対応 |
|---|---|
| Chrome・Edge | 135 から |
| Chrome(Android) | 135 から |
| Firefox | 148 から |
| Safari | 18.4 から |
| Safari(iOS) | 18.4 から |
対応していないブラウザー向けに別の形を用意するときは、@supports で分けます。
.hero {
clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%); /* 直線だけの形 */
}
@supports (clip-path: shape(from 0 0, line to 100% 0)) {
.hero {
clip-path: shape(
from 0 0,
hline to 100%,
vline to calc(100% - 30px),
curve to 50% calc(100% - 30px) with 75% 100%,
smooth to 0 calc(100% - 30px),
close
);
}
}
検証環境の Chromium 153 では、@supports の中の shape() の指定が使われました。





