预备知识

效果图

image.png

  • 有波浪滚动效果
  • 有爱心有细长的尖

波浪效果的原理

利用 视觉差 产出波浪的效果,由两个正弦曲线做相对运动

正弦曲线公式

image.png

正弦曲线公式:y = A sin(Bx + C) + D

A 控制振幅,A 值越大,波峰和波谷越大,A 值越小,波峰和波谷越小;
image.png

B 值会影响周期,B 值越大,那么周期越短,B 值越小,周期越长。
image.png

C 值会影响图像左右移动,C 值为正数,图像右移,C 值为负数,图像左移。
image.png

D 值控制上下移动。
image.png

振幅:控制波浪的高度
周期:控制波浪的宽度
相移:控制波浪的水平移动
垂直位移:控制水位的高度

动画效果的实现主要是利用相移,通过不断水平移动曲线,产出波浪移动的感觉,然后可以绘制多条曲线,
曲线之间通过控制属性(高度、宽度、移动速度),产生视觉差,就会有波浪起伏的感觉了。

桃心的方程

image.png

桃心的方程在网上可以找到

波浪的特性

image.png

绘制桃心

  1. const canvas = document.getElementById("ydtyest");
  2. const ctx = canvas.getContext("2d");
  3. const drawHeart = (ctx, x = 150, y = 130, a = 9) => {
  4. }
  5. //绘制心形状
  6. drawHeart(ctx);

获取桃心的绘制点

根据桃心的方程,将桃心拆分多份,获取每个断点(绘制点)的坐标

  1. const drawHeart = (ctx, x = 150, y = 130, a = 9) => {
  2. //所有心上小点的集合
  3. const vectors = [];
  4. const partNum = 50; //绘制的点数,点数越多,图形越细腻
  5. for (let i = 0; i < partNum; i++) {
  6. //设置一下心上两点之间的角度 具体分成多少份
  7. const step = i / partNum * (Math.PI * 2);
  8. const vector = {
  9. x: a * (16 * Math.pow(Math.sin(step), 3)),
  10. y: a * (13 * Math.cos(step) - 5 * Math.cos(2 * step) - 2 * Math.cos(3 * step) - Math.cos(4 *
  11. step))
  12. }
  13. vectors.push(vector);
  14. }
  15. }

画桃心

拿到桃心的点集合后,就可以开始绘制桃心

  1. const drawHeart = (ctx, x = 150, y = 130, a = 9) => {
  2. //...
  3. ctx.save();
  4. ctx.beginPath();
  5. ctx.translate(x, y); //平移桃心图形。通过公式获取的部分点在坐标轴的其他象限,看不到
  6. ctx.rotate(Math.PI);//旋转桃心,因为一开始绘制的桃心的倒的
  7. for (let i = 0; i < partNum; i++) {
  8. const vector = vectors[i];
  9. ctx.lineTo(vector.x, vector.y);
  10. }
  11. ctx.strokeStyle = "rgba(186,165,130,0.6)";
  12. ctx.lineWidth = 2;
  13. ctx.stroke();
  14. ctx.restore();
  15. ctx.clip();//为使后面的波浪只在桃心内显示
  16. }
  17. //绘制心形状
  18. drawHeart(ctx);

画曲线的原理:曲线其实也是由多个点连线组成的,点越多,曲线越顺滑

绘制波浪

定义波浪的类及属性

  1. class Wave {
  2. constructor({
  3. canvasWidth, //轴长
  4. canvasHeight, //轴高
  5. waveWidth = 0.055, //波浪的宽度 == B
  6. waveHeight = 6, //设置波浪的高度 == A
  7. speed = 0.04,
  8. xOffset = 0, //水平的位置 == C
  9. colors = ["#F39C6B", "#A0563B"]
  10. }) {
  11. this.points = [];
  12. this.startX = 0;
  13. this.colors = colors;
  14. this.canvasWidth = canvasWidth;
  15. this.canvasHeight = canvasHeight;
  16. this.waveWidth = waveWidth;
  17. this.waveHeight = waveHeight;
  18. this.speed = speed;
  19. this.xOffset = xOffset;
  20. }
  21. }

