



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp









生徒ヒーローセクションの下に波形の区切りを入れたいんですが、画像で用意するしかないですか?
ペン博士CSSとSVGで作れるよ!clip-pathやSVGパスを使えば、拡大にも強い波形が作れる。動かすアニメーションも今日は全部見せるね。
Webサイトのヒーローセクション下部に波形の区切りを入れたい、水面のようにゆらゆら動く背景を作りたい——そんな表現をCSSだけで実現できます。Illustratorやアニメーションツールは不要です。
波アニメーションはアプローチが複数あり、それぞれ得意な表現が異なります。border-radiusを変形させる方法・SVGパスを横スクロールさせる方法・clip-pathで波形を切り抜く方法を使い分けることで、あらゆる場面に対応できます。
この記事ではそれぞれの手法を基本から応用まで丁寧に解説し、コピペして即使えるコードを豊富に提供します。失敗しやすいポイントとその対処法も合わせて紹介します。

CSSで波を表現する主要な手法は3つあります。それぞれの特徴を理解してから実装に進みましょう。
| 手法 | メリット | デメリット | 向いている用途 |
|---|---|---|---|
| border-radius変形 | HTMLのみで完結、軽量 | リアルな波形は難しい | 水面・ふわふわした背景 |
| SVGパス横スクロール | 精密な波形、滑らか | SVGのパスデータが必要 | セクション区切り、ヒーロー下部 |
| clip-path | 要素を波形に切り抜ける | ブラウザ対応に注意 | セクション背景の切り抜き |
パフォーマンスの観点では、いずれの手法もtransformとopacityプロパティを使うことでGPUアクセラレーションが働き、メインスレッドへの負荷を最小化できます。widthやheightを@keyframesでアニメーションさせると再レイアウトが発生するので避けましょう。
最もシンプルな波表現が、border-radiusを@keyframesで変化させる手法です。円や楕円の形状をゆっくり変形させることで、水面のようなゆらぎを演出できます。
まずHTML構造です。div一つで完結します。
<div class="wave-blob"></div>
CSSです。border-radiusには8つの値を指定でき、これを@keyframesで変化させます。
.wave-blob {
width: 300px;
height: 300px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
animation: morphWave 6s ease-in-out infinite;
}
@keyframes morphWave {
0% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
}
25% {
border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%;
}
50% {
border-radius: 50% 60% 30% 60% / 40% 50% 60% 50%;
}
75% {
border-radius: 40% 60% 50% 40% / 70% 30% 50% 40%;
}
100% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
}
}
border-radiusの8値指定は「水平方向の4角 / 垂直方向の4角」という意味です。スラッシュ前後で水平・垂直の半径を独立して制御できるため、複雑な有機的形状が作れます。
複数の半透明な要素を重ね、アニメーションの位相をずらすことで奥行きのある水面表現になります。
<div class="water-surface">
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
</div>
.water-surface {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
background: #0a1628;
}
.blob {
position: absolute;
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
animation: morphWave 6s ease-in-out infinite;
mix-blend-mode: screen;
}
.blob-1 {
width: 600px;
height: 600px;
top: -150px;
left: -100px;
background: rgba(102, 126, 234, 0.4);
animation-duration: 7s;
}
.blob-2 {
width: 500px;
height: 500px;
top: -100px;
left: 30%;
background: rgba(118, 75, 162, 0.35);
animation-duration: 9s;
animation-delay: -2s;
}
.blob-3 {
width: 450px;
height: 450px;
top: -80px;
right: -50px;
background: rgba(52, 152, 219, 0.3);
animation-duration: 11s;
animation-delay: -4s;
}
@keyframes morphWave {
0%, 100% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
transform: translate(0, 0) rotate(0deg);
}
25% {
border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%;
transform: translate(10px, -20px) rotate(5deg);
}
50% {
border-radius: 50% 60% 30% 60% / 40% 50% 60% 50%;
transform: translate(-5px, 10px) rotate(-3deg);
}
75% {
border-radius: 40% 60% 50% 40% / 70% 30% 50% 40%;
transform: translate(15px, 5px) rotate(7deg);
}
}
mix-blend-mode: screenを使うと、重なった部分が明るくなり光が反射しているような質感になります。背景が暗い場合に特に効果的です。
クリックやホバーで広がる波紋エフェクトも同じ手法で作れます。
<div class="ripple-container">
<div class="ripple"></div>
<div class="ripple" style="animation-delay: 0.5s"></div>
<div class="ripple" style="animation-delay: 1s"></div>
</div>
.ripple-container {
position: relative;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.ripple {
position: absolute;
width: 100px;
height: 100px;
border: 2px solid rgba(102, 126, 234, 0.8);
border-radius: 50%;
animation: rippleOut 2s ease-out infinite;
}
@keyframes rippleOut {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0;
}
}

