Canvas: 优雅的代码作图系列:中国国旗

有很多个这练手的,好的差的都有。这次,我演示下用极客的代码,画出最标准的中国国旗,并详细说明代码是怎么写出来的。

绘制规范:

一、严格按照绘制说明;

二、设置基本单位长度,其他长度全以单位长度的倍数表示;

三、坐标系取制作样式上的坐标,制作样式上有的坐标,照取,没有的,全部通过计算;

先把绘制说明复制一遍:

  1. 先将旗面划分为4个等分长方形,再将左上方长方形划分长宽15×10个方格。
  2. 大五角星的中心位于该长方形上5下5、左5右10之处。大五角星外接圆的直径为6单位长度。
  3. 四颗小五角星的中心点,第一颗位于上2下8、左10右5,第二颗位于上4下6、左12右3,第三颗位于上7下3、左12右3,第四颗位于上9下1、左10右5之处。
  4. 每颗小五角星外接圆的直径均为2单位长度。四颗小五角星均有一角尖正对大五角星的中心点。

下面先讲下如何作图。

一、设置 <canvas> 元素并取得 canvascontext 对象:

  1. <canvas id="canvas-flag" width="800"></canvas>
  1. var canvas = document.getElementById("canvas-flag");
  2. var context = canvas.getContext("2d");

<canvas>width 可以根据需要设置,也可以只设置 height,也可以不设置,在后面的代码中再行设置。但不要同时设置 widthheight,因为长宽比是固定的。

二、设置长宽及取得基本单位长度

  1. var WIDTH = canvas.width;
  2. var UNIT = WIDTH / 30;
  3. var HEIGHT = canvas.height = UNIT * 20;

因为开始在 <canvas> 元素上已经定义了 width="800",所以单位长度等于 WIDTH / 30,高度需要以单位长度自乘以 20。第三句为连续赋值,把 UNIT * 20 的值赋给 canvas.height 后,再赋给 HEIGHT

如果 <canvas> 元素上没有定义 width 的话:

  1. // 只定义了 height
  2. var HEIGHT = canvas.height;
  3. var UNIT = WIDTH / 20;
  4. var WIDTH = canvas.width = UNIT * 30;
  5. // width, height 都没有定义
  6. var WIDTH = canvas.width = 800;
  7. var UNIT = WIDTH / 30;
  8. var HEIGHT = canvas.height = UNIT * 20;

这样设置过后,后面的单位长度就都可以以这三个变量为基准了。

三、填充背景:

在这里,我把两个颜色设置成了常量。这个图比较简单,常量的优势不大,但复杂点的图,尤其是一种颜色被用在了好几个块上面的时候,优势就出来了。反复被引用而又未赋给常量的字符串叫魔术字符串,在编码中应尽量消灭。

  1. var COLOR_RED = "#de2910";
  2. var COLOR_YELLOW = "#ffde00";
  1. context.fillStyle = COLOR_RED;
  2. 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)的缺省值。

  1. function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {}

在函数体中,首先设置一个画笔指针,用来连接各个顶点。画笔指针初始化在起始顶点;画线(实际上是路径,是看不见的线,显示需要 fill()stroke() )后,画笔指针移动到相间的顶点,再画线,再移动,重复 countSides 次。至于填充,这不是这个方法的事,这个方法不负责。

  1. function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {
  2. var pointer = startAngle == null ? - Math.PI / 2 : startAngle;
  3. // 设置画笔指针,初始点为起始顶点
  4. // 用 startAngle == null 的原因是为了防止 startAngle = 0 的情况。
  5. var angle = 2 * Math.PI / countSides;
  6. // 取得相邻顶点的角度
  7. context.beginPath();
  8. for (var i = 0; i < countSides; i++) { // 需要重复 countSides 次
  9. context.lineTo(cx + radius * Math.cos(pointer), cy + radius * Math.sin(pointer));
  10. // 画笔指针所在点的坐标:(圆心加上半径乘以正弦值, 圆心加上半径乘以余弦值)
  11. // beginPath() 后不调用moveTo() 而直接 lineTo(),相当于 moveTo()
  12. // 就是说,第一次进循环,画笔指针点在动起始顶点,以后就是把画笔指针所在的点连起来
  13. pointer += 2 * angle;
  14. // 画笔指针移动到相间的顶点
  15. }
  16. context.fillStyle = fillColor;
  17. context.fill();
  18. // 填充
  19. context.closePath();
  20. }

好了,这样就行了!

