想在博客首页加一个波浪效果,谷歌了半天没有找到合适的代码,于是决定自己写一个。网上有一个使用canvas实现的,但是我觉得既然css已经可以支持动画效果了,有没有使用css实现的动画效果来实现我想要的海浪的效果呢?于是开始琢磨。
首先想到的,是如何制作波浪的外观,即波状曲线。刚开始陷入误区,觉得一定要画出线来,后来逐渐觉得,可以换个思路。波的产生是什么原理?是能量的传播。能量在波中传播的时候,不是靠物质的移动,对于水而言,其实是一直在原地上下波动,并没有像视觉上看到的在前进。既然自然原理是这样的,那么我们在实现效果的时候,也可以遵循这一原理。
首先,我们看到的是一片宁静的海面:
当海底某处发生地震时,这个海域的海水被地震波的能量顶出海面。我们姑且将这个水域看作一个整体,海水将高出海平面很多:
而隔壁的海域由于受到影响,也发生了同样的情况,但是由于时间稍晚,当中心海域浪最高的时刻,没那么高:
由此内推下去,再远一些的海域再晚一些。如此下去,能量将会传播整个海域,如果不考虑能量传播中的损耗,所有的浪高度都会是一样的。
上面是第一阶段,即当海底发生震动,将能量释放出来的时刻。当海水达到最高点时,会往下降。
如上所示,整个浪将会传向更远的海域。波浪就是这样形成的。连续不断的波浪是由于海底地震连续不断的顶起海水,形成的。所以,最终实际上整个波浪效果,是由单个柱体的循环上下运动,以及不同柱体运动上的时间差形成的一个效果。
根据这个原理,把柱体的宽度缩小,小到消除柱体之间的高度差,那样就看上去是一条连续运动的线形成的波浪效果。
根据这个思路,用css和js来实现他们。
首先,我们需要一个海洋:
<div class="ocean" id="ocean1"></div>
其次,我们需要海洋中的柱体:
<script> function addWaves(id, width) { var ocean = document.getElementById(id) var count = Math.floor(ocean.clientWidth/width) var docFrag = document.createDocumentFragment() for(let i = 0; i < count; i ++) { let wave = document.createElement("div") wave.className = "wave" wave.style.width = width + "px" wave.style.left = width * i + "px" docFrag.appendChild(wave) } ocean.appendChild(docFrag) } addWaves("ocean1", 5) // 规定每个柱体宽度为5像素 </script>
<style> .ocean { position: relative; } .ocean .wave { position: absolute; bottom: 0; height: 100%; } /* 上面是基础的css,下面可以根据你当前的需要调整样式 */ #ocean1 { width: 600px; height: 150px; margin-top: 100px; } #ocean1 .wave { background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, rgb(0,50,150)), color-stop(1, rgb(0,150,255))); } </style>
这样,一个平静的海域就构建好了。
接下来就是构建动态的效果。利用css3的keyframes来实现一个循环的动画帧,使海水动起来。新增如下样式:
@-webkit-keyframes wave { 0% { height: 100%; } 50% { height: 120%; } 100%{ height: 100%; } } .ocean .wave { animation: wave 2s infinite ease-in-out; }
添加这段css之后,你发现,整个海平面都在一起上下运动……所以,我们要继续处理延时的问题。延时的话在css里面有一个animation-delay属性。但是我们必须要让每一个wave的delay大于前一个,所以只能在js里面动态添加这个属性,在上面的js里面,给每个wave设置style的地方再增加一条:
wave.style.width = width + "px"wave.style.left = width * i + "px" wave.style.animationDelay = (i/100) + "s"
增加了一个animationDelay,这样就可以让每一个wave的动画延时执行,i/100是说把时间间隔缩小到以1/100s为单位。
这样,怎个波浪就动起来了,实现了我们想要的效果。不过这个效果里面有一点不是很好,就是如果5px作为一个柱体的宽度的话,会有明显的锯齿效果,想要消除锯齿,最好是让柱体的宽度等于1px,并且让柱体之间的延时变小,也就是说两个柱体之间的连续性更好,这样锯齿就相对没有那么明显。但由于延时变小,波浪的速度就会变快,波长也就会变小。总之最终代码如下:
<!doctype html> <head> <meta charset="utf-8"> <title>ocean</title> <style> @-webkit-keyframes wave { 0% { height: 100%; } 50% { height: 120%; } 100%{ height: 100%; } } .ocean { position: relative; } .ocean .wave { position: absolute; bottom: 0; height: 100%; animation: wave 2s infinite ease-in-out; } .ocean { width: 600px; height: 150px; margin-top: 100px; } .ocean .wave { background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, rgb(0,50,150)), color-stop(1, rgb(0,150,255))); } </style> </head> <body> <div class="ocean" id="ocean1"></div> <br> <div class="ocean" id="ocean2"></div> <br> <div class="ocean" id="ocean3"></div> <script> /** * id: 要获取的div元素的id属性 * width: 每一个wave柱体的宽度,单位px * duration: 每一个wave柱体上下运动一个循环的时长,单位s * delay: 当前运动的这个柱体相对于上一个柱体开始运动的延时,单位s */ function addWaves(id, width, duration, delay) { var ocean = document.getElementById(id) var count = Math.floor(ocean.clientWidth/width) var docFrag = document.createDocumentFragment() for(let i = 0; i < count; i ++) { let wave = document.createElement("div") wave.className = "wave" wave.style.width = width + "px" wave.style.left = (width * i) + "px" wave.style.animationDelay = (i * delay) + "s" wave.style.animationDuration = duration + "s" docFrag.appendChild(wave) } ocean.appendChild(docFrag) } addWaves("ocean1", 5, 2, 1/100) addWaves("ocean2", 1, 2, 1/100) addWaves("ocean3", 1, 1.5, 1/1000) </script> </body> </html>
注意一下addWaves函数,当width和delay取值不同时,得到的效果也不同。width是柱体的宽度,不能太大,delay是第二个柱体相对于第一个柱体开始运动的延时时间,单位为妙,因此这里都是按1/100以下作为单位的。波峰为ocean高度的120%(写死在css里面的),一个波长为:waveWith = width * duration / delay
,如果你的ocean宽度大于波长,就可以在可视区域内看到oceanWidth/waveWith
个浪头。这与你的设计非常相关。
好了,关于波浪的实现就到这里,希望对你有用。
2017-02-18 12137
思路很棒,不过最终效果锯齿太严重了
是的,css锯齿也是个难题