より精密でリアルな波形を作る場合、SVGのpathを使います。SVGの波をtransform:translateXで横方向にスクロールさせることで、無限に続く波の動きを表現できます。
横スクロール波のコツは、SVGの幅を200%にして波形を2つ並べ、左に100%スクロールして元の位置に戻すことでシームレスなループを作ることです。
<div class="wave-wrapper">
<svg class="wave-svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 24 150 28" preserveAspectRatio="none">
<defs>
<path id="gentle-wave"
d="M-160 44c30 0 58-18 88-18s58 18 88 18 58-18 88-18 58 18 88 18
v44h-352z"/>
</defs>
<g class="wave-parallax">
<use href="#gentle-wave" x="48" y="0" fill="rgba(255,255,255,0.7)"/>
<use href="#gentle-wave" x="48" y="3" fill="rgba(255,255,255,0.5)"/>
<use href="#gentle-wave" x="48" y="5" fill="rgba(255,255,255,0.3)"/>
<use href="#gentle-wave" x="48" y="7" fill="rgba(255,255,255,1)"/>
</g>
</svg>
</div>
.wave-wrapper {
position: relative;
width: 100%;
overflow: hidden;
line-height: 0;
}
.wave-svg {
display: block;
width: calc(100% + 1.3px);
height: 80px;
}
.wave-parallax > use {
animation: waveMove 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
}
.wave-parallax > use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
}
.wave-parallax > use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
}
.wave-parallax > use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
}
.wave-parallax > use:nth-child(4) {
animation-delay: -5s;
animation-duration: 20s;
}
@keyframes waveMove {
0% {
transform: translate3d(-90px, 0, 0);
}
100% {
transform: translate3d(85px, 0, 0);
}
}
4つの<use>要素に異なるopacityとアニメーション速度を与えることで、波が重なり合う立体感が生まれます。それぞれのy属性の値を変えて波の高さをずらすのがポイントです。
波の形状はpathのd属性で決まります。よく使う波形のパターンをいくつか示します。
<!-- なだらかな波(サインカーブに近い) -->
<path d="M0,32 C30,50 70,10 100,32 C130,54 170,14 200,32 L200,64 L0,64 Z"/>
<!-- 急な波(波が高い) -->
<path d="M0,40 C20,60 40,20 60,40 C80,60 100,20 120,40
C140,60 160,20 180,40 C190,50 200,40 200,40
L200,64 L0,64 Z"/>
<!-- 非対称な波(左右で形が違う) -->
<path d="M0,20 C60,60 140,0 200,30 L200,64 L0,64 Z"/>
実際のWebサイトで最も多い用途が、セクション同士の境界に波を配置するパターンです。完全な実装コードを示します。
<!-- ヒーローセクション -->
<section class="hero">
<div class="hero-content">
<h1>ページタイトル</h1>
<p>サブコピーテキスト</p>
</div>
<!-- 波形の仕切り -->
<div class="wave-divider">
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1440 100" preserveAspectRatio="none">
<path fill="#ffffff"
d="M0,60 C240,100 480,20 720,60
C960,100 1200,20 1440,60 L1440,100 L0,100 Z"
class="wave-path"/>
</svg>
</div>
</section>
<!-- 次のセクション(白背景) -->
<section class="next-section">
<p>コンテンツ</p>
</section>
.hero {
position: relative;
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
/* 下部に波が重なるスペースを確保 */
padding-bottom: 100px;
}
.hero-content {
text-align: center;
z-index: 1;
}
.wave-divider {
position: absolute;
bottom: -1px; /* 隙間ができないよう1px下にはみ出す */
left: 0;
width: 100%;
overflow: hidden;
line-height: 0;
}
.wave-divider svg {
display: block;
width: 100%;
height: 100px;
}
/* アニメーションあり版 */
.wave-path {
animation: waveShift 8s ease-in-out infinite alternate;
transform-origin: center;
}
@keyframes waveShift {
0% {
d: path("M0,60 C240,100 480,20 720,60 C960,100 1200,20 1440,60 L1440,100 L0,100 Z");
}
100% {
d: path("M0,40 C240,20 480,80 720,40 C960,20 1200,80 1440,40 L1440,100 L0,100 Z");
}
}
.next-section {
background: #ffffff;
padding: 80px 20px;
}
注意点として、dプロパティをCSSアニメーションで変化させる手法(morphing)はChrome・Firefoxでは動作しますが、Safariではサポートが限定的です。確実に動作させたい場合はtranslateXによるスクロール手法を選ぶと安全です。

