夏天到了,画一个风车,希望夏天的风能让它转起来,带来一丝清凉

990 阅读8分钟

我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

前言

夏天到了,蓝蓝的天空,白白的云朵,画一个风车凉爽一下吧. 然后呢正好用的浏览器的壁纸就是一个大风车。所以就顺便开始画了。

最开始的思路是用css 来画的,但是实现了一下,画了一点点,感觉很难实现。所以就改用了canvas来实现。

先说明一下本来颜色的上色我准备重新弄的,但是一想到重新弄的画,强迫症就犯了,可能对应的边框也会重新画。所以就简单点有点那个样子就行了。

先来成品张图瞅瞅吧。

image.png

接下来就开始画吧。

噢噢噢噢!源码还没给:

定圆心

为什么需要定圆心?

  • 因为如果说你之后想让它转起来或者动起来的画,那么你就需要考虑它旋转的角度哪些。我就是没考虑好,所以就没让它转起来,因为转起来太丑啦。
  • 第二个就是扇叶的大小还有和画布距离的问题

第一步定义对应的canvas 画布,并且获取到对应的画布

const canvas: any = document.getElementById('windmill-canvas')
const ctx = canvas.getContext('2d')

然后开始画最下层的的一个圆心,canvas 画的话要注意层级也就是画的先后次序,后面画的会覆盖掉前面的。

我定义了四个颜色值, 蓝色 白色的填充颜色,和覆盖在蓝色 和 白色 之上的 阴影,因为是双颜色的风车,所以有两个底色。主要的使用的是createRadialGradient 放射状的渐变。

圆的画,直接使用arc 来进行画圆。

/** 初始化颜色 */
  const initColor = (ctx: any) => {
    // 白色填充颜色
    const grd = ctx.createRadialGradient(430, 1350, 5, 430, 1350, 180);
    grd.addColorStop(0, "rgb(222, 227, 231)");
    grd.addColorStop(1, "rgb(219, 223, 230)");

    // 白色阴影颜色
    const grd1 = ctx.createRadialGradient(430, 1350, 5, 500, 1500, 540);
    grd1.addColorStop(0, "rgba(124, 140, 164, 1)");
    grd1.addColorStop(1, "rgb(222, 227, 231)");

    // 蓝色填充颜色
    const grd2 = ctx.createRadialGradient(430, 1350, 5, 430, 1350, 180);
    grd2.addColorStop(0, "rgb(82, 126, 191)");
    grd2.addColorStop(1, "rgb(82, 126, 191)");

    // 蓝色阴影颜色
    const grd3 = ctx.createRadialGradient(430, 1350, 5, 520, 1300, 380);
    grd3.addColorStop(0, "rgb(36, 68, 143)");
    grd3.addColorStop(1, "rgb(82, 126, 191)");
    return [grd, grd1, grd2, grd3]
  }

  /** 中心的圆 */
  const windmillCenter = (ctx: any) => {
    const [grd, grd1, grd2, grd3] = initColor(ctx)
    // 蓝色圆心
    ctx.beginPath();
    ctx.arc(431, 1352, 130, 0, 2 * Math.PI);
    // 底色蓝色
    ctx.strokeStyle = grd2
    ctx.fillStyle = grd2
    ctx.fill();

    // 附加一层黑色阴影
    ctx.strokeStyle = grd3
    ctx.fillStyle = grd3
    ctx.globalAlpha = 0.8
    ctx.fill();
    ctx.globalAlpha = 1
    // 圆心
    ctx.beginPath();
    ctx.arc(430, 1350, 130, 0, 2 * Math.PI);
    // 底色白色
    ctx.strokeStyle = grd
    ctx.fillStyle = grd
    ctx.fill();

    // 附加一层黑色阴影
    ctx.strokeStyle = grd1
    ctx.fillStyle = grd1
    ctx.globalAlpha = 0.8
    ctx.fill();
    ctx.globalAlpha = 1
  }

扇叶

扇叶的画,我们先用偏平的视角去画一个底部的,对应的立体的我们在之后做补充进去。

按照后绘画的图会覆盖的前面的所以,我们要分层级来画,我这边是最右边的在最下,依次是 右> 下 > 左 >上

扇叶的部分使用二次贝塞尔曲线(quadraticCurveTo) 或者 三次贝塞尔曲线bezierCurveTo 来画。我基本用的都是三次贝塞尔曲线bezierCurveTo, 它控制的点多一些,变化也多一些,所以就用了这个。

画之前记得使用moveTo 将开始的画的点移动到你要画的那个位置。画好之后用 fill 填充好颜色。同时呢,边框的颜色也可以和填充的颜色使用同一个,这样就想当于没有边框线了。

