之前在博客主页一直想加一种波浪的效果,但是一直没有沉下心去学习。先将三种实现方式贴到博客,方便后续引入。
首先看最low的方法
css
1 2
| // html <div class="wave"></div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| // css .wave { position: relative; width: 200px; height: 200px; background-color: rgb(118, 218, 255); }
.wave:before, .wave:after { content: ""; position: absolute; width: 400px; height: 400px; top: 0; left: 50%; background-color: rgba(255, 255, 255, .4); transform: translate(-50%, -70%) rotate(0); animation: rotate 8s linear infinite; border-radius: 35%; z-index: 10; }
@keyframes rotate { 50% { transform: translate(-50%, -75%) rotate(180deg); } 100% { transform: translate(-50%, -70%) rotate(360deg); } }
|
首先我们切开一个200x200px的画布,我们利用伪元素在其内写入变化的效果。
首先我们选定波浪的形状,通过transform与border-radius
分别对其位置与形态产生改变,此时定义动画效果,将animation
指定形状的位置及旋转变化,造成视觉上的移动效果。
svg
这是一种可伸缩矢量图形,非常适合做动画交互,对于path和line的绘制支持十分强大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="200"> <g fill="rgba(106,127,239,0.1)"> <path d="M 0 70 Q 75 39, 150 70 T 300 70 T 450 70 T 600 70 T 750 70 V 100 H 0 V 0"></path> <animateTransform attributeName="transform" attributeType="XML" type="translate" from="0" to="-300" dur="1.5s" repeatCount="indefinite"></animateTransform> </g> <g fill="rgba(106,127,239,0.15)"> <path d="M 0 70 Q 87.5 47, 175 70 T 350 70 T 525 70 T 700 70 T 875 70 T 1050 70 V 100 H 0 V 0"></path> <animateTransform attributeName="transform" attributeType="XML" type="translate" from="0" to="-350" dur="3s" repeatCount="indefinite"></animateTransform> </g> <g fill="rgba(106,127,239,0.18)" transform="translate(-903.868 0)"> <path d="M 0 70 Q 135 36, 270 70 T 540 70 T 810 70 T 1080 70 V 100 H 0 V 0" transform="translate(-38.232284367796474, 0)"></path> <animateTransform attributeName="transform" attributeType="XML" type="translate" from="0" to="-540" dur="2s" repeatCount="indefinite"></animateTransform> </g> </svg>
|
这里svg制定了三条轨迹,显而易见的是fill可以指定我们填充的颜色,大家可能对path里的d
属性感到疑惑,我们首先要知道几个概念:
1 2 3 4 5 6 7
| d属性中使用了以下几个命令:
M:M x y表示移动到(x, y)点( svg中左上角是(0,0)点 ) Q:Q x1 y1, x2 y2表示绘制二次贝塞尔曲线,x1 y1为二次贝塞尔的控制点,x2 y2为终点,可以使用贝塞尔生产曲线工具帮助生成。 T:T x y表示生成上一个二次贝塞尔曲线的镜像,其终点坐标为(x,y) V:V y表示从当前点(x0,y0)垂直移动到(x0, y) H:H x表示从当前点(x0, y0)水平移动到(x, y0)
|
但是这样只会绘制静态的路径,是无法让它蠕动起来的,为了让它充满生机,引入这个东西
1 2 3 4 5 6 7
| <animateTransform attributeName="transform" attributeType="XML" type="translate" from="0" to="-300" dur="1.5s" repeatCount="indefinite"> </animateTransform>
|
- attributeName:需要运动的属性
- type:具体运动的类型
- from:运动初始值
- to:运动终点值
- dur:运动时间
- repeatCount:重复次数,indefinite为无限循环
通过一系列的图形组装,设定不同的运动速率,即可得到较为丰富的画面
canvas
对于这种实现方式,优点是调用了GPU的加速处理,但是无疑js的计算让此变得复杂,总体来说并不建议做复杂的动画交互。
第一步
1 2 3 4 5
| // 创建上下文 const canvas = document.querySelector('canvas'); const ctx = document.querySelector('canvas').getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight;
|
第二步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 设定波浪透明度 const wavesOpacities = [0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1];
// 设定相关参数 const params = { AMPLITUDE_WAVES: canvas.height, AMPLITUDE_MIDDLE: canvas.height / 3, AMPLITUDE_SIDES: canvas.height / 2, OFFSET_SPEED: 120, SPEED: 3, OFFSET_WAVES: 35, NUMBER_WAVES: 3, COLOR: ['#032bac', '#8a280b', '#ff9acc'], NUMBER_CURVES: 2, OFFSET_CURVE: true, RESET: false }; let speedInc = 0;
|
第三步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| // 定义render方法 const render = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight;
for (let j = params.NUMBER_WAVES - 1; j >= 0; j--) { // 每个波浪的距离 let offset = speedInc + j * Math.PI * params.OFFSET_WAVES;
// 颜色及透明度变化 ctx.fillStyle = params.COLOR[j];
ctx.globalAlpha = wavesOpacities[j];
// 设定传波函数 let leftRange = ((Math.sin((offset / params.OFFSET_SPEED) + 2) + 1) / 2 * params.AMPLITUDE_SIDES) + (canvas.height - params.AMPLITUDE_SIDES) / 2; let rightRange = ((Math.sin((offset / params.OFFSET_SPEED) + 2) + 1) / 2 * params.AMPLITUDE_SIDES) + (canvas.height - params.AMPLITUDE_SIDES) / 2;
let leftCurveRange = (Math.sin((offset / params.OFFSET_SPEED) + 2) + 1) / 2 * params.AMPLITUDE_WAVES + (canvas.height - params.AMPLITUDE_WAVES) / 2; let rightCurveRange = (Math.sin((offset / params.OFFSET_SPEED) + 1) + 1) / 2 * params.AMPLITUDE_WAVES + (canvas.height - params.AMPLITUDE_WAVES) / 2;
let endCurveRange = ((Math.sin((offset / params.OFFSET_SPEED) + 2) + 1) / 2 * params.AMPLITUDE_MIDDLE) + (canvas.height - params.AMPLITUDE_MIDDLE) / 2;
let reverseLeftCurveRange = endCurveRange - rightCurveRange + endCurveRange; let reverseRightCurveRange = endCurveRange - leftCurveRange + endCurveRange;
if (params.OFFSET_CURVE === false) {
leftCurveRange = rightCurveRange; reverseRightCurveRange = reverseLeftCurveRange;
}
// 开始填充路径 ctx.beginPath();
ctx.moveTo(0, leftRange);
ctx.bezierCurveTo(canvas.width / (params.NUMBER_CURVES * 3), leftCurveRange, canvas.width / (params.NUMBER_CURVES * 3 / 2), rightCurveRange, canvas.width / params.NUMBER_CURVES, endCurveRange);
for (let i = 1; i < params.NUMBER_CURVES; i++) {
const finalRightCurveRange = i % 2 !== 0 ? rightCurveRange : reverseRightCurveRange; const finalLeftCurveRange = i % 2 !== 0 ? leftCurveRange : reverseLeftCurveRange;
// 设定波点 const secondPtX = canvas.width * (i / params.NUMBER_CURVES) + canvas.width / (params.NUMBER_CURVES * 3); const secondPtY = endCurveRange - finalRightCurveRange + endCurveRange; const thirdPtX = canvas.width * (i / params.NUMBER_CURVES) + canvas.width * (2 / (params.NUMBER_CURVES * 3)); const thirdPtY = endCurveRange - finalLeftCurveRange + endCurveRange; const lastPtX = canvas.width * ((i + 1) / params.NUMBER_CURVES); const lastPtY = i === params.NUMBER_CURVES - 1 ? rightRange : endCurveRange;
ctx.bezierCurveTo(secondPtX, secondPtY, thirdPtX, thirdPtY, lastPtX, lastPtY);
}
ctx.lineTo(canvas.width, canvas.height); ctx.lineTo(0, canvas.height); ctx.lineTo(0, rightRange);
ctx.closePath(); ctx.fill(); }
// 增速 speedInc += params.SPEED; };
|
最后调用TweenMax.ticker.addEventListener(‘tick’, render);,注意最后的调用函数需要引入GSAP库,它是一个高性能的web animation lib。
实现效果就为