clip-pathを使うと、矩形のdivを波形に切り抜くことができます。SVGを別途用意する必要がなく、CSSだけで波形セクションが作れます。
<section class="hero-clip">
<div class="hero-clip-content">
<h1>ヒーローセクション</h1>
</div>
</section>
<section class="content-section">
<p>次のセクション</p>
</section>
.hero-clip {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
/* clip-pathで下部を波形に切り抜く */
clip-path: polygon(
0 0,
100% 0,
100% 80%,
75% 90%,
50% 80%,
25% 90%,
0 80%
);
/* clip-pathで切れた部分のスペースを確保 */
padding-bottom: 15%;
}
.hero-clip-content {
text-align: center;
}
.content-section {
background: #fff;
padding: 40px 20px;
margin-top: -10%;
}
polygonは折れ線になるため、本物の波には見えません。より滑らかな曲線を出すにはpath()関数を使います。
/* path()を使った滑らかな波形 */
.hero-smooth-wave {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
min-height: 500px;
padding-bottom: 120px;
clip-path: path(
"M0,0 L1440,0 L1440,320
C1200,380 960,280 720,320
C480,360 240,260 0,320 Z"
);
/* path()はビューポート幅固定のため、レスポンシブ時に注意 */
}
path()はピクセル値で指定するため、レスポンシブ対応が難しいという弱点があります。レスポンシブが必要な場合は後述するSVGをposition:absoluteで配置する手法の方が実用的です。
clip-pathを@keyframesでアニメーションさせることも可能です。ただし頂点の数が同じでないとアニメーションが補間されないため注意が必要です。
.wave-clip-animated {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
height: 400px;
animation: clipWave 4s ease-in-out infinite alternate;
}
@keyframes clipWave {
0% {
clip-path: polygon(
0 0, 100% 0, 100% 75%,
75% 85%, 50% 75%,
25% 85%, 0 75%
);
}
100% {
clip-path: polygon(
0 0, 100% 0, 100% 85%,
75% 75%, 50% 85%,
25% 75%, 0 85%
);
}
/* 頂点数(7点)が同じなので補間される */
}
波アニメーションにグラデーションを組み合わせると、夕焼けの海や深海のような雰囲気が出せます。ここでは複数の半透明波を重ねてリッチな背景を作る方法を解説します。
<div class="ocean-bg">
<div class="ocean-wave wave-front"></div>
<div class="ocean-wave wave-middle"></div>
<div class="ocean-wave wave-back"></div>
<div class="ocean-content">
<h2>テキストコンテンツ</h2>
</div>
</div>
.ocean-bg {
position: relative;
width: 100%;
height: 500px;
background: linear-gradient(
to bottom,
#001220 0%,
#003052 40%,
#005580 70%,
#0077b6 100%
);
overflow: hidden;
}
.ocean-wave {
position: absolute;
bottom: 0;
left: 0;
width: 200%; /* 2倍幅でシームレスループ */
height: 200px;
background-repeat: repeat-x;
}
.wave-front {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,60 C150,100 350,20 600,60 C850,100 1050,20 1200,60 L1200,120 L0,120 Z' fill='rgba(0,119,182,0.6)'/%3E%3C/Svg%3E");
background-size: 600px 200px;
animation: waveScroll 6s linear infinite;
bottom: 0;
}
.wave-middle {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,80 C200,40 400,100 600,80 C800,60 1000,100 1200,80 L1200,120 L0,120 Z' fill='rgba(0,96,150,0.5)'/%3E%3C/Svg%3E");
background-size: 800px 200px;
animation: waveScroll 10s linear infinite reverse;
bottom: 10px;
}
.wave-back {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 120' preserveAspectRatio='none'%3E%3Cpath d='M0,40 C300,80 600,10 900,40 C1050,55 1150,30 1200,40 L1200,120 L0,120 Z' fill='rgba(0,70,120,0.4)'/%3E%3C/Svg%3E");
background-size: 1000px 200px;
animation: waveScroll 14s linear infinite;
bottom: 20px;
}
@keyframes waveScroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%); /* 幅を200%にしているので-50%で1ループ */
}
}
.ocean-content {
position: relative;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #fff;
}
波の色自体をグラデーションアニメーションさせることも可能です。background-positionを移動させることで色が変化して見えます。
.gradient-wave {
width: 100%;
height: 300px;
background: linear-gradient(
270deg,
#ff6b6b, #feca57, #48dbfb, #ff9ff3, #54a0ff
);
background-size: 400% 400%;
animation: gradientShift 8s ease infinite;
clip-path: polygon(
0 20%, 10% 30%, 20% 20%, 30% 35%,
40% 20%, 50% 35%, 60% 20%, 70% 30%,
80% 20%, 90% 30%, 100% 20%, 100% 100%, 0 100%
);
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}