完整的代码如下:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Flag of the people's republic of china</title>
  8. </head>
  9. <body>
  10. <canvas id="canvas-flag" width="800"></canvas>
  11. <script>
  12. (function() {
  13. // Flag of the people's republic of china, made by KilArmd
  14. var COLOR_RED = "#de2910";
  15. var COLOR_YELLOW = "#ffde00";
  16. var canvas = document.getElementById("canvas-flag");
  17. var context = canvas.getContext("2d");
  18. var WIDTH = canvas.width;
  19. var UNIT = WIDTH / 30;
  20. var HEIGHT = canvas.height = UNIT * 20;
  21. context.fillStyle = COLOR_RED;
  22. context.fillRect(0, 0, WIDTH, HEIGHT);
  23. drawStar(context, 5 * UNIT, 5 * UNIT, 3 * UNIT, 5, COLOR_YELLOW);
  24. [ [10, 2], [12, 4], [12, 7], [10, 9] ].forEach(function(coors) {
  25. drawStar(context, coors[0] * UNIT, coors[1] * UNIT, UNIT, 5, COLOR_YELLOW,
  26. Math.atan((coors[1] - 5) / (coors[0] - 5)) + Math.PI);
  27. });
  28. function drawStar(context, cx, cy, radius, countSides, fillColor, startAngle) {
  29. var pointer = startAngle == null ? - Math.PI / 2 : startAngle;
  30. var angle = 2 * Math.PI / countSides;
  31. context.beginPath();
  32. for (var i = 0; i < countSides; i++) {
  33. context.lineTo(cx + radius * Math.cos(pointer), cy + radius * Math.sin(pointer));
  34. pointer += 2 * angle;
  35. }
  36. context.fillStyle = fillColor;
  37. context.fill();
  38. context.closePath();
  39. }
  40. }());
  41. </script>
  42. </body>
  43. </html>

本文章由 KilArmd 原创,转载请注明出处

Canvas: 优雅的代码作图系列:中国国旗的更多相关文章

  1. 使用git推送代码到开源中国以及IDEA环境下使用git

    使用git推送代码到开源中国以及IDEA环境下使用git 在学习Java的过程中我们会使用到git这个工具来将我们本周所编写的代码上传到开源中国进行代码托管,而在使用git的时候有很多的同学由于不会操 ...

  2. Android Studio 单刷《第一行代码》系列 02 —— 日志工具 LogCat

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  3. Android Studio 单刷《第一行代码》系列 01 —— 第一战 HelloWorld

    前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...

  4. Android Studio 单刷《第一行代码》系列目录

    前言(Prologue) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Android ...

  5. Android Studio 单刷《第一行代码》系列 07 —— Broadcast 广播

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  6. Android Studio 单刷《第一行代码》系列 06 —— Fragment 生命周期

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  7. Android Studio 单刷《第一行代码》系列 05 —— Fragment 基础

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  8. Android Studio 单刷《第一行代码》系列 04 —— Activity 相关

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

  9. Android Studio 单刷《第一行代码》系列 03 —— Activity 基础

    前情提要(Previously) 本系列将使用 Android Studio 将<第一行代码>(书中讲解案例使用Eclipse)刷一遍,旨在为想入坑 Android 开发,并选择 Andr ...

随机推荐

  1. 2.如何修改apache的默认端口

    打开apache的conf文件夹,找到server.xml,修改里面这段的port即可,重启apache,修改成功

  2. 常见的JQuery应用举例

    在学习JS之后,JQuery(以下简称JQ)为我们提供了一种更加便捷和简单的操作模式,利用它开发人员将更为高效的进行工作,下面将一些常见的问题进行举例. 1.点击某处弹出提醒,例如某些游戏在注册时会弹 ...

  3. 容器如何访问外部世界?- 每天5分钟玩转 Docker 容器技术(36)

    前面我们已经解决了容器间通信的问题,接下来讨论容器如何与外部世界通信.这里涉及两个方向: 容器访问外部世界 外部世界访问容器 容器访问外部世界 在我们当前的实验环境下,docker host 是可以访 ...

  4. (转载)配置tomcat支持jython

    工作需要,特记录下配置tomcat支持jython开发的过程.参考链接:@http://blog.itpub.net/13186779/viewspace-201861/ *环境在win7下搭建,jd ...

  5. sublime text 3 配置python IDE

    Python越来越受“程序猿”们的青睐.快速的开发模式,简洁的代码格式,海量的扩展,这无疑都为python的火热奠定了基础. “磨刀不误砍柴工”,一款功能强劲的IDE能帮助开发者有效的管理.编辑,运行 ...

  6. Mybatis(七) mybatis的逆向工程的配置详解

    还是觉得看书学习有意思~嘿嘿.今天把mybatis给结束掉. --WH 一.什么是逆向工程? 简单点说,就是通过数据库中的单表,自动生成java代码. Mybatis官方提供了逆向工程,可以针对单表自 ...

  7. 基于 svn 服务器及 cocoapods-repo-svn 插件进行组件化私有库的创建

    一.准备 组件化 随着业务需求的增长,在单工程 MVC 模式下,app 代码逐渐变得庞大,面对的高耦合的代码和复杂的功能模块,我们或许就需要进行重构了,以组件化的形式,将需要的组件以 pod 私有库的 ...

  8. JAXP Dom 案例 对xml文件进行增加 查找 删除

    利用 JAXP 对 XML文件 的处理,把xml当做一个数据库来对待

  9. Java之字符串String,StringBuffer,StringBuilder

    String类: String类即字符串类型,并不是Java的基本数据类型,但可以像基本数据类型一样使用,用双引号括起来进行声明.在Java中用String类的构造方法来创建字符串变量. 声明字符串: ...

  10. JDBC相关知识

    一.连接数据库 1. 步骤 //1.创建一个Driver实现类的对象 Driver driver = new com.mysql.jdbc.Driver();//注意抛异常 //2.准备 url 和 ...