我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
前言
夏天到了,蓝蓝的天空,白白的云朵,画一个风车凉爽一下吧. 然后呢正好用的浏览器的壁纸就是一个大风车。所以就顺便开始画了。
最开始的思路是用css 来画的,但是实现了一下,画了一点点,感觉很难实现。所以就改用了canvas来实现。
先说明一下本来颜色的上色我准备重新弄的,但是一想到重新弄的画,强迫症就犯了,可能对应的边框也会重新画。所以就简单点有点那个样子就行了。
先来成品张图瞅瞅吧。
接下来就开始画吧。
噢噢噢噢!源码还没给:
定圆心
为什么需要定圆心?
- 因为如果说你之后想让它转起来或者动起来的画,那么你就需要考虑它旋转的角度哪些。我就是没考虑好,所以就没让它转起来,因为转起来太丑啦。
- 第二个就是扇叶的大小还有和画布距离的问题
第一步定义对应的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
}
接下来一次画出剩下三个扇叶。
扇叶的折叠部分
折叠的部分一样的,也需要考虑层级的问题,然后呢这次的顺序就是 上 > 左 > 下 > 右
那就先来画最上边的部分吧。折叠的部主要考虑的是弯曲的角度和遮盖的。其次最重要的就是描边。边弄好了才会有那种立体的效果。
先来看下,没有描边之前的效果:
这个时候立体感还不是很突出,然后加描边:
这样一对比是不是立体感就处理啦;所以上代码
/** 顶部的叶子: 最上层 */
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()
}
结束
啊!这就结束啦!🤔是的结束啦。画这个太费时间了,而且我画的还很潦草。所以就先结束吧,还得准备其他的东西呢。
这个只能算一个半成品吧。不过也能看得过去,然后的动画我加过了,但是贼丑,可能也有没有考虑到的情况,要改的话,估计又要花很久的时间,所以就没弄了。
最后呢就是: 希望夏天的风能带给你一丝清凉,唤醒属于学生时代的暑假记忆。