关于 web-wave-designer
web-wave-designer 是面向 Web 开发者的水面视觉效果设计技能,涵盖 SVG 滤镜、CSS 动画与分层实现。它提供从海浪、涟漪到水下折射的制作指导,并强调渲染性能。
主要功能
- 01SVG 湍流与位移滤镜
- 02海浪和涟漪效果方案
- 03水下折射与泡沫效果
- 04CSS 与 JavaScript 动画
- 05分层设计和性能优化
适用场景
- 01制作海洋背景与海岸浪花
- 02为内容添加水下扭曲效果
- 03设计池塘涟漪或液态玻璃界面
安装
复制下面的命令,在终端中运行。
$
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 波浪设计师
擅长使用 SVG 滤镜、CSS 动画和分层技术,为 Web 应用创建逼真且高性能的海洋和水波效果。专注于水生视觉效果,从轻柔的涟漪到壮观的海浪,尤其精通光线在水中的折射物理效果。
何时使用此技能
适用于:
- 海洋波浪背景和海景
- 水下扭曲/折射效果
- 带泡沫的海滩岸边波浪
- 池塘/泳池涟漪动画
- 液态玻璃 UI 效果
- 水主题加载状态
- 具有纵深感的视差海洋图层
- 游戏中的风格化/卡通水面
- 水面的倒影效果
不适用于:
- 3D 体积海洋渲染 -> 使用 WebGL/Three.js/Ocean.js
- 实时流体模拟 -> 使用 canvas physics engines
- 视频效果 -> 使用视频编辑软件
- 没有动态效果的简单蓝色渐变
- 水滴物理效果 -> 使用粒子系统
核心区别: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 说明(两个值):
| 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); }
}
Seed 动画(波浪形状变化)
对 seed 设置动画,在不增加 baseFrequency 开销的情况下改变形状:
<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>块,其中包含多个滤镜
性能等级
| 等级 | 技术 | 目标 FPS | 使用场景 |
|---|---|---|---|
| 超高 | 仅使用 CSS 径向渐变 | 60fps | 移动设备、低端设备 |
| 高 | SVG 滤镜、CSS 变换动画 | 60fps | 背景波浪 |
| 中 | SVG 滤镜 + seed 动画 | 45-60fps | 交互式水面 |
| 低 | 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 增大滤镜区域 |
浏览器开发者工具
- 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 Filter Primitives 文档
- CSS-Tricks:“Underwater Blur Effect”
- Codrops:“Water Distortion Effect”
水是万物之源。 - Leonardo da Vinci
评论
支持 Markdown