web-wave-designer について
web-wave-designerは、SVGフィルターやCSSアニメーション、レイヤー構成で水の表現を作るWeb開発者向けスキルです。海のうねりから水中の屈折までを扱い、パフォーマンス面の指針も提供します。
主な機能
- 01SVGの乱流・変位フィルター
- 02波や波紋の作成パターン
- 03水中屈折と泡の表現
- 04CSS・JavaScriptアニメーション
- 05レイヤー設計と性能調整
活用シーン
- 01海の背景や岸辺の波の制作
- 02水越しに見えるコンテンツの歪み
- 03池の波紋やリキッドガラスUIの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ curiositech/ some_claude_skills --skill web-wave-designerインストール後、Claude Code で/web-wave-designerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CHANGELOG.md.claude-plugin/plugin.jsonreferences/animation-patterns.mddisplacement-and-color.mdturbulence-deep-dive.md詳細
Web Wave Designer
SVG フィルター、CSS アニメーション、レイヤリング技法を使用して、Web アプリケーション向けのリアルで高性能な海や水の波のエフェクトを作成するエキスパートです。穏やかなさざ波からダイナミックな大波まで、水をテーマにしたビジュアルを専門とし、特に水中での光の屈折に関する知識が豊富です。
このスキルを使う場面
次の用途に使用します:
- 海の波の背景や海景
- 水中の歪み/屈折エフェクト
- 泡を伴う浜辺の波
- 池やプールの波紋アニメーション
- リキッドガラスの UI エフェクト
- 水をテーマにした読み込み状態
- 奥行きのある視差効果付きの海のレイヤー
- ゲーム向けの様式化された水や漫画風の水
- 水面の反射エフェクト
次の用途には使用しないでください:
- 3D のボリュームを持つ海のレンダリング → WebGL/Three.js/Ocean.js を使用してください
- リアルタイムの流体シミュレーション → canvas の物理エンジンを使用してください
- 動画エフェクト → 動画編集ソフトウェアを使用してください
- 動きのない単純な青いグラデーション
- 水滴の物理シミュレーション → パーティクルシステムを使用してください
重要な違い:turbulence と fractalNoise
重要:水のエフェクトには type="turbulence" を使用してください(雲のような fractalNoise は使用しません)。
| タイプ | ビジュアル | 最適な用途 |
|---|---|---|
turbulence | 連続した流れのパターン。透明な黒から始まります | 水、波、液体 |
fractalNoise | ランダムな雲状のパッチ。不透明です | 雲、煙、地形 |
<feTurbulence type="turbulence" baseFrequency="0.01 0.1" />
<feTurbulence type="fractalNoise" baseFrequency="0.01" />
水のための SVG フィルターパイプライン
基本的な水のエフェクトを作るフィルターチェーン:
Source -> feTurbulence -> feDisplacementMap -> feComponentTransfer -> feComposite
(waves) (distortion) (color/opacity) (blend)
1. feTurbulence - 波のパターン生成
<feTurbulence
type="turbulence"
baseFrequency="0.01 0.1"
numOctaves="3"
seed="42"
result="waves"
/>
baseFrequency の説明(2 つの値):
| X 周波数 | Y 周波数 | 結果 |
|---|---|---|
| 0.01 | 0.1 | 縦方向に揺らぐ、長い横波 |
| 0.005 | 0.05 | 深い海のうねり |
| 0.02 | 0.15 | 荒い水面の波 |
| 0.03 | 0.03 | 四角い波紋(池) |
比率が重要です:
- X が Y よりかなり小さい:横に引き伸ばされた波(海)
- X == Y:円形の波紋(池、プール)
- X が Y よりかなり大きい:縦方向の筋(水しぶき)
2. feDisplacementMap - 屈折エフェクト
水の背後にあるコンテンツが波打って見えるように、屈折や歪みを作り出します。
<feDisplacementMap
in="SourceGraphic"
in2="waves"
scale="20"
xChannelSelector="R"
yChannelSelector="G"
result="refracted"
/>
| Scale の値 | 効果 |
|---|---|
| 10-15 | プールの穏やかな波紋 |
| 15-25 | 標準的な水の屈折 |
| 25-40 | 強い波の歪み |
| 40+ | サイケデリック(非現実的) |
3. feComponentTransfer - 水の色
チャンネルを操作して、ノイズを水のような色に変換します。
<feComponentTransfer in="waves" result="waterColor">
<feFuncR type="linear" slope="0.3" intercept="0"/>
<feFuncG type="linear" slope="0.7" intercept="0.2"/>
<feFuncB type="linear" slope="1.2" intercept="0.3"/>
<feFuncA type="linear" slope="0.6" intercept="0.3"/>
</feComponentTransfer>
4. feGaussianBlur - コースティクス(水中の光)
<feGaussianBlur
in="waves"
stdDeviation="2"
result="caustics"
/>
5. 合成 - レイヤーの組み立て
<feFlood flood-color="#0077be" flood-opacity="0.4" result="baseWater"/>
<feBlend in="caustics" in2="baseWater" mode="screen" result="waterLayer"/>
<feComposite in="waterLayer" in2="SourceGraphic" operator="over"/>
波の種類別レシピ
海面の波
<svg width="100%" height="300">
<defs>
<filter id="oceanWaves" x="0" y="0" width="100%" height="100%">
<feTurbulence type="turbulence"
baseFrequency="0.005 0.05"
numOctaves="4"
seed="1"
result="waves">
<animate attributeName="baseFrequency"
dur="60s"
values="0.005 0.05;0.007 0.06;0.005 0.05"
repeatCount="indefinite"/>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="waves" scale="25"/>
</filter>
</defs>
<rect width="100%" height="100%" fill="url(#oceanGradient)" filter="url(#oceanWaves)"/>
</svg>
池の波紋(円形)
<filter id="pondRipples">
<feTurbulence type="turbulence"
baseFrequency="0.02 0.02"
numOctaves="2"
seed="10"
result="ripples"/>
<feDisplacementMap in="SourceGraphic" in2="ripples" scale="12"/>
</filter>
浜辺の波(砕ける波)
<filter id="shoreWaves">
<feTurbulence type="turbulence"
baseFrequency="0.008 0.12"
numOctaves="3"
seed="5"
result="waves"/>
<feGaussianBlur in="waves" stdDeviation="1.5" result="softWaves"/>
<feDisplacementMap in="SourceGraphic" in2="softWaves" scale="30"/>
<feTurbulence type="fractalNoise"
baseFrequency="0.03"
numOctaves="5"
result="foam"/>
<feColorMatrix in="foam" type="matrix"
values="1 0 0 0 0.9
1 0 0 0 0.95
1 0 0 0 1
0 0 0 0.3 0"/>
</filter>
水中の歪み(水越しに見る効果)
<filter id="underwater" x="-10%" y="-10%" width="120%" height="120%">
<feTurbulence type="turbulence"
baseFrequency="0.015 0.08"
numOctaves="3"
result="distort">
<animate attributeName="baseFrequency"
dur="8s"
values="0.015 0.08;0.018 0.09;0.015 0.08"
repeatCount="indefinite"/>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="distort"
scale="20"
xChannelSelector="R"
yChannelSelector="B"/>
<feColorMatrix type="matrix"
values="0.9 0 0 0 0
0 0.95 0 0 0.02
0 0 1.1 0 0.05
0 0 0 1 0"/>
</filter>
リキッドガラスエフェクト(モダンな UI)
<filter id="liquidGlass" x="-5%" y="-5%" width="110%" height="110%">
<feTurbulence type="turbulence"
baseFrequency="0.01 0.05"
numOctaves="2"
seed="99"
result="ripple">
<animate attributeName="seed"
dur="4s"
values="99;100;101;100;99"
repeatCount="indefinite"/>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="ripple" scale="8"/>
<feGaussianBlur stdDeviation="0.5"/>
</filter>
様式化された波/漫画風の波
<filter id="cartoonWater">
<feTurbulence type="turbulence"
baseFrequency="0.02 0.08"
numOctaves="1"
result="waves"/>
<feDisplacementMap in="SourceGraphic" in2="waves" scale="15"/>
</filter>
アニメーション技法
JavaScript requestAnimationFrame(最も滑らか)
const turbulence = document.querySelector('#seaFilter feTurbulence');
let frame = 0;
function animateWaves() {
frame += 0.003;
// Gentle breathing motion
const xFreq = 0.006 + Math.sin(frame) * 0.002;
const yFreq = 0.05 + Math.sin(frame * 0.7) * 0.01;
turbulence.setAttribute('baseFrequency', `${xFreq} ${yFreq}`);
requestAnimationFrame(animateWaves);
}
animateWaves();
SVG animate(宣言的、CPU 負荷が高い)
<feTurbulence baseFrequency="0.01 0.1" numOctaves="3">
<animate
attributeName="baseFrequency"
dur="60s"
keyTimes="0;0.5;1"
values="0.008 0.08;0.012 0.12;0.008 0.08"
repeatCount="indefinite"
/>
</feTurbulence>
警告:フィルター属性に対する SVG animate は、毎フレーム、フィルター全体を再計算することになります。使用は控えめにしてください。
CSS Transform アニメーション(最高のパフォーマンス)
フィルターではなく、水の要素を動かします:
.wave-layer {
animation: wave-drift 20s linear infinite;
}
@keyframes wave-drift {
from { transform: translateX(0) translateY(0); }
to { transform: translateX(-50%) translateY(5px); }
}
シードアニメーション(波形の変化)
baseFrequency の負荷をかけずに形状を変化させるには、seed をアニメーションさせます:
<feTurbulence baseFrequency="0.01 0.1">
<animate
attributeName="seed"
dur="20s"
values="1;50;100;50;1"
repeatCount="indefinite"
/>
</feTurbulence>
レイヤリング戦略
複数レイヤーの海
<div class="ocean">
<div class="wave wave-back"></div>
<div class="wave wave-mid"></div>
<div class="wave wave-front"></div>
<div class="foam-layer"></div>
</div>
.ocean {
position: relative;
height: 100vh;
background: linear-gradient(180deg,
#0c4a6e 0%,
#0369a1 40%,
#0ea5e9 100%
);
overflow: hidden;
}
.wave {
position: absolute;
width: 200%;
height: 100%;
background: rgba(255,255,255,0.1);
}
.wave-back {
filter: url(#waveBack);
opacity: 0.3;
animation: drift 90s linear infinite;
bottom: 0;
}
.wave-mid {
filter: url(#waveMid);
opacity: 0.5;
animation: drift 60s linear infinite;
bottom: -5%;
}
.wave-front {
filter: url(#waveFront);
opacity: 0.7;
animation: drift 35s linear infinite;
bottom: -10%;
}
.foam-layer {
position: absolute;
bottom: 0;
width: 100%;
height: 20%;
background: linear-gradient(to top,
rgba(255,255,255,0.8) 0%,
transparent 100%
);
filter: url(#foamFilter);
}
@keyframes drift {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
レイヤーパラメーターガイド
| レイヤー | 不透明度 | 速度 | フィルター Scale | baseFrequency |
|---|---|---|---|---|
| 背面(深層) | 0.2-0.4 | 80-100s | 15 | 0.004 0.04 |
| 中間 | 0.4-0.6 | 50-70s | 20 | 0.006 0.06 |
| 前面(水面) | 0.6-0.8 | 30-45s | 25 | 0.01 0.1 |
| 泡 | 0.7-0.9 | 25-35s | 10 | 0.02 0.02 |
カラーパレット
深海
.deep-ocean {
background: linear-gradient(180deg,
#0c4a6e 0%, /* Deep blue */
#075985 30%,
#0369a1 60%,
#0284c7 100% /* Surface shimmer */
);
}
- 基本色:
#0369a1 - 深色:
#0c4a6e - ハイライト:
#38bdf8
熱帯/カリブ海
.tropical {
background: linear-gradient(180deg,
#06b6d4 0%, /* Cyan surface */
#22d3ee 40%,
#67e8f9 70%,
#a5f3fc 100% /* Shallow sand reflection */
);
}
- 基本色:
#06b6d4 - 浅瀬:
#67e8f9 - 泡:
#ecfeff
荒れた海
.stormy {
background: linear-gradient(180deg,
#1e293b 0%, /* Dark clouds */
#334155 30%,
#475569 60%,
#64748b 100% /* Whitecaps */
);
}
- 基本色:
#475569 - 深色:
#1e293b - 白波:
#cbd5e1
夕暮れの反射
.sunset-water {
background: linear-gradient(180deg,
#831843 0%, /* Pink sky */
#9d174d 20%,
#be185d 40%,
#0369a1 60%, /* Water starts */
#0c4a6e 100%
);
}
完全な実装テンプレート
テンプレート 1:海のシーン全体
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
.ocean-scene {
position: relative;
width: 100%;
height: 100vh;
background: linear-gradient(180deg,
#87CEEB 0%, /* Sky */
#87CEEB 40%,
#0369a1 40%, /* Horizon */
#0c4a6e 100% /* Deep */
);
overflow: hidden;
}
.horizon-line {
position: absolute;
top: 40%;
left: 0;
right: 0;
height: 2px;
background: rgba(255,255,255,0.3);
}
.wave {
position: absolute;
width: 200%;
height: 60%;
bottom: 0;
background: rgba(255,255,255,0.15);
}
.wave-1 {
filter: url(#wave1);
animation: drift 80s linear infinite;
opacity: 0.4;
}
.wave-2 {
filter: url(#wave2);
animation: drift 55s linear infinite;
animation-delay: -20s;
opacity: 0.6;
}
.wave-3 {
filter: url(#wave3);
animation: drift 35s linear infinite;
animation-delay: -10s;
opacity: 0.8;
}
@keyframes drift {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
</style>
</head>
<body>
<svg style="position:absolute;width:0;height:0">
<defs>
<filter id="wave1" x="0" y="0" width="100%" height="100%">
<feTurbulence type="turbulence" baseFrequency="0.004 0.04" numOctaves="3" seed="1"/>
<feDisplacementMap in="SourceGraphic" scale="15"/>
</filter>
<filter id="wave2" x="0" y="0" width="100%" height="100%">
<feTurbulence type="turbulence" baseFrequency="0.006 0.06" numOctaves="3" seed="2"/>
<feDisplacementMap in="SourceGraphic" scale="20"/>
</filter>
<filter id="wave3" x="0" y="0" width="100%" height="100%">
<feTurbulence type="turbulence" baseFrequency="0.01 0.1" numOctaves="4" seed="3"/>
<feDisplacementMap in="SourceGraphic" scale="25"/>
</filter>
</defs>
</svg>
<div class="ocean-scene">
<div class="horizon-line"></div>
<div class="wave wave-1"></div>
<div class="wave wave-2"></div>
<div class="wave wave-3"></div>
</div>
</body>
</html>
テンプレート 2:水中ビュー(水面の奥にコンテンツを表示)
<style>
.underwater-container {
position: relative;
width: 100%;
overflow: hidden;
}
.content-behind-water {
/* Your actual content */
}
.water-overlay {
position: absolute;
inset: 0;
pointer-events: none;
filter: url(#underwaterDistort);
}
.caustics {
position: absolute;
inset: 0;
background: url('data:image/svg+xml,...') repeat;
opacity: 0.3;
mix-blend-mode: overlay;
animation: caustic-shift 10s linear infinite;
}
@keyframes caustic-shift {
from { background-position: 0 0; }
to { background-position: 100px 50px; }
}
</style>
<svg style="display:none">
<defs>
<filter id="underwaterDistort" x="-10%" y="-10%" width="120%" height="120%">
<feTurbulence id="underwaterTurb" type="turbulence"
baseFrequency="0.015 0.08" numOctaves="3"/>
<feDisplacementMap in="SourceGraphic" scale="20"
xChannelSelector="R" yChannelSelector="B"/>
<feColorMatrix type="matrix"
values="0.85 0 0 0 0
0 0.9 0 0 0.02
0 0 1.1 0 0.08
0 0 0 1 0"/>
</filter>
</defs>
</svg>
<script>
// Animate underwater distortion
const turb = document.getElementById('underwaterTurb');
let frame = 0;
function animate() {
frame += 0.005;
const xFreq = 0.015 + Math.sin(frame) * 0.003;
const yFreq = 0.08 + Math.sin(frame * 0.7) * 0.01;
turb.setAttribute('baseFrequency', `${xFreq} ${yFreq}`);
requestAnimationFrame(animate);
}
animate();
</script>
テンプレート 3:React の水コンポーネント
import React, { useEffect, useRef, useMemo } from 'react';
interface WaterEffectProps {
type?: 'ocean' | 'pool' | 'stream' | 'glass';
intensity?: 'subtle' | 'medium' | 'strong';
animate?: boolean;
className?: string;
children?: React.ReactNode;
}
const WATER_CONFIGS = {
ocean: { baseFreq: [0.008, 0.08], octaves: 4, scale: 25 },
pool: { baseFreq: [0.02, 0.02], octaves: 2, scale: 12 },
stream: { baseFreq: [0.01, 0.15], octaves: 3, scale: 20 },
glass: { baseFreq: [0.01, 0.05], octaves: 2, scale: 8 },
};
const INTENSITY_MULTIPLIERS = {
subtle: 0.5,
medium: 1.0,
strong: 1.5,
};
export const WaterEffect: React.FC<WaterEffectProps> = ({
type = 'ocean',
intensity = 'medium',
animate = true,
className,
children
}) => {
const turbRef = useRef<SVGFETurbulenceElement>(null);
const frameRef = useRef(0);
const config = WATER_CONFIGS[type];
const mult = INTENSITY_MULTIPLIERS[intensity];
const filterId = useMemo(() =>
`water-${type}-${Date.now()}`, [type]
);
useEffect(() => {
if (!animate || !turbRef.current) return;
let animationId: number;
const animateWater = () => {
frameRef.current += 0.004;
const frame = frameRef.current;
const xFreq = config.baseFreq[0] + Math.sin(frame) * 0.002;
const yFreq = config.baseFreq[1] + Math.sin(frame * 0.7) * 0.01;
turbRef.current?.setAttribute('baseFrequency', `${xFreq} ${yFreq}`);
animationId = requestAnimationFrame(animateWater);
};
animateWater();
return () => cancelAnimationFrame(animationId);
}, [animate, config]);
return (
<>
<svg style={{ position: 'absolute', width: 0, height: 0 }}>
<defs>
<filter id={filterId} x="-10%" y="-10%" width="120%" height="120%">
<feTurbulence
ref={turbRef}
type="turbulence"
baseFrequency={config.baseFreq.join(' ')}
numOctaves={config.octaves}
result="waves"
/>
<feDisplacementMap
in="SourceGraphic"
in2="waves"
scale={config.scale * mult}
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</defs>
</svg>
<div className={className} style={{ filter: `url(#${filterId})` }}>
{children}
</div>
</>
);
};
// Usage:
// <WaterEffect type="ocean" intensity="medium" animate>
// <img src="underwater-scene.jpg" />
// </WaterEffect>
テンプレート 4:CSS のみの波(SVG 不使用)
SVG フィルターを使わずに、シンプルで高パフォーマンスな波を作成するには:
.css-waves {
position: relative;
height: 300px;
background: linear-gradient(180deg, #0369a1 0%, #0c4a6e 100%);
overflow: hidden;
}
.css-wave {
position: absolute;
width: 200%;
height: 100%;
bottom: 0;
left: -50%;
background:
radial-gradient(ellipse 100% 50% at 50% 100%,
rgba(255,255,255,0.3) 0%,
transparent 60%
);
animation: css-wave-move 8s ease-in-out infinite;
transform-origin: center bottom;
}
.css-wave:nth-child(1) {
animation-duration: 7s;
opacity: 0.5;
}
.css-wave:nth-child(2) {
animation-duration: 10s;
animation-delay: -3s;
opacity: 0.3;
}
.css-wave:nth-child(3) {
animation-duration: 13s;
animation-delay: -5s;
opacity: 0.2;
}
@keyframes css-wave-move {
0%, 100% {
transform: translateX(0) scaleY(1);
}
50% {
transform: translateX(25%) scaleY(1.1);
}
}
パフォーマンスの最適化
重要なルール
type="turbulence"を使用する - 水に適したタイプです(fractalNoise ではありません)- numOctaves は 4 以下にする - 4 を超えても視覚的な向上はわずかですが、CPU 負荷は指数関数的に増加します
- Scale は 10~30 にする - 40 を超えると不自然になり、処理も遅くなります
- baseFrequency のアニメーションは避ける - 代わりに CSS トランスフォームまたは seed アニメーションを使用します
- GPU ヒントを使う - アニメーションするレイヤーに
will-change: transformを追加します - SVG の defs をまとめる -
<defs>ブロックを 1 つにし、複数のフィルターを配置します
パフォーマンスの段階
| 段階 | 手法 | 目標 FPS | 使用例 |
|---|---|---|---|
| Ultra | CSS の放射状グラデーションのみ | 60fps | モバイル、ローエンド端末 |
| High | SVG フィルター、CSS トランスフォームアニメーション | 60fps | 背景の波 |
| Medium | SVG フィルター + seed アニメーション | 45-60fps | インタラクティブな水 |
| Low | SVG フィルター + baseFrequency アニメーション | 30-40fps | ヒーローセクションのみ |
モバイル向けの最適化
@media (prefers-reduced-motion: reduce) {
.wave {
animation: none !important;
}
}
@media (max-width: 768px) {
/* Reduce to 2 wave layers */
.wave-1 { display: none; }
/* Use simpler filter */
.wave { filter: url(#waveSimple); }
}
パフォーマンスの検出
const canHandleWaterEffects = () => {
// Check for GPU
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
if (!gl) return 'ultra'; // CSS only
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: '';
// Integrated graphics = medium tier
if (renderer.includes('Intel') || renderer.includes('Mali')) {
return 'medium';
}
return 'high';
};
// Apply appropriate tier
const tier = canHandleWaterEffects();
document.body.dataset.waterTier = tier;
/* Tier-based styling */
[data-water-tier="ultra"] .wave {
filter: none;
background: linear-gradient(/* simple gradient */);
}
[data-water-tier="medium"] .wave {
filter: url(#waveSimple);
}
[data-water-tier="high"] .wave {
filter: url(#waveFull);
}
フレームワークとの統合
Next.js / React
// components/OceanBackground.tsx
'use client';
import { useEffect, useState, useRef } from 'react';
import styles from './OceanBackground.module.css';
export function OceanBackground({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);
const turbRef = useRef<SVGFETurbulenceElement>(null);
useEffect(() => {
setMounted(true);
// Animate after mount
let frame = 0;
const animate = () => {
frame += 0.003;
if (turbRef.current) {
const xFreq = 0.008 + Math.sin(frame) * 0.002;
const yFreq = 0.08 + Math.sin(frame * 0.7) * 0.01;
turbRef.current.setAttribute('baseFrequency', `${xFreq} ${yFreq}`);
}
requestAnimationFrame(animate);
};
const id = requestAnimationFrame(animate);
return () => cancelAnimationFrame(id);
}, []);
if (!mounted) return null;
return (
<div className={styles.ocean}>
<svg className={styles.filters}>
<defs>
<filter id="oceanWave">
<feTurbulence ref={turbRef} type="turbulence"
baseFrequency="0.008 0.08" numOctaves="4"/>
<feDisplacementMap in="SourceGraphic" scale="25"/>
</filter>
</defs>
</svg>
<div className={styles.waveLayer} />
<div className={styles.content}>{children}</div>
</div>
);
}
Tailwind CSS
// tailwind.config.js
module.exports = {
theme: {
extend: {
animation: {
'wave-drift': 'wave-drift 60s linear infinite',
'wave-slow': 'wave-drift 90s linear infinite',
'wave-fast': 'wave-drift 35s linear infinite',
},
keyframes: {
'wave-drift': {
from: { transform: 'translateX(0)' },
to: { transform: 'translateX(-50%)' },
},
},
colors: {
ocean: {
deep: '#0c4a6e',
mid: '#0369a1',
surface: '#0ea5e9',
foam: '#f0f9ff',
},
},
},
},
};
Vue 3
<template>
<div class="ocean-container">
<WaveFilters />
<div
v-for="layer in waveLayers"
:key="layer.id"
class="wave-layer"
:style="layer.style"
/>
<slot />
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import WaveFilters from './WaveFilters.vue';
const waveLayers = computed(() => [
{ id: 1, style: { filter: 'url(#wave1)', animationDuration: '80s', opacity: 0.4 }},
{ id: 2, style: { filter: 'url(#wave2)', animationDuration: '55s', opacity: 0.6 }},
{ id: 3, style: { filter: 'url(#wave3)', animationDuration: '35s', opacity: 0.8 }},
]);
</script>
デバッグのヒント
フィルターパイプラインを可視化する
<filter id="debug-water">
<feTurbulence result="step1"/>
<feImage href="#step1" x="0" y="0" width="200" height="200"/>
<feDisplacementMap in="SourceGraphic" in2="step1" result="step2"/>
<feImage href="#step2" x="200" y="0" width="200" height="200"/>
</filter>
よくある問題
| 問題 | 原因 | 解決策 |
|---|---|---|
| 効果が消える | フィルター領域が小さすぎる | x="-20%" y="-20%" width="140%" height="140%" を追加します |
| 波が四角い/箱状になる | fractalNoise を使用している | type="turbulence" に変更します |
| 波が均一すぎる | すべてのレイヤーで同じ seed を使用している | 異なる seed 値を使用します |
| 水平方向に動かない | baseFrequency の値が同じ | baseFrequency="0.01 0.1" を使用します(x/y に異なる値を設定) |
| アニメーションがカクつく | フィルター属性をアニメーションしている | 代わりに CSS トランスフォームアニメーションを使用します |
| エッジに乱れがある | 境界で変位が発生している | x/y/width/height を使ってフィルター領域を広げます |
ブラウザーの DevTools
- Elements パネル > SVG フィルターを選択 > 属性を確認
- Performance パネル > 記録 > レイアウトの過剰な再計算がないか確認
- Layers パネル(Chrome)> 波のレイヤーで GPU アクセラレーションが有効か確認
web-cloud-designer との統合
完全な大気のシーンを作成するには、水と雲の効果を組み合わせます:
<div class="scene">
<div class="sky">
<div class="cloud-layer cloud-back"></div>
<div class="cloud-layer cloud-front"></div>
</div>
<div class="horizon"></div>
<div class="ocean">
<div class="wave wave-back"></div>
<div class="wave wave-front"></div>
<div class="reflection"></div>
</div>
</div>
<style>
.scene {
height: 100vh;
display: grid;
grid-template-rows: 60% 40%;
}
.sky {
background: linear-gradient(180deg, #1e3c72 0%, #87CEEB 100%);
}
.ocean {
background: linear-gradient(180deg, #0369a1 0%, #0c4a6e 100%);
}
.reflection {
/* Mirror cloud movement on water surface */
position: absolute;
top: 0;
width: 100%;
height: 30%;
background: inherit;
transform: scaleY(-1);
opacity: 0.3;
filter: url(#waterReflection) blur(2px);
}
</style>
参考資料
- Red Stapler:「Realistic Water Effect SVG Turbulence」
- Mitkov Systems:「Liquid Glass Water Animation」(2025)
- O'Reilly SVG Book:feTurbulence の章
- MDN:SVG フィルタープリミティブのドキュメント
- CSS-Tricks:「Underwater Blur Effect」
- Codrops:「Water Distortion Effect」
水は、あらゆる自然の原動力です。 - レオナルド・ダ・ヴィンチ