Canvas: 优雅的代码作图系列:中国国旗
Canvas: 优雅的代码作图系列:中国国旗
有很多个这练手的,好的差的都有。这次,我演示下用极客的代码,画出最标准的中国国旗,并详细说明代码是怎么写出来的。
绘制规范:
一、严格按照绘制说明;
二、设置基本单位长度,其他长度全以单位长度的倍数表示;
三、坐标系取制作样式上的坐标,制作样式上有的坐标,照取,没有的,全部通过计算;
先把绘制说明复制一遍:
- 先将旗面划分为4个等分长方形,再将左上方长方形划分长宽15×10个方格。
- 大五角星的中心位于该长方形上5下5、左5右10之处。大五角星外接圆的直径为6单位长度。
- 四颗小五角星的中心点,第一颗位于上2下8、左10右5,第二颗位于上4下6、左12右3,第三颗位于上7下3、左12右3,第四颗位于上9下1、左10右5之处。
- 每颗小五角星外接圆的直径均为2单位长度。四颗小五角星均有一角尖正对大五角星的中心点。
下面先讲下如何作图。
一、设置 <canvas>
元素并取得 canvas
和 context
对象:
<canvas id="canvas-flag" width="800"></canvas>
var canvas = document.getElementById("canvas-flag");
var context = canvas.getContext("2d");
<canvas>
的 width
可以根据需要设置,也可以只设置 height
,也可以不设置,在后面的代码中再行设置。但不要同时设置 width
和 height
,因为长宽比是固定的。
二、设置长宽及取得基本单位长度
var WIDTH = canvas.width;
var UNIT = WIDTH / 30;
var HEIGHT = canvas.height = UNIT * 20;
因为开始在 <canvas>
元素上已经定义了 width="800"
,所以单位长度等于 WIDTH / 30
,高度需要以单位长度自乘以 20
。第三句为连续赋值,把 UNIT * 20
的值赋给 canvas.height
后,再赋给 HEIGHT
。
如果 <canvas>
元素上没有定义 width
的话:
// 只定义了 height
var HEIGHT = canvas.height;
var UNIT = WIDTH / 20;
var WIDTH = canvas.width = UNIT * 30;
// width, height 都没有定义
var WIDTH = canvas.width = 800;
var UNIT = WIDTH / 30;
var HEIGHT = canvas.height = UNIT * 20;
这样设置过后,后面的单位长度就都可以以这三个变量为基准了。
三、填充背景:
在这里,我把两个颜色设置成了常量。这个图比较简单,常量的优势不大,但复杂点的图,尤其是一种颜色被用在了好几个块上面的时候,优势就出来了。反复被引用而又未赋给常量的字符串叫魔术字符串,在编码中应尽量消灭。
var COLOR_RED = "#de2910";
var COLOR_YELLOW = "#ffde00";
context.fillStyle = COLOR_RED;
context.fillRect(0, 0, WIDTH, HEIGHT);
四、画星星
画五角星相对来说要难一些。把五角星的五个角标为顶点1、顶点2、顶点3、顶点4、顶点5,通常用笔是这样画星星的:顶点1连到顶点3,连到顶点5,连到顶点2,连到顶点4,最后再连到顶点1。实际上大于三的奇数角星都可以这样画。
现在回到制图说明上来,每颗五角星的中心(本质上是圆心),半径都是确定的。也就是说,只需要明确顶点1(暂时称作起始顶点)的角度,就明确了五角星。
在 canvas
画布上,横轴的方向是向右的,纵轴的方向是向下的。
对于大五角星,起始顶点是向上的,角度是:
$$ -\ \pi\ /\ 2 $$
对于小五角星,有一个顶点是明确的,就是指向大五角星中心的那个顶点。现在来计算其角度,设置大五角中心的坐标是\((A_x, A_y)\),小五角星中心的坐标是 \((B_x, B_y)\),那么,起始角的角度就是两点连线的斜率的反正切,再加上\(\pi\)。连线斜率的反正切是横轴与连线的角度,加\(\pi\)是为了旋转180度,让其指向大五角星的中心。
$$
\arctan \frac{(B_y - A_y)}{(B_x - A_x)} + \pi;
$$
根据上面的绘制说明,每颗星星的中点(本质上是圆心)是确定的,半径是确定的。但我们画星星,还需要一个明确的起始角度,大星星的为向上,小星星的明确的起始角是指向大星星中点的,但需要通过计算。
总共要画六个星星,我们可以写个函数 drawStar
,接收以下参数:context
对象,中心横坐标,中心纵坐标,半径,几角星,填充颜色,起始角。
由于大于三的奇数角星都可以用这个方法来绘制,所有添加了一个几角星的参数。还可以给起始角添加一个向上(- Math.PI / 2
)的缺省值。
function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {}
在函数体中,首先设置一个画笔指针,用来连接各个顶点。画笔指针初始化在起始顶点;画线(实际上是路径,是看不见的线,显示需要 fill()
或 stroke()
)后,画笔指针移动到相间的顶点,再画线,再移动,重复 countSides
次。至于填充,这不是这个方法的事,这个方法不负责。
function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {
var pointer = startAngle == null ? - Math.PI / 2 : startAngle;
// 设置画笔指针,初始点为起始顶点
// 用 startAngle == null 的原因是为了防止 startAngle = 0 的情况。
var angle = 2 * Math.PI / countSides;
// 取得相邻顶点的角度
context.beginPath();
for (var i = 0; i < countSides; i++) { // 需要重复 countSides 次
context.lineTo(cx + radius * Math.cos(pointer), cy + radius * Math.sin(pointer));
// 画笔指针所在点的坐标:(圆心加上半径乘以正弦值, 圆心加上半径乘以余弦值)
// beginPath() 后不调用moveTo() 而直接 lineTo(),相当于 moveTo()
// 就是说,第一次进循环,画笔指针点在动起始顶点,以后就是把画笔指针所在的点连起来
pointer += 2 * angle;
// 画笔指针移动到相间的顶点
}
context.fillStyle = fillColor;
context.fill();
// 填充
context.closePath();
}
好了,这样就行了!
完整的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Flag of the people's republic of china</title>
</head>
<body>
<canvas id="canvas-flag" width="800"></canvas>
<script>
(function() {
// Flag of the people's republic of china, made by KilArmd
var COLOR_RED = "#de2910";
var COLOR_YELLOW = "#ffde00";
var canvas = document.getElementById("canvas-flag");
var context = canvas.getContext("2d");
var WIDTH = canvas.width;
var UNIT = WIDTH / 30;
var HEIGHT = canvas.height = UNIT * 20;
context.fillStyle = COLOR_RED;
context.fillRect(0, 0, WIDTH, HEIGHT);
drawStar(context, 5 * UNIT, 5 * UNIT, 3 * UNIT, 5, COLOR_YELLOW);
[ [10, 2], [12, 4], [12, 7], [10, 9] ].forEach(function(coors) {
drawStar(context, coors[0] * UNIT, coors[1] * UNIT, UNIT, 5, COLOR_YELLOW,
Math.atan((coors[1] - 5) / (coors[0] - 5)) + Math.PI);
});
function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {
var pointer = startAngle == null ? - Math.PI / 2 : startAngle;
var angle = 2 * Math.PI / countSides;
context.beginPath();
for (var i = 0; i < countSides; i++) {
context.lineTo(cx + radius * Math.cos(pointer), cy + radius * Math.sin(pointer));
pointer += 2 * angle;
}
context.fillStyle = fillColor;
context.fill();
context.closePath();
}
}());
</script>
</body>
</html>
Canvas: 优雅的代码作图系列:中国国旗的更多相关文章
- 使用git推送代码到开源中国以及IDEA环境下使用git
使用git推送代码到开源中国以及IDEA环境下使用git 在学习Java的过程中我们会使用到git这个工具来将我们本周所编写的代码上传到开源中国进行代码托管,而在使用git的时候有很多的同学由于不会操 ...
- Android Studio 单刷《第一行代码》系列 02 —— 日志工具 LogCat
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
- Android Studio 单刷《第一行代码》系列 01 —— 第一战 HelloWorld
前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...
- Android Studio 单刷《第一行代码》系列目录
前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...
- Android Studio 单刷《第一行代码》系列 07 —— Broadcast 广播
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
- Android Studio 单刷《第一行代码》系列 06 —— Fragment 生命周期
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
- Android Studio 单刷《第一行代码》系列 05 —— Fragment 基础
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
- Android Studio 单刷《第一行代码》系列 04 —— Activity 相关
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
- Android Studio 单刷《第一行代码》系列 03 —— Activity 基础
前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...
随机推荐
- Linux下如何启动svn服务器
service svnserve start 启动服务 service svnserve stop 停止服务 service svnserve restart 重启服务 rpm -e --nodeps ...
- Mongodb基础用法及查询操作[转载]
插入多条测试数据> for(i=1;i<=1000;i++){... db.blog.insert({"title":i,"content":&qu ...
- 最全的CSS浏览器兼容问题(转至http://68design.net/Web-Guide/HTMLCSS/37154-1.html)
最全的CSS浏览器兼容问题 CSS对浏览器的兼容性有时让人很头疼,或许当你了解当中的技巧跟原理,就会觉得也不是难事,从网上收集了IE7,6与Fireofx的兼容性处理方法并整理了一下.对于web2 ...
- 2017寒假零基础学习Python系列之 印子
今日为2017年2月6日,据在慕课网上学习廖雪峰Python教程也快一周左右了,完全是零基础入门Python,大一上学期粗浅的接触学习了C语言,早就听说过Python语言的大名,又想把Python的爬 ...
- VI修改文件
找到路径 vi 文件名 i 添加或修改 esc : wq 保存退出 --------------------------------------------------- ...
- VB6之WebBrowser控件
UI短手或者GDI+用烦的童鞋可以借用WebBrowser打造漂亮的程序界面,只需要下载一个好看点的html代码就够了. 引用: Microsoft Html Object Library 部件: M ...
- win32SDK的hello,world程序(二)
接上篇,原生的控件都不太好看,所以决定自己画一个,稍微处理下消息就能用了.不过,美化这东西是需要天赋的.即使技术再好,没有对UI布局调整和良好的审美能力,做出来的东西还是很挫. 主要把消息逻辑和画的过 ...
- Java编程代码性能优化总结
如果有足够的时间开发.维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的. 代码优化的目标是: 1.减小代码的体积 2.提高代码运行的效率 ...
- Hibernate update 和 merge 、saveOrUpdate的区别
this.getSession().update(obj); this.getSession().merge(obj); this.getSession().saveOrUpdate(obj); 1. ...
- JavaScript跨域请求和jsonp请求实例
<script type="text/javascript" src="./whenReady.js"></script> <sc ...