前言

上篇主要是理论的概述,本篇会多些实践,来讲讲canvas的基础用法,并包含一些基础三角函数的应用,推荐没有canvas基础的朋友阅读,熟悉的朋友可以跳过。
本人能力有限,欢迎牛人共同讨论,批评指正。

一起来画画吧

canvas的API有很多,如果一一列举30分钟你是绝对看不完的,而且怎么流水账还不如自己去看文档呢(笑),本教程的思路是用实例一步一步从无到有讲解基础用法。
canvas相关文档

准备工作

  1. 布置画布:通过添加<canvas>标签,添加canvas元素;
  2. 获取画布:通过<canvas>标签的id,获得canvas对象;
  3. 获得画笔:通过canvas对象的getContext("2d")方法,获得2D环境。
<canvas id="canvas" width="400" height="400"></canvas>
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

画个箭头

首先我们来画个红边黄底的箭头,使用面向对象的代码组织方式,全部代码如下。
类名为Arrow。它拥有x轴坐标、y轴坐标、底的颜色color、旋转弧度rotation四个属性。
实例方法是draw(),它需要一个context对象作为参数,就是准备工作里的context,它就相当于是画笔,这里其实是类似依赖注入的过程,将canvas的画笔交给实例的draw()方法,实例用这个画笔去画出箭头,绘画过程见代码注释。特别注意以下几点:

  • beginPath()方法调用后moveTo()和lineTo移动坐标是相对与beginPath()时画笔的坐标的,可以理解成画笔自带一个坐标系,它可以旋转和在画布上移动,绘制工作的坐标都是属于这个坐标系的;
  • beginPath()是绘制设置状态的起始点,它之后代码设置的绘制状态的作用域结束于绘制方法stroke()、fill()或者closePath();
  • save()的作用是保存笔的状态,因为一个画布的笔只有一支,会在不同对象中传递,为了不污染后续的画就应该先保存,画完再restore()还原;
  • <canvas>本身是透明的,可以使用CSS给它个背景,例子中普遍使用白色背景。
/**
* 箭头类
* @class Representing a arrow.
*/
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "Arrow" }] */
class Arrow {
/**
* Create a arrow.
*/
constructor() {
this.x = 0;
this.y = 0;
this.color = '#ffff00';
this.rotation = 0;
}
/**
* Draw the arrow.
* @param {Object} _context - The canvas context.
*/
draw(_context) {
const context = _context;
// 会先保存画笔状态
context.save();
// 移动画笔
context.translate(this.x, this.y);
// 旋转画笔
context.rotate(this.rotation);
// 设置线条宽度
context.lineWidth = 2;
// 设置线条颜色
context.strokeStyle = '#ff0000';
// 设置填充颜色
context.fillStyle = this.color;
// 开始路径
context.beginPath();
// 将笔移动到相对位置
context.moveTo(-50, -25);
// 画线到相对位置
context.lineTo(0, -25);
context.lineTo(0, -50);
context.lineTo(50, 0);
context.lineTo(0, 50);
context.lineTo(0, 25);
context.lineTo(-50, 25);
context.lineTo(-50, -25);
// 闭合路径
context.closePath();
// 填充路径包围区
context.fill();
// 绘制路径
context.stroke();
// 载入保存的笔信息
context.restore();
}
}

同理我们还可以画点其他的,比如一个圆ball.js,稍微多些参数,慢慢理解。
成品的效果可以先看这个(稍微剧透):一个会跟踪鼠标位置的箭头

加入循环动起来

现在我们已经掌握了画画的基本功,并且可以画箭头arrow.js和圆ball.js,然而这样只是静止画,接下来我们需要一个循环,不断的执行擦除和重画的工作才能实现帧动画。
下面这段代码的中绘图函数drawFrame被立即执行,并递归调用自身,你将会在大部分例子中看到。
循环原理上一篇已经说明,不再重复。这里要说明的是clearRect(),这个函数接受一个矩形坐标,也就是(x轴坐标,y轴坐标,矩形宽度,矩形高度),用于清除矩形区域内的画。
例子里直接是清除了整个画布,但这不是绝对的,刷不刷新,是局部刷新还是全部刷新,都需要灵活处理。
这里有个不刷新的例子:鼠标画图工具