更新波浪的坐标

  1. class Wave{
  2. //...
  3. //更新当前的波浪
  4. update({
  5. nowRange
  6. } = {}) {
  7. this.points = [];
  8. const {
  9. startX,
  10. canvasWidth,
  11. canvasHeight,
  12. waveWidth,
  13. waveHeight,
  14. speed,
  15. xOffset
  16. } = this;
  17. //nowrRange == D
  18. for (let x = startX; x < startX + canvasWidth; x += 20 / canvasWidth) {
  19. //y = A sin(Bx + C) + D
  20. //y = 波浪高度 * sin(x*波浪的宽度 + 水平位移)
  21. const y = waveHeight * Math.sin((startX + x) * waveWidth + xOffset);
  22. const dY = canvasHeight * (1 - (nowRange / 100));
  23. this.points.push([x, y + dY])
  24. }
  25. this.xOffset += this.speed;
  26. }
  27. }

绘制波浪方法

图形的绘制方法放在图形的类中

  1. class Wave{
  2. //...
  3. //进行波浪内部的绘制
  4. draw(ctx) {
  5. ctx.save();
  6. const points = this.points;
  7. ctx.beginPath();
  8. for (let i = 0; i < points.length; i++) {
  9. //point = {x:,y:}
  10. const point = points[i];
  11. ctx.lineTo(point[0], point[1]);
  12. }
  13. //跟踪一下他的lineto
  14. ctx.lineTo(this.canvasWidth,this.canvasHeight);
  15. ctx.lineTo(this.startX,this.canvasHeight);
  16. ctx.fillStyle = this.getChartColor(ctx);
  17. ctx.fill();
  18. ctx.restore();
  19. }
  20. }

设置波浪的渐变色

  1. class Wave{
  2. //...
  3. getChartColor(ctx) {
  4. const radius = this.canvasWidth / 2;
  5. const grd = ctx.createLinearGradient(radius, radius, radius, this.canvasHeight);
  6. grd.addColorStop(0, this.colors[0]);
  7. grd.addColorStop(1, this.colors[1]);
  8. return grd;
  9. }
  10. }

把波浪绘制到桃心里

  1. //声明两个波浪
  2. this.wave1 = new Wave({
  3. canvasWidth: 300,
  4. canvasHeight: 300,
  5. waveWidth: 0.055,
  6. waveHeight: 4,
  7. speed: 0.04,
  8. xOffset: 0, //初始化的偏移
  9. });
  10. this.wave2 = new Wave({
  11. canvasWidth: 300,
  12. canvasHeight: 300,
  13. waveWidth: 0.04,
  14. waveHeight: 3,
  15. speed: 0.02,
  16. colors : ["rgba(243,145,107,0.48)", "rgba(160,86,59,0.48)"],
  17. xOffset: 2, //初始化的偏移
  18. });
  19. let nowRange = 0;
  20. let rangeRange = 60;
  21. const draw = () => {
  22. //清空
  23. ctx.clearRect(0,0,300,300);
  24. if (nowRange <= rangeRange) {
  25. nowRange += 1;
  26. }
  27. //把波浪绘制到心里
  28. //更新当前的位置
  29. this.wave1.update({
  30. nowRange
  31. });
  32. //重新绘制当前的相移动
  33. this.wave1.draw(ctx);
  34. this.wave2.update({
  35. nowRange
  36. });
  37. this.wave2.draw(ctx);
  38. window.requestAnimationFrame(draw);
  39. }
  40. draw();

小结

1.容易把图形画反,需要调整绘制的顺序
2.每次重新绘制前,都要将画板清空

源码:
yd-wave.zip