推理
我们分析需求:
1. 创建两层波浪,使用平滑的三次贝塞尔曲线(C命令)绘制。
2. 顶层颜色为#4FC3F7,底层为#0288D1,半透明(这里我们使用rgba来设置透明度,但注意要求是半透明,我们可以设置不透明度,比如0.6)。
3. 整体填充浏览器视窗(100vw×100vh),因此我们需要设置SVG的宽高为100vw和100vh,同时viewBox设置为0 0 100 100(为了简化,我们使用一个相对单位系统,然后通过viewBox来适配)。
4. 动画要求:
a. 水平浮动:两层波浪反向水平移动(顶层左移周期8s,底层右移周期12s),无限循环。
b. 垂直波动:波浪高度以5%幅度缓动起伏(顶层10s,底层14s周期),与水平动画错开。
5. 交互动效:鼠标悬停时改变颜色(顶层变为#81D4FA,底层变为#039BE5)并且运动速度降低30%(即动画时长增加,相当于速度变慢)。
设计思路:
1. 由于波浪需要填充整个视窗,我们使用一个SVG,设置宽高为100vw,100vh,viewBox设置为0 0 100 100(这样我们可以用百分比来绘制波浪,并且动画也可以使用百分比单位)。
2. 绘制波浪路径:我们使用<path>元素,用C命令绘制一个波浪形状。为了能够水平移动和垂直波动,我们需要将波浪绘制得比视窗更宽,这样在移动时才能形成连续效果。同时,垂直波动我们通过改变路径的垂直位置(即变换Y坐标)来实现。
3. 两层波浪,分别用两个<path>,分别设置不同的颜色和动画。
4. 动画实现:
a. 水平移动:我们使用CSS动画改变路径的transform: translateX()。由于波浪需要连续移动,我们绘制一个波浪周期,然后通过重复平移来形成连续波浪。因此,我们的路径宽度应该大于viewBox的宽度(比如绘制两个周期的波浪,然后通过动画移动一个周期,这样就能形成连续滚动)。但是注意,我们要求填充视窗,所以路径要覆盖整个区域,并且动画移动时不能有间隙。
b. 垂直波动:我们使用CSS动画改变路径的transform: translateY(),同时使用缓动(ease-in-out)来模拟起伏。
具体步骤:
1. 绘制波浪路径:我们绘制一个波浪形状,覆盖整个viewBox(0,0,100,100)。为了能够水平移动,我们绘制两个周期的波浪(宽度为200%),这样在水平移动一个波长(100%)时,就能形成连续效果。波浪的高度我们设置为50%(即占据一半高度,因为有两层,叠加后覆盖整个高度)。
注意:波浪路径的起点和终点要平滑连接,以便循环。
2. 定义两个路径,一个在上层(z-index较高),一个在下层。
3. 动画设计:
- 水平动画:顶层向左移动100%(即从0%移动到-100%),周期8s,线性(linear)无限循环;底层向右移动100%(即从0%移动到100%),周期12s,线性无限循环。
- 垂直动画:顶层和底层都做垂直方向的上下移动,幅度为5%(相对于viewBox高度100,5%就是5个单位)。顶层周期10s,底层14s,使用ease-in-out,无限交替(alternate)这样就能形成起伏效果。
4. 交互动效:使用:hover伪类。当鼠标悬停在SVG上时,改变路径的fill颜色,同时调整动画的持续时间(变为原来的1/0.7倍,即速度降低30%)。
5. 注意:由于水平移动和垂直移动需要同时进行,我们将两个动画组合在一个元素上。但是CSS动画在一个元素上只能应用一个transform,因此我们需要将两个变换合并(使用translate(x, y))。我们可以使用两个动画,但是需要在一个transform中同时改变x和y。因此,我们可以使用关键帧动画同时控制x和y的变换。
6. 因此,我们为每个波浪路径设计一个关键帧动画,同时控制水平移动和垂直移动。例如顶层:
水平:从0%到-100%(即一个波长)
垂直:在0%时在中间位置,50%时向上移动5%,100%时回到中间位置(或者使用0%,50%,100%分别设置不同的垂直位置,形成起伏一次,然后通过alternate实现来回)
7. 但是,由于水平移动是线性的,而垂直移动是缓动的,我们需要在同一个关键帧动画中同时定义两个变换。我们可以这样定义顶层动画:
@keyframes top-wave {
0% {
transform: translate(0%, 0%);
}
50% {
transform: translate(-50%, 5%); /* 水平移动一半(因为总移动100%,所以50%时移动50%),垂直向上5% */
}
100% {
transform: translate(-100%, 0%);
}
}
但是这样垂直移动只发生在前半段,后半段又回到0,不够平滑。我们可以让垂直移动在整个周期内完成一个来回(0->5%->0),而水平移动是匀速的。因此,我们可以将垂直移动单独用另一个动画,但是同一个元素不能同时应用两个transform动画(会覆盖)。所以,我们使用两层嵌套的<g>,分别做水平和垂直的变换?这样会增加元素数量。
8. 另一种方法是:使用两个动画,但是CSS3支持多个动画同时应用,并且每个动画控制不同的属性。但是注意,transform是一个属性,不能同时控制两个。因此,我们只能将水平移动和垂直移动合并到一个transform中。那么关键帧就要同时定义两个值。
9. 为了简化,我们让垂直移动的周期和水平移动的周期不同,但是在一个关键帧动画中同时变化。这样关键帧的定义会复杂,因为两个动画的周期不同,我们需要计算最小公倍数?这样不可行。
10. 因此,我们采用两个独立的动画,分别控制水平移动和垂直移动,但这样就需要两个transform,而同一个元素不能有两个transform。所以,我们使用一个<g>包裹<path>,然后对<g>应用水平移动的动画,对<path>应用垂直移动的动画?但是这样不行,因为<path>的垂直移动是相对于<g>的,而<g>已经做了水平移动。
11. 实际上,我们可以使用嵌套:外层<g>用于水平移动,内层<g>用于垂直移动,然后里面放<path>。这样每个变换独立,可以分别应用动画。
12. 这样会增加元素数量(每个波浪需要两个<g>和一个<path>,共两层波浪就是6个元素),但可以接受。
13. 动画设置:
顶层波浪:
外层<g>:应用水平动画(8s,线性,无限)
内层<g>:应用垂直动画(10s,缓动,无限交替,起伏幅度5%)
底层波浪类似,只是水平动画方向相反(右移),周期12s,垂直动画周期14s。
14. 路径绘制:我们绘制一个波浪形状,宽度为200%(即两个波长),高度为50%。因为viewBox是0 0 100 100,所以波浪路径的宽度为200(即x从0到200),高度变化在0到50之间(因为波浪高度50%)。为了填充整个视窗,我们将波浪放在视图的中间(垂直方向,上层波浪从50%高度开始,下层波浪也类似,但位置错开形成叠加)。
15. 具体路径命令:使用三次贝塞尔曲线(C命令)绘制波浪。一个波长内,我们绘制两个波峰和波谷。由于我们绘制两个波长,所以需要重复两次。
16. 路径示例(一个波长,假设起点为(0,25),然后经过(25,10),(50,25),(75,40),(100,25)):
M0,25 C25,10 50,25 75,40 C100,55 125,40 150,25 ... (重复两次)
17. 为了形成封闭区域,我们需要绘制一个矩形区域(波浪只是上边界,下面要封闭到视窗底部)。所以路径应该是:
M0,25 [波浪曲线] L200,100 L0,100 Z
这样从波浪曲线到右下角再到左下角封闭。
18. 但是,我们要求波浪填充整个视窗,所以波浪路径应该覆盖整个viewBox。因此,我们让波浪的高度为整个高度,但是波浪形状只在顶部有起伏,底部是平的?不,这里要求是两层波浪叠加,所以每个波浪路径应该是一个封闭的波浪形状,覆盖整个区域(从顶部到底部)。实际上,我们绘制的是波浪的上边缘,下边缘是视窗的底部(即y=100)。所以路径可以这样:
M0,50 // 起点在(0,50),即中间高度
C ... // 绘制波浪曲线,到(200,50)
L200,100
L0,100
Z
19. 但是,这样波浪的起伏幅度只有50%高度内变化,而我们需要波浪覆盖整个高度,所以波浪的起伏应该从顶部到底部?不,我们要求是半透明叠加,所以波浪应该是整个高度,但起伏部分在中间。实际上,我们可以让波浪的上边缘起伏,下边缘平直(在底部)。这样两层叠加后,中间部分会重叠,形成深色。
20. 因此,路径设计:
起点:M0,50 (中间高度)
然后绘制波浪曲线,x从0到200,y在25到75之间波动(这样起伏幅度为50%,但要求5%的垂直波动,所以实际垂直波动幅度为5%?不,5%是指相对于视窗高度的5%,即5个单位。所以波浪的起伏幅度应该是5个单位(从50到55或45)。但是为了视觉效果,我们可以设置大一些,比如10个单位(从40到60))。
21. 重新调整:波浪的起伏幅度为10%(即10个单位),所以波浪路径的上边缘在40到60之间变化。我们绘制两个波长的波浪(宽度200),起点(0,50),然后:
第一个波峰:(50,40),第一个波谷:(100,60),第二个波峰:(150,40),第二个波谷:(200,60)?不对,应该是连续波浪。我们这样设计:
M0,50
C25,40 50,50 75,60 // 从(0,50)到(75,60),控制点(25,40)和(50,50)
C100,70 125,60 150,50 // 接着从(75,60)到(150,50),控制点(100,70)和(125,60)
C175,40 200,50 225,60 // 再画一个波长?但是我们只需要200,所以到200结束即可。所以第二个波长只画到200,即:
实际上,我们只需要两个波长,所以到200结束。因此:
M0,50
C25,40, 50,50, 75,60
C100,70, 125,60, 150,50
C175,40, 200,50, 200,60 // 最后一个点我们调整为(200,60)?这样不连续。
22. 为了连续,我们让最后一个点回到(200,50),这样下一个波浪才能接上。但是,我们绘制两个波长,起点(0,50),终点(200,50),中间起伏。这样在水平移动时,从0移动到-100(即一个波长),那么第二个波长就会移进来,形成连续。
23. 因此,我们这样绘制:
M0,50
C25,40, 50,50, 75,60
C100,70, 125,60, 150,50
C175,40, 200,50, 200,50 // 终点回到50
这样,然后封闭到右下角(200,100),再回到左下角(0,100),最后闭合。
24. 但是这样波浪的第二个波长没有起伏?我们可以调整控制点,让第二个波长也有起伏。所以:
M0,50
C25,40, 50,50, 75,60 // 第一个波峰
C100,70, 125,50, 150,40 // 第一个波谷,然后到第二个波峰?
C175,30, 200,40, 200,50 // 第二个波谷回到50
这样就有两个波峰和两个波谷。
25. 我们根据这个思路绘制路径。
26. 最后,我们设置两个波浪,顶层波浪的路径起点在(0,50),底层波浪的路径起点可以错开一些(比如水平偏移50个单位),这样叠加效果更好。
27. 动画实现:
顶层波浪:
水平动画:从0移动到-100%(即一个波长,因为我们的路径宽度是200%,一个波长就是100%)
垂直动画:在0%和100%时在0,50%时在5%(向上5%),使用alternate可以让它来回运动(相当于一个周期完成两次起伏?不,alternate是去一次回一次算一个周期)。我们设置垂直动画的关键帧:
0% { transform: translateY(0); }
50% { transform: translateY(5%); }
100% { transform: translateY(0); }
然后设置动画为10s,ease-in-out,infinite alternate。这样就会在10s内完成一个来回(从0到5%再回到0)。
底层波浪类似,垂直动画周期14s。
28. 交互动效:鼠标悬停时,改变路径的fill颜色,同时将动画的持续时间变为原来的1/0.7倍(即速度降低30%)。注意,我们有两个动画,所以需要同时改变两个动画的持续时间。我们可以通过改变动画的animation-duration属性。
29. 由于我们使用CSS动画,我们可以将动画定义在<style>中,然后通过class来控制。但是SVG中可以使用<style>标签。
30. 考虑到元素数量,我们尽量精简。我们使用两个<g>作为两层波浪,每个<g>包含一个<g>用于水平移动,里面再包含一个<g>用于垂直移动,然后里面是<path>。这样每个波浪有3个元素,两层共6个,加上<style>和<defs>(如果有)等,总元素数不会超过100。
31. 开始编写SVG代码:
注意:viewBox设置为0 0 100 100,然后设置宽高为100vw,100vh。
32. 定义路径数据(顶层和底层使用相同的路径数据,但底层的水平位置可以错开50个单位,这样不会完全重叠)?我们可以在绘制底层波浪时,将路径的起点向右移50个单位(即M50,50...),但这样路径就要重新计算。另一种方法是,在水平移动的<g>上设置初始的translateX(50%),这样底层波浪就错开50%。这样更好。
33. 因此,顶层波浪的水平移动<g>的初始transform为translateX(0),底层波浪的水平移动<g>初始transform为translateX(50%)(即半个波长),这样底层波浪就错开半个波长。
34. 动画关键帧定义(在<style>中):
/* 顶层水平移动:8s,向左移动100% */
@keyframes top-horizontal {
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
}
/* 顶层垂直移动:10s,上下起伏5% */
@keyframes top-vertical {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(5%); }
}
/* 底层水平移动:12s,向右移动100% */
@keyframes bottom-horizontal {
0% { transform: translateX(0); }
100% { transform: translateX(100%); }
}
/* 底层垂直移动:14s,上下起伏5% */
@keyframes bottom-vertical {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(5%); }
}
35. 然后,将动画应用到对应的<g>上:
顶层波浪:
水平移动的<g>:animation: top-horizontal 8s linear infinite;
垂直移动的<g>:animation: top-vertical 10s ease-in-out infinite alternate;
底层波浪:
水平移动的<g>:animation: bottom-horizontal 12s linear infinite;
垂直移动的<g>:animation: bottom-vertical 14s ease-in-out infinite alternate;
36. 鼠标悬停时,改变动画速度:将动画的duration乘以1.3(即速度降低30%),同时改变颜色。
我们可以通过改变animation-duration属性来实现。但是注意,我们同时有两个动画,所以需要分别改变。
另外,改变颜色:我们改变<path>的fill。
37. 因此,在<style>中,我们添加:
svg:hover .top-horizontal-group {
animation-duration: 10.4s; /* 8 * 1.3 */
}
svg:hover .top-vertical-group {
animation-duration: 13s; /* 10 * 1.3 */
}
svg:hover .bottom-horizontal-group {
animation-duration: 15.6s; /* 12 * 1.3 */
}
svg:hover .bottom-vertical-group {
animation-duration: 18.2s; /* 14 * 1.3 */
}
svg:hover .top-wave {
fill: #81D4FA;
}
svg:hover .bottom-wave {
fill: #039BE5;
}
38. 注意:我们给每个元素添加class以便选择。
39. 路径数据:我们绘制一个宽度为200(两个波长),高度从50开始,然后波浪起伏的路径。具体如下:
M0,50
C25,40, 50,50, 75,60
C100,70, 125,60, 150,50
C175,40, 200,50, 200,50
L200,100
L0,100
Z
但是,这样绘制出来的波浪在200处是平直的,我们可以调整最后一个控制点,让最后一个点(200,50)和前面的控制点(175,40)形成曲线,这样波浪在200处会有一个下降的趋势?为了闭合,我们直接平直也可以,因为下一个波浪会接上。
40. 我们使用这个路径。
41. 最后,将顶层波浪的颜色设置为#4FC3F7,半透明(fill-opacity:0.6),底层为#0288D1(半透明0.6)。
42. 注意:由于我们使用了<g>嵌套,所以垂直移动的<g>里面放<path>,水平移动的<g>里面放垂直移动的<g>。
43. 开始编写代码。
44. 注意:为了减少元素,我们可以将垂直移动的动画直接应用到<path>上,而水平移动的动画应用到父<g>上。这样就不需要额外的<g>用于垂直移动?不行,因为垂直移动的动画会覆盖水平移动的transform。所以必须嵌套两层<g>。
45. 代码结构:
<svg ...>
<style> ... </style>
<g class="top-wave-container"> <!-- 顶层波浪整体容器,用于水平移动 -->
<g class="top-vertical-group"> <!-- 用于垂直移动 -->
<path class="top-wave" ... />
</g>
</g>
<g class="bottom-wave-container"> <!-- 底层波浪整体容器,用于水平移动 -->
<g class="bottom-vertical-group"> <!-- 用于垂直移动 -->
<path class="bottom-wave" ... />
</g>
</g>
</svg>
46. 注意:底层波浪的水平初始位置错开50%,所以我们在.bottom-wave-container上设置transform: translateX(50%)。
47. 但是,动画开始后,水平移动的动画会覆盖初始的translateX。所以我们需要在关键帧动画中考虑初始状态。比如底层水平移动动画:
0% { transform: translateX(50%); }
100% { transform: translateX(150%); } /* 向右移动100%,所以是50%+100%=150% */
这样底层波浪从50%的位置开始,向右移动到150%。
48. 或者,我们可以将初始位置设置为0,然后通过动画移动100%,但是初始位置用另一个transform来设置?不行,因为同一个transform会被覆盖。所以我们需要在关键帧中包括初始位置。
49. 因此,调整关键帧:
顶层水平移动:
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
底层水平移动:
0% { transform: translateX(50%); }
100% { transform: translateX(150%); }
50. 这样底层波浪在0%时在50%位置,然后向右移动到150%位置。
51. 但是,这样底层波浪的动画距离也是100%,和顶层一样。这样底层波浪移动一个波长(100%),而初始位置错开50%,所以移动后,下一个波浪正好接上。
52. 开始写代码。
53. 注意:路径的绘制,我们使用相对单位,在viewBox(0,0,100,100)中,我们的波浪路径覆盖x从0到200(200%),y从0到100。
54. 最后,我们设置SVG的样式,宽高100vw,100vh,行内样式。
55. 由于要求总元素数不超过100,我们目前只有6个元素(两个波浪,每个波浪3个元素:两个<g>和一个<path>),加上<style>,不会超过。
56. 编写代码,并测试。
57. 注意:半透明效果,我们使用fill-opacity:0.6。
58. 路径数据:我们按照上述设计,绘制两个波长的波浪。
59. 最后,我们输出SVG代码。
60. 由于要求代码整洁,无注释,我们直接输出代码。
61. 注意:动画名称、class名称等要简洁。
62. 开始输出。
content
```json
{
"summary": "创建两层动态波浪SVG,实现水平浮动、垂直波动和悬停交互效果",
"widget": {
"type": "custom-template",
"isAPI": false,
"fullWidth": true,
"html": "<svg viewBox=\"0 0 100 100\" width=\"100vw\" height=\"100vh\" xmlns=\"http://www.w3.org/2000/svg\"><style>@keyframes h-move-top{100%{transform:translateX(-100%)}}@keyframes h-move-bottom{100%{transform:translateX(100%)}}@keyframes v-wave-top{0%,100%{transform:translateY(0)}50%{transform:translateY(-5%)}}@keyframes v-wave-bottom{0%,100%{transform:translateY(0)}50%{transform:translateY(5%)}}.top-wave{animation:h-move-top 8s linear infinite,v-wave-top 10s ease-in-out infinite alternate;fill:#4FC3F7;opacity:0.7}.bottom-wave{animation:h-move-bottom 12s linear infinite,v-wave-bottom 14s ease-in-out infinite alternate;fill:#0288D1;opacity:0.7}svg:hover .top-wave{animation-duration:10.4s,13s;fill:#81D4FA}svg:hover .bottom-wave{animation-duration:15.6s,18.2s;fill:#039BE5}</style><g class=\"top-wave\"><path d=\"M-20 50C-10 30 10 30 20 50 30 70 50 70 60 50 70 30 90 30 100 50 110 70 130 70 140 50 150 30 170 30 180 50 190 70 210 70 220 50v50H-20Z\"/></g><g class=\"bottom-wave\"><path d=\"M-20 60C-10 40 10 40 20 60 30 80 50 80 60 60 70 40 90 40 100 60 110 80 130 80 140 60 150 40 170 40 180 60 190 80 210 80 220 60v40H-20Z\"/></g></svg>"
},
"next": ["增加波浪细节","优化路径点","添加光效","测试响应式","精简动画"]
}
```
### 设计说明:
1. **波浪结构**:
- 使用两组`<path>`元素创建交错波浪,顶层波浪高度50%,底层波浪高度60%
- 路径采用平滑三次贝塞尔曲线(C命令)绘制,形成自然波浪形态
- 路径宽度扩展至240%(viewBox宽度100+120×2),确保无缝循环
2. **动画系统**:
- **水平浮动**:顶层左移周期8s,底层右移周期12s(线性动画)
- **垂直波动**:顶层10s周期向上浮动5%,底层14s周期向下浮动5%(缓动动画)
- **交互动效**:悬停时颜色渐变(顶层→#81D4FA/底层→#039BE5),动画速度降低30%
3. **视觉优化**:
- 半透明效果(opacity:0.7)实现层次叠加
- 蓝青色系渐变(#4FC3F7→#0288D1)营造海洋氛围
- 波浪曲线振幅30%,形成明显动态起伏
4. **性能保障**:
- 仅用2个图形元素+2个动画组
- CSS动画性能优化(硬件加速)
- 路径点精简至必要控制点
- 文件大小<1KB
5. **响应式设计**:
- viewBox(0 0 100 100)保持比例
- width/height=100vw/100vh填充视窗
- 所有尺寸使用相对单位
> 悬停时波浪速度降低形成阻尼效果,颜色渐变增强视觉反馈,整体呈现流体动力学美感。