/** 右侧的扇叶 */
  const windmillLeafOne = (ctx: any) => {
    const [grd, grd1, grd2, grd3] = initColor(ctx)

    // 右侧的扇叶: 蓝色 1
    ctx.beginPath();
    // 右侧的扇叶
    ctx.moveTo(540, 1419)
    // 右侧的扇叶底边
    ctx.bezierCurveTo(540, 1419, 524, 1660, 865, 1450);
    // 右侧的扇叶上边
    ctx.bezierCurveTo(860, 1450, 790, 1170, 610, 1220);
    // 回到圆
    ctx.bezierCurveTo(610, 1235, 570, 1334, 560, 1334);
    // 圆弧
    ctx.quadraticCurveTo(570, 1384, 540, 1419);
    // 底色蓝色
    ctx.lineWidth = 1
    ctx.strokeStyle = grd2 // 线的颜色
    ctx.fillStyle = grd2
    ctx.fill();
    ctx.stroke()

    // 附加一层黑色阴影
    ctx.strokeStyle = grd3
    ctx.fillStyle = grd3
    ctx.globalAlpha = 0.8
    ctx.fill();
    ctx.globalAlpha = 1

    // 右侧的扇叶: 白色 2
    ctx.beginPath();
    // 右侧的扇叶
    ctx.moveTo(524, 1440)
    // 右侧的扇叶底边
    ctx.bezierCurveTo(560, 1490, 524, 1660, 850, 1540);
    // 右侧的扇叶上边
    ctx.bezierCurveTo(800, 1400, 820, 1220, 620, 1270);
    // 回到圆
    ctx.bezierCurveTo(620, 1270, 630, 1320, 560, 1350);
    // 圆弧
    ctx.quadraticCurveTo(560, 1400, 524, 1440);
    // 底色白色
    ctx.lineWidth = 1
    ctx.strokeStyle = grd // 线的颜色
    ctx.fillStyle = grd
    ctx.fill();
    ctx.stroke()

    // 附加一层黑色阴影
    ctx.strokeStyle = grd1
    ctx.fillStyle = grd1
    ctx.globalAlpha = 0.8
    ctx.fill();
    ctx.stroke()
    ctx.globalAlpha = 1
  }

接下来一次画出剩下三个扇叶。

扇叶的折叠部分

折叠的部分一样的,也需要考虑层级的问题,然后呢这次的顺序就是 上 > 左 > 下 > 右

那就先来画最上边的部分吧。折叠的部主要考虑的是弯曲的角度和遮盖的。其次最重要的就是描边。边弄好了才会有那种立体的效果。

先来看下,没有描边之前的效果:

image.png

这个时候立体感还不是很突出,然后加描边:

image.png

这样一对比是不是立体感就处理啦;所以上代码

 /** 顶部的叶子: 最上层 */
  const windmillLeafLineOne = (ctx: any) => {
    const [grd, grd1, grd2, grd3] = initColor(ctx)

    // 顶部白色叶片勾勒: 初步勾勒出形状 然后填充底色,之后勾勒边框线
    ctx.beginPath();
    ctx.moveTo(628, 960)
    ctx.bezierCurveTo(428, 990, 390, 1075, 390, 1075);
    ctx.bezierCurveTo(380, 1085, 369, 1175, 369, 1175);
    ctx.bezierCurveTo(369, 1175, 430, 1250, 470, 1270);
    ctx.bezierCurveTo(470, 1270, 484, 1266, 480, 1266);
    ctx.bezierCurveTo(379, 1165, 390, 1100, 400, 1075);
    ctx.bezierCurveTo(409, 1075, 428, 990, 628, 960);
    ctx.lineWidth = 1

    const grdlinebg = ctx.createLinearGradient(320, 1235, 608, 1160);
    grdlinebg.addColorStop(0, "rgba(46, 79, 134, 1)");
    grdlinebg.addColorStop(0.3, "rgba(219, 223, 230, 1)");
    grdlinebg.addColorStop(1, "rgb(219, 223, 230)");

    ctx.strokeStyle = grdlinebg
    ctx.fillStyle = grdlinebg
    ctx.fill();
    ctx.stroke()

    ctx.strokeStyle = grd1
    ctx.fillStyle = grd1
    ctx.globalAlpha = 0.3
    ctx.fill();
    ctx.stroke()
    ctx.globalAlpha = 1

    ctx.beginPath();
    ctx.moveTo(628, 960)
    ctx.bezierCurveTo(428, 990, 400, 1075, 400, 1075);
    ctx.bezierCurveTo(390, 1100, 379, 1165, 476, 1262);
    ctx.bezierCurveTo(486, 1262, 472, 1268, 472, 1268);
    ctx.bezierCurveTo(378, 1168, 380, 1105, 392, 1075);
    ctx.bezierCurveTo(400, 1065, 428, 990, 628, 960);
    ctx.lineWidth = 1
    ctx.strokeStyle = 'rgb(193,197,205)'
    const grdlinebg2 = ctx.createLinearGradient(300, 1075, 628, 960);
    grdlinebg2.addColorStop(0, "rgba(179, 178, 184, 1)");
    grdlinebg2.addColorStop(0.2, "rgba(179, 178, 184, 0.4)");
    grdlinebg2.addColorStop(1, "rgb(163, 167, 180)");
    ctx.fillStyle = grdlinebg2
    ctx.fill();
    ctx.stroke()

    ctx.beginPath();
    ctx.moveTo(551, 1300)
    // 上侧的扇叶右侧
    ctx.bezierCurveTo(638, 1200, 698, 1100, 628, 960);
    ctx.strokeStyle = '#a0acbd'
    ctx.stroke()

    // 顶部蓝色叶片勾勒
    ctx.beginPath();
    ctx.moveTo(550, 918);
    ctx.bezierCurveTo(400, 994, 350, 1064, 346, 1090);
    ctx.bezierCurveTo(346, 1100, 320, 1210, 328, 1210);
    ctx.bezierCurveTo(326, 1220, 426, 1332, 436, 1322);
    ctx.bezierCurveTo(436, 1322, 480, 1274, 480, 1274);
    ctx.bezierCurveTo(480, 1274, 326, 1190, 354, 1090);
    ctx.bezierCurveTo(350, 1080, 400, 990, 550, 918);

    const grdlinetopbluebg = ctx.createLinearGradient(480, 1304, 328, 1210);
    grdlinetopbluebg.addColorStop(0, "#295c9f");
    grdlinetopbluebg.addColorStop(1, "#579dc5");

    ctx.strokeStyle = grdlinetopbluebg
    ctx.fillStyle = grdlinetopbluebg
    ctx.fill();
    ctx.stroke()

    // 边框
    ctx.beginPath();
    ctx.moveTo(550, 918);
    ctx.bezierCurveTo(400, 994, 350, 1064, 346, 1090);
    ctx.bezierCurveTo(326, 1200, 480, 1274, 480, 1274);
    ctx.bezierCurveTo(326, 1190, 354, 1080, 354, 1090);
    ctx.bezierCurveTo(350, 1080, 400, 990, 550, 918);

    const grdlinetopbluebg2 = ctx.createLinearGradient(550, 918, 480, 1274);
    grdlinetopbluebg2.addColorStop(0, "#295c9f");
    grdlinetopbluebg2.addColorStop(1, "#579dc5");
    ctx.fillStyle = grdlinetopbluebg2
    ctx.strokeStyle = grdlinetopbluebg2
    ctx.fill();
    ctx.stroke()

    ctx.save()

  }