(function drawFrame() {
// 类似setTimeout的操作
window.requestAnimationFrame(drawFrame, canvas);
// 将画布擦干净
context.clearRect(0, 0, canvas.width, canvas.height);
// ...继续你的作画
}());

给它点动力

现在画面已经是在不断的重绘,但为什么还是静止的呢?因为每一次刷新都没有改变要画的内容。
那我们就给它一个目标吧,这样它才能动起来,比如就让箭头始终指向鼠标。
下面是核心代码,主要目的就是求出每帧arrow的旋转角度,这里使用的工具类mouse会实时返回鼠标的x,y轴坐标,封装原理上一篇已经讲过,根据这鼠标的坐标和arrow的坐标,即可得到鼠标的相对于arrow的距离dx和dy,如下图:

而arrow的旋转角度即可以通过dx和dy使用反正切函数得到,这里需要注意几点:

  • 仔细看上面代码中arrow的绘制过程,可知其原点是在中心位置的,所以刚好旋转角度就是画笔的旋转角度;
  • dx和dy是鼠标相对与arrow的坐标,所以图中把坐标系挪动箭头中心是没毛病的;
  • 用atan2,而不是atan,是因为tan值本来就可能是重复的,比如-1/2和1/(-2)两个都是-0.5,无法区分象限,而atan2就可以区分开。

完整实例:一个会跟踪鼠标位置的箭头

window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const mouse = utils.captureMouse(canvas);
const arrow = new Arrow(); arrow.x = canvas.width / 2;
arrow.y = canvas.height / 2; (function drawFrame() {
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
const dx = mouse.x - arrow.x;
const dy = mouse.y - arrow.y; arrow.rotation = Math.atan2(dy, dx);
arrow.draw(context);
}());
};

三角函数

上下运动

终于顺利过渡到三角函数的话题(笑)。三角函数不止有反正切一个应用,下面再看一个例子。
下面是一个ball在上下运动的核心代码,重点就是ball的y轴坐标改变,就是这句:

ball.y = clientY + Math.sin(angle) * range;

利用Math.sin(angle)的取值范围是-1到1,并且会随着angle增大而反复,使ball在一定范围上下运动。
完整例子:一个上下运动的球(可调参数版)

window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const ball = new Ball();
let angle = 0;
// 运动中心
const clientY = 200;
// 范围
const range = 50;
// 速度
const speed = 0.05; ball.x = canvas.width / 2;
ball.y = canvas.height / 2; (function drawFrame() {
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height); ball.y = clientY + Math.sin(angle) * range;
angle += speed;
ball.draw(context);
}());
};

向前运动

只是上下运动不过瘾,那就让圆前进吧,其实就是每帧改变x轴的位置。
核心代码如下,相比前面的上下运动,多了x轴的速度,每帧移动一点就形成了波浪前进的效果。
完整实例:一个波浪运动的球

window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const ball = new Ball();
let angle = 0;
const centerY = 200;
const range = 50;
const xspeed = 1;
const yspeed = 0.05; ball.x = 0;
(function drawFrame() {
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
ball.x += xspeed;
ball.y = centerY + Math.sin(angle) * range;
angle += yspeed;
ball.draw(context);
}());
};

其他示例

其他的应用就不一一讲解,罗列出来一些:

【30分钟学完】canvas动画|游戏基础(2):从零开始画画的更多相关文章

  1. 【30分钟学完】canvas动画|游戏基础(1):理论先行

    前言 本文虽说是基础教程,但这是相对动画/游戏领域来说,在前端领域算是中级教程了,不适合前端小白或萌新.阅读前请确保自己对前端三大件(JavaScript+CSS+HTML)的基础已经十分熟悉,而且有 ...

  2. 【30分钟学完】canvas动画|游戏基础(7):动量守恒与多物体碰撞

    前言 一路沿着本系列教程学习的朋友可能会发现,前面教程中都尽量避免提及质量的概念,很多运动概念也时刻提醒大家这不是真实的物体运动.因为真实的物体运动其实跟质量都是密不可分的,而且质量的引入自然必须提及 ...

  3. 【30分钟学完】canvas动画|游戏基础(4):边界与碰撞

    前言 本系列前几篇中常出现物体跑到画布外的情况,本篇就是为了解决这个问题. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 越界检测 假定物体是个圆形,如图其圆心坐标即是物 ...

  4. 【30分钟学完】canvas动画|游戏基础(6):坐标旋转探究

    前言 本篇主要讲坐标旋转及其应用,这是编程动画必不可少的技术. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 坐标旋转 模拟场景:已知一个中心点(centerX,cent ...

  5. 【30分钟学完】canvas动画|游戏基础(extra1):颜色那些事

    前言 本篇主要讲解关于计算机颜色系统的概念,后续结合一些canvas的应用.因为是"你不知道也没关系"的边缘知识,所以作为本系列教程的扩展,没有兴趣的同学可以跳过. 开始我们万紫千 ...

  6. 【30分钟学完】canvas动画|游戏基础(extra1-1):美图我也行

    前言 本文是接续系列教程的extra1,主要是介绍颜色系统在canvas中的应用. 本来是与extra1一起成文的,因为segmentfault莫名其妙的字数限制bug只能分割放送了. canvas操 ...

  7. 【30分钟学完】canvas动画|游戏基础(5):重力加速度与模拟摩擦力

    前言 解决运动和碰撞问题后,我们为了让运动环境更加自然,需要加入一些环境因子,比如常见的重力加速度和模拟摩擦力. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 重力加速度 ...

  8. 30分钟学玩转RabbitMQ

    最近在学习RabbitMQ,在网上找了不少资料发现都特高端.动辄集群部署,分布式架构什么的,对于一个初学者实在不够友好.心想求人不如求自己,为什么不自己整理一套资料呢?于是<30分钟学玩转Rab ...

  9. 3分钟学完Python,直接从入门到精通

    作为帅气小编,我已经把python一些模块的甩在这儿了qwq,只要你拿到这些干货,包你玩转python,直接冲向"大佬"的段位,如果已经学了C或者C++或者说如果你需要你的一段关键 ...

随机推荐

  1. [DS+Algo] 002 一维表结构

    目录 1. 顺序表 1.1 分类 1.2 实现方式 1.3 扩容问题 1.4 操作 2. 链表 2.1 分类 2.2 链表相关操作 2.3 链表 VS 顺序表 3. 关于代码实现 1. 顺序表 1.1 ...

  2. 使用Oracle12c 以上的PDB创建数据库用户 密码过期的简单处理

    1. 先通过监听查看PDB的名字 Windows 打开命令行: 输入命令 lsnrctl status 一般在如图示的最下面 2. 也可以通过GS的全局配置文件来查看 数据库连接SID信息. C:\P ...

  3. kafka 安装教程

    安装详述: https://www.jianshu.com/p/596f107e901a 3.0:运行:cd 到: D:\Installed_software\Professional\kafka_2 ...

  4. JS事件绑定的两种形式

    第一种: obj.on*=function(){} var btn=document.getElementById('myBtn'); btn.onclick=function(){ alert(1) ...

  5. GmSSL Build with VS2017

    使用背景: 最近研究GB35114, 有关于sip协议部分,exosip的已经编译过,由于gb3511中采用的是国密算法,因此这里记录一下GMSSL在windows下的编译过程以及遇到的错误 详细GM ...

  6. 提高CUI测试稳定性技术

    GUI自动化测试稳定性,最典型的表现形式就是,同样的测试用例在同样的环境上,时而测试通 过,时而测试失败. 这也是影响GUI测试健康发展的一个重要障碍,严重降低了GUI测试的可信性. 五种造成GUI测 ...

  7. Python 入门之格式化输出

    Python 入门之格式化输出 1.格式化 (1)%为占位 (2)%s --- 站字符串的位置(数字.字符串都能够进行填充) name = input('请输入姓名:') age = input('请 ...

  8. Codeforces - 1096G - Lucky Tickets - NTT

    https://codeforc.es/contest/1096/problem/G 把数组分成前后两半,那么前半部分的各个值的表示方案的平方的和就是答案. 这些数组好像可以dp出来. 一开始设dp[ ...

  9. go & nssm

    参考 用go写windows系统服务

  10. arcgis server10.2自带打印模板路径

    找到arcgis server10.2安装目录路径,我的安装路径为C盘,如下: C:\Program Files\ArcGIS\Server\Templates\ExportWebMapTemplat ...