波アニメーションは視覚的に美しい一方、実装を誤るとパフォーマンスの問題を引き起こします。以下のルールを守ることで、スムーズな60fpsアニメーションを維持できます。
/* GPUレイヤーに昇格させてメインスレッドの負荷を下げる */
.wave-element {
will-change: transform;
/* will-changeは必要な要素だけに適用する。全要素に使うとVRAMを圧迫する */
}
/* transformとopacityのみをアニメーション対象にする */
/* NG: widthやheightをアニメーションさせると再レイアウトが発生 */
@keyframes badWave {
0% { height: 100px; } /* 再レイアウト発生 */
100% { height: 200px; }
}
/* OK: transformで代替する */
@keyframes goodWave {
0% { transform: scaleY(0.5); } /* GPUで処理 */
100% { transform: scaleY(1); }
}
前庭障害などを持つユーザーに配慮し、動きを減らす設定がONの場合はアニメーションを停止させます。
/* デフォルトはアニメーションあり */
.wave-element {
animation: morphWave 6s ease-in-out infinite;
}
/* アニメーション軽減設定がONの場合は停止 */
@media (prefers-reduced-motion: reduce) {
.wave-element {
animation: none;
}
.wave-parallax > use {
animation: none;
}
}
/* または動きを最小限に抑えたアニメーションに差し替える */
@media (prefers-reduced-motion: reduce) {
.wave-element {
animation: fadeWave 3s ease-in-out infinite alternate;
}
@keyframes fadeWave {
0% { opacity: 0.7; }
100% { opacity: 1; }
}
}
| 問題 | 原因 | 対処法 |
|---|---|---|
| 波と背景の間に隙間が見える | SVGのbottomが-1pxでない | bottom: -1pxを設定する |
| アニメーションがカクつく | transform以外のプロパティを変化させている | transformとopacityのみを使う |
| モバイルで波が切れる | SVGのviewBox設定が固定値 | preserveAspectRatio=”none”を指定する |
| clip-pathのアニメーションが動かない | before/afterで頂点数が異なる | keyframesの頂点数を揃える |
| SVG波がIEで動かない | IE非対応のCSS機能を使用 | 2025年現在IEサポートは不要な場合がほとんど |
波アニメーションは、SVGパスの横スクロール・clip-path・border-radiusのモーフィングなど複数の作り方があります。ポイントは①区切り装飾はSVGが拡大に強い②動かすときはtransformで軽く③重ねると水面らしい奥行きが出る、の3点。用途に合わせて、静的な区切りか動く演出かを選んでください。
transform:translateXを使ったSVG横スクロール手法とborder-radiusモーフィングはSafariでも問題なく動作します。ただしCSSのpath()モーフィング(dプロパティのアニメーション)はSafariのサポートが限定的なため、クロスブラウザ対応が必要な場合はSVGのtranslatXを使う手法を選んでください。
まずブラウザのDevToolsでPerformanceパネルを確認し、どのフレームで処理が重くなっているか特定します。対策は主に3つです。①アニメーションをtransformとopacityのみに限定する ②will-change: transformを問題の要素に付与してGPUレイヤーに昇格させる ③波のレイヤー数を減らして描画コストを下げる、です。
レスポンシブ対応の容易さからSVGを推奨します。SVGはviewBoxとpreserveAspectRatio=”none”の組み合わせで自動的に横幅に追従します。clip-pathのpath()はピクセル固定のためレスポンシブ対応が難しく、polygon()は直線になってしまいます。アニメーションが不要な静的な波形ならclip-pathのpolygonでも問題ありません。
SVGのfill属性またはCSSのfillプロパティに、次のセクションの背景色と同じ値を指定します。例えば次のセクションが白(#ffffff)ならfill="#ffffff"にします。次のセクションが画像背景の場合は、波の色に半透明を使いfill="rgba(255,255,255,0.9)"のように設定すると自然に馴染みます。
SVG波が崩れる主な原因は2つです。①SVGにpreserveAspectRatio=”none”を指定していない場合、縦横比が固定されて崩れます。②width: calc(100% + 1.3px)のような微調整が抜けていて横に隙間ができる場合があります。またline-height: 0をラッパーに指定しないと、SVGの下に謎の隙間が生じることがあります。この3点を確認してください。
CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

目的に合わせて選べる「AI副業」「AI転職」「AI活用」の3コースを用意。副収入・キャリアチェンジ・日常の生産性アップまで、あなたのゴールに合わせてAIを学べます。
会員登録はカンタン30秒で完了します。まずは公式LINEから、無料でAI学習をスタートしましょう!
WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。
公式サイト より
今すぐ
無料カウンセリング
を予約!