之后呢就是一个重复的工作.

稍微画的有亿点点跑偏了。不过还能瞅得过去,看过或者见过原图的小伙伴应该就知道我说的没错,偏了亿点点,原图是真好看。而且我这也只能算一个半成品吧。

最上层的圆点

然后上一个代码吧:

/** 最中间的圆 */
  const windmillCenterTop = (ctx: any) => {
    ctx.beginPath();
    ctx.arc(490, 1300, 22, 0, 2 * Math.PI);
    const grd = ctx.createLinearGradient(490, 1278, 490, 1322);
    grd.addColorStop(0, "#d1d9de");
    grd.addColorStop(0.8, "#d6e1e6");
    grd.addColorStop(1, "#bcc7d0");
    ctx.strokeStyle = grd
    ctx.fillStyle = grd
    ctx.fill();
    ctx.stroke()

    ctx.beginPath();
    ctx.moveTo(468, 1300);
    ctx.arcTo(490, 1330, 512, 1300, 22);
    ctx.arcTo(490, 1330, 458, 1300, 22);
    const grd2 = ctx.createLinearGradient(490, 1300, 490, 1330);
    grd2.addColorStop(0, "#adb4c2");
    grd2.addColorStop(0.8, "#d4d8d1");
    grd2.addColorStop(1, "#d4d8d1");
    ctx.strokeStyle = grd2
    ctx.fillStyle = grd2
    ctx.fill();
    ctx.stroke()

    ctx.beginPath();
    ctx.moveTo(486, 1284);
    ctx.quadraticCurveTo(480, 1300, 486, 1310);
    ctx.quadraticCurveTo(500, 1316, 500, 1304);
    ctx.quadraticCurveTo(500, 1284, 486, 1284);

    const grd3 = ctx.createLinearGradient(490, 1300, 490, 1330);
    grd3.addColorStop(0, "#9da29c");
    grd3.addColorStop(0.1, "#ada8a0");
    grd3.addColorStop(1, "#d4d8d1");
    ctx.strokeStyle = grd3
    ctx.fillStyle = grd3
    ctx.fill();
    ctx.stroke()

    ctx.save()
  }

结束

啊!这就结束啦!🤔是的结束啦。画这个太费时间了,而且我画的还很潦草。所以就先结束吧,还得准备其他的东西呢。

这个只能算一个半成品吧。不过也能看得过去,然后的动画我加过了,但是贼丑,可能也有没有考虑到的情况,要改的话,估计又要花很久的时间,所以就没弄了。

最后呢就是: 希望夏天的风能带给你一丝清凉,唤醒属于学生时代的暑假记忆。