WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

CSSで波アニメーションを作る方法|波形区切り・SVGウェーブ

生徒

ヒーローセクションの下に波形の区切りを入れたいんですが、画像で用意するしかないですか?

ペン博士

CSSとSVGで作れるよ!clip-pathやSVGパスを使えば、拡大にも強い波形が作れる。動かすアニメーションも今日は全部見せるね。

Webサイトのヒーローセクション下部に波形の区切りを入れたい、水面のようにゆらゆら動く背景を作りたい——そんな表現をCSSだけで実現できます。Illustratorやアニメーションツールは不要です。

波アニメーションはアプローチが複数あり、それぞれ得意な表現が異なります。border-radiusを変形させる方法・SVGパスを横スクロールさせる方法・clip-pathで波形を切り抜く方法を使い分けることで、あらゆる場面に対応できます。

この記事ではそれぞれの手法を基本から応用まで丁寧に解説し、コピペして即使えるコードを豊富に提供します。失敗しやすいポイントとその対処法も合わせて紹介します。

  • border-radiusを@keyframesで変形させる水面風アニメーションの作り方
  • SVGパスの波をtransform:translateXで横スクロールさせる手法
  • clip-pathを使った波形セクション区切りの実装
  • グラデーションと波を重ね合わせたリッチな背景表現
  • ヒーローセクション下部の波仕切りの実装手順
  • パフォーマンスを損なわない実装のポイント

目次

波アニメーションの3つのアプローチ

波の作り方3種

CSSで波を表現する主要な手法は3つあります。それぞれの特徴を理解してから実装に進みましょう。

手法 メリット デメリット 向いている用途
border-radius変形 HTMLのみで完結、軽量 リアルな波形は難しい 水面・ふわふわした背景
SVGパス横スクロール 精密な波形、滑らか SVGのパスデータが必要 セクション区切り、ヒーロー下部
clip-path 要素を波形に切り抜ける ブラウザ対応に注意 セクション背景の切り抜き

パフォーマンスの観点では、いずれの手法もtransformとopacityプロパティを使うことでGPUアクセラレーションが働き、メインスレッドへの負荷を最小化できます。widthやheightを@keyframesでアニメーションさせると再レイアウトが発生するので避けましょう。


border-radiusを使った水面風アニメーション

最もシンプルな波表現が、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角」という意味です。スラッシュ前後で水平・垂直の半径を独立して制御できるため、複雑な有機的形状が作れます。

応用:複数のblobを重ねて水面らしさを出す

複数の半透明な要素を重ね、アニメーションの位相をずらすことで奥行きのある水面表現になります。

<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パスで作る精密な波アニメーション

SVG波の手順

より精密でリアルな波形を作る場合、SVGのpathを使います。SVGの波をtransform:translateXで横方向にスクロールさせることで、無限に続く波の動きを表現できます。

基本構造:SVG波の仕組みを理解する

横スクロール波のコツは、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属性の値を変えて波の高さをずらすのがポイントです。

波形のパスをカスタマイズする

波の形状はpathd属性で決まります。よく使う波形のパターンをいくつか示します。

<!-- なだらかな波(サインカーブに近い) -->
<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で作る波形セクション区切り

区切りの入れ方

clip-pathを使うと、矩形のdivを波形に切り抜くことができます。SVGを別途用意する必要がなく、CSSだけで波形セクションが作れます。

基本:clip-pathで波形に切り抜く

<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%;
}

より滑らかな波形:ellipse関数を使う

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アニメーション

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アニメーションを維持できます。

will-changeとGPUアクセラレーション

/* 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); }
}

prefers-reduced-motionへの対応

前庭障害などを持つユーザーに配慮し、動きを減らす設定が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点。用途に合わせて、静的な区切りか動く演出かを選んでください。

よくある質問(FAQ)

CSSの波アニメーションはSafariでも動きますか?

transform:translateXを使ったSVG横スクロール手法とborder-radiusモーフィングはSafariでも問題なく動作します。ただしCSSのpath()モーフィング(dプロパティのアニメーション)はSafariのサポートが限定的なため、クロスブラウザ対応が必要な場合はSVGのtranslatXを使う手法を選んでください。

波アニメーションでパフォーマンスが悪化した場合はどうすればよいですか?

まずブラウザのDevToolsでPerformanceパネルを確認し、どのフレームで処理が重くなっているか特定します。対策は主に3つです。①アニメーションをtransformとopacityのみに限定する ②will-change: transformを問題の要素に付与してGPUレイヤーに昇格させる ③波のレイヤー数を減らして描画コストを下げる、です。

セクションの波形区切りでSVGとclip-pathどちらを使うべきですか?

レスポンシブ対応の容易さから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・Webデザイン実装の総まとめ

CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

AIスキルで、未来の自分をアップデート!

今なら完全無料でAIを学べる!WithAI

今なら完全無料でAIを学べる!

  • 動画や実践で楽しく学べる:初心者でも安心のカリキュラム
  • スマホ・PCどちらでもOK:好きな時間に学習できる
  • 料金は一切ナシ0円でAIスキルが身につく

目的に合わせて選べる「AI副業」「AI転職」「AI活用」の3コースを用意。副収入・キャリアチェンジ・日常の生産性アップまで、あなたのゴールに合わせてAIを学べます。

会員登録はカンタン30秒で完了します。まずは公式LINEから、無料でAI学習をスタートしましょう!

この記事を書いた人

WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次