长久以来,web上的动画都是Flash。比如动画广告、游戏等等,基本上都是Flash实现的。Flash是有缺点的,比如需要安装Adobe Flash Player,漏洞多,重量比较大。卡顿和不流畅等等。
HTML5提出了一个新的canvas标签,彻底颠覆了Flas 的主导地位。无论是广告、游戏都可以使用canvas实现了,Canvas是一个轻量级的画布,我们使用Canvas进行JavaScript的编程,不需要增加额外的插件,性能也很好,不卡顿,在手机中也很流畅
1.canvas 基本概念
1.1 canvas 的定义
< canvas > 是h5 新增的标签,它只是图形的容器,也称为画布。
在画布上进行绘制,那么你就需要使用脚本语言(通常是JavaScript)来进行绘制图形。
1.2 canvas 能做什么
- 基础图形的绘制
- 文字的绘制
- 图形的变形和图片的合成
- 图片和视频的处理
- 动画的实现
- 小游戏的制作
1.3 支持canvas 的浏览器
IE 9、Firefox、Opera、Chrome 和 Safari 支持 标签。
注释:IE 8 或更早版本的 IE 浏览器不支持 标签。
1.4 如何判断浏览器是否支持
<canvas id="myCanvas">浏览器不支持canvas</canvas>
如果浏览器不支持canvas标签,里面的文字就会显示出来
2.canvas 基本使用
https://www.runoob.com/html/html5-canvas.html 这是各种规则
3.canvas 总结
3.1 使用总结
- 先创建画布
- 使用你需要图形模板语法
- 要管理好你的图形,否则不小心会出现覆盖现象
3.2 关于beginPath
beginPath方法定义:开始一条路径,或重置当前的路径。
- canvas中的绘制方法(如stroke, fill),遇到stroke 方法, 都会以“上一次beginPath”之后的所有路径为基础进行绘制。
- 不管你用moveTo把画笔移动到哪里,只要不重新beginPath,那你一直都是在画一条路径(注:此处『一条路径』并非指连在一起)。所以每次使用stroke方法,都是把**“上一次beginPath”**之前的路径当前颜色的画笔再画一下。
例如以下代码块,先开始画出第一条红色线。没有遇见beginPath, 但是再次遇见stroke,造成用设置为绿色的笔再次描绘一次第一条红色线。每次都是如此,直到遇见下个beginPath 结束。
context.beginPath();
context.moveTo(100,100);
context.lineTo(200,100);
context.strokeStyle = "red";
context.stroke();
context.moveTo(100,200);
context.lineTo(200,200);
context.strokeStyle = "green";
context.stroke();
context.beginPath();
context.moveTo(100,300);
context.lineTo(200,300);
context.strokeStyle = "yellow";
context.stroke();
3.3 关于save 方法和 restore 方法 的定义和总结
- save()保存画布的所有状态。每次保存都相当于压栈,将当前保存的状态为画布状态
- restore()恢复 canvas状态的。将栈里面的状态释放,选取你压入的状态,或者(多次调用)一步步返回到初始化状态。
可能保存的状态有:
当前应用的变形(即移动,旋转和缩放,见下) 以及下面这些属性:strokeStyle, fillStyle, globalAlpha,
lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset,
shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor,
globalCompositeOperation, font, textAlign, textBaseline, direction,
imageSmoothingEnabled
3.4 属性的叠加(只有转换方法会出现)
涉及以下转换方法
- scale() 缩放当前绘图至更大或更小。
- rotate() 旋转当前绘图。
- translate() 重新映射画布上的 (0,0) 位置。
- transform() 替换绘图的当前转换矩阵。
- setTransform() 将当前转换重置为单位矩阵。然后运行 transform()。
setTransform() 将当前转换重置为单位矩阵。然后运行 transform()。
例如4.3 滚动车轮实战,如果你不恢复初始画布状态,那么translate属性会一直叠加,直到往下跑出画布。其他全部属性也是这样。例如下面的scale属性。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
您的浏览器不支持 HTML5 canvas 标签。
</canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.strokeRect(5,5,25,15);
ctx.scale(2,2);
ctx.strokeRect(5,5,25,15);
ctx.scale(2,2);
ctx.strokeRect(5,5,25,15);
</script>
</body>
</html>
3.5 canvas中 translate rotate 执行顺序问题
执行顺序是谁在前面,谁后执行。像队列里面的出对入队
3.6 beginPath 和 save区别
beginPath 是从新开始一条新线,只是路径的方法。使用的环境状态还是当前画布设置的环境状态
save 方法是对各种环境变量的保存,保存当前画布环境状态
4.实战
4.1 碰壁反弹小球
功能需求: 随机出现小球,且碰到见墙壁要进行反弹
实现思路:
- 需要创建小球的实体类,这样每个小球都有自己专有属性和方法
- 初始化实体类方法:渲染小球出现
- 更新小球路径方法:渲染小球出现的位置,以及判断是否需要折返
- 每次更新都需要清空画布,更新小球位置,渲染小球出现
<body>
<canvas id='can' style="border: 1px solid black;"></canvas>
<style>
* {
margin: 0px;
padding: 0px;
}
html,
body {
width: 100%;
height: 100%;
}
</style>
<script>
let vas = document.getElementById('can')
console.log('document.clientWidth', document.body.clientWidth)
vas.width = document.body.clientWidth - 20
vas.height = document.body.clientHeight - 10
var ctx = vas.getContext("2d");
let ballArr = []
// 设置球
class Ball {
constructor(x, y, r, xstep, ystep) {
this.x = x
this.y = y
this.r = r
this.xstep = xstep
this.ystep = ystep
}
// 显示球
init() {
ctx.beginPath()
ctx.fillStyle = "#FF0000";
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fill()
}
// 运动
update() {
this.x += this.xstep
this.y += this.ystep
if (this.x < this.r || this.x > (vas.width - 5)) {
this.xstep = - this.xstep
}
if (this.y < this.r || this.y > (vas.height - 5)) {
this.ystep = - this.ystep
}
}
}
function init() {
for (let index = 0; index < 20; index++) {
let x = Number((Math.random()) * 500 + 5)
let y = Number((Math.random()) * 500 + 5)
let r = 5
let xstep = Number((Math.random()) * 10 - 5)
let ystep = Number((Math.random()) * 10 - 5)
let ball = new Ball(x, y, r, xstep, ystep)
ball.init()
ballArr.push(ball)
}
}
init()
setInterval(() => {
ctx.clearRect(0, 0, vas.width, vas.height)
for (let index = 0; index < ballArr.length; index++) {
ballArr[index].update()
ballArr[index].init()
}
}, 10)
</script>
</body>
4.2 小球连线
功能需求: 随机出现小球,碰到见墙壁要进行反弹,小球在一定的距离内连线
实现思路:
- 需要创建小球的实体类,这样每个小球都有自己专有属性和方法
- 初始化实体类方法:渲染小球出现
- 更新小球路径方法:渲染小球出现的位置,以及判断是否需要折返
- 判断小球连线方法:需要从当前位置进行循环来判断,全部小球循环,否则出现两个线
- 每次更新都需要清空画布,更新小球位置,进行判断是否需要连线,渲染小球出现
<body>
<canvas id='can' style="border: 1px solid black;"></canvas>
<style>
* {
margin: 0px;
padding: 0px;
}
html,
body {
width: 100%;
height: 100%;
}
</style>
<script>
let vas = document.getElementById('can')
console.log('document.clientWidth', document.body.clientWidth)
vas.width = document.body.clientWidth - 20
vas.height = document.body.clientHeight - 10
var ctx = vas.getContext("2d");
let ballArr = []
// 设置球
class Ball {
constructor(x, y, r, xstep, ystep) {
this.x = x
this.y = y
this.r = r
this.xstep = xstep
this.ystep = ystep
// 获取当前
this.index = ballArr.length
}
// 显示球
init() {
ctx.beginPath()
ctx.fillStyle = "red";
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fill()
}
// 运动
update() {
this.x += this.xstep
this.y += this.ystep
if (this.x < this.r || this.x > (vas.width - 5)) {
this.xstep = - this.xstep
}
if (this.y < this.r || this.y > (vas.height - 5)) {
this.ystep = - this.ystep
}
}
// 小球连线
line() {
for (let temp = this.index; temp < ballArr.length; temp++) {
if (Math.abs(this.x - ballArr[temp].x) < 150 && Math.abs(this.y - ballArr[temp].y) < 150) {
ctx.strokeStyle = `rgba(255,0,0,${10 / (Math.sqrt(Math.pow(this.x - ballArr[temp].x, 2) + Math.pow(this.y - ballArr[temp].y, 2)))})`
ctx.beginPath()
ctx.moveTo(this.x, this.y)
ctx.lineTo(ballArr[temp].x, ballArr[temp].y)
ctx.stroke()
}
}
}
}
function init() {
for (let index = 0; index < 30; index++) {
let x = (Math.random()) * 500 + 5
let y = (Math.random()) * 500 + 5
let r = 5
let xstep = (Math.random()) * 10 - 5
let ystep = (Math.random()) * 10 - 5
let ball = new Ball(x, y, r, xstep, ystep)
ball.init()
ballArr.push(ball)
}
}
init()
setInterval(() => {
ctx.clearRect(0, 0, vas.width, vas.height)
// 先全部位置确定,再进行连线显示
for (let index = 0; index < ballArr.length; index++) {
ballArr[index].update()
}
for (let index = 0; index < ballArr.length; index++) {
ballArr[index].line()
ballArr[index].init()
}
}, 10)
</script>
</body>
4.3 滚动车轮
功能需求: 车轮往前行走
实现思路:
- 使用rotate,车轮以中心点滚动
- 谁用translate,滚动后平移
- 使用save,restore方法,回到初始化属性设置,消除旋转平移的影响
涉及到的问题:
- 变换属性会叠加问题: 一直设置变换属性,这个时候是一直叠加的,需要通过save,restore 恢复到未设置之前。
- translate, rotate 执行顺序问题:谁在前面反而后执行
- canvas 绘制完毕,图片还没加载成功问题:所以使用onload 当图片加载 成功再绘制
- rotate 旋转围绕的圆心:是以画布的左上角为圆心旋转
<body>
<style>
canvas {
border: 1px solid #000;
}
</style>
<canvas width="1200" height="500"></canvas>
<script>
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var image = new Image();
image.src = "images/lunzi.png";
image.onload = function() {
// 定时器
// 旋转的度数
var deg = 0;
// 位置
var x = 200;
setInterval(function() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
deg += 0.01;
x++;
// 备份
ctx.save();
// 平移
ctx.translate(x, 200);
// 旋转
ctx.rotate(deg);
ctx.drawImage(image, -374 / 2, -374 / 2);
// 恢复
ctx.restore();
}, 10)
}
</script>
</body>
4.4 刮刮乐实现
功能需求:挂出奖
实现思路:
- 将文字隐藏在canvas 下面
- beginPath 开始画,并消除绘画的影响
- arc进行圆形返回消除
- 按下移动就涂掉,松开就停止涂掉
<body>
<style>
div {
border: 1px solid #000;
width: 250px;
height: 60px;
font-size: 40px;
line-height: 60px;
text-align: center;
position: relative;
user-select: none;
}
canvas {
position: absolute;
left: 0;
top: 0;
}
</style>
<div>
特等奖
<canvas width="250" height="60"></canvas>
</div>
<script>
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#333";
ctx.fillRect(0, 0, 250, 60);
// 设置新画上的元素,实际上就是擦除之前的元素
ctx.globalCompositeOperation = "destination-out";
// 鼠标按下
canvas.onmousedown = function() {
// 拖动
canvas.onmousemove = function(event) {
// 画圆
ctx.beginPath();
ctx.arc(event.offsetX, event.offsetY, 10, 0, 7, false);
ctx.fill()
}
}
canvas.onmouseup = function() {
canvas.onmousemove = function() {}
}
</script>
</body>