先直接展示下最终效果,代码已上传至 码上掘金
本章将会主要介绍关于 Canvas
的基础知识,看完之后应该就能理解最终的代码了。
Canvas
最初由 Apple
于 2004
年 引入,用于 Mac OS X Webkit
组件,为仪表盘小组件和 Safari
浏览器等应用程序提供支持。后来,它被 Gecko
内核的浏览器(尤其是 Mozilla Firefox
),Opera
和 Chrome
实现,并被网页超文本应用技术工作小组提议为下一代的网络技术的标准元素(HTML5新增元素)。
Canvas
提供了非常多的 JavaScript绘图 API
(比如:绘制路径、矩形、圆、文本和图像等方法),与 <canvas>
元素可以绘制各种 2D 图形。
Canvas API 主要聚焦于 2D 图形。当然也可以使用 <canvas>
元素对象的 WebGL API 来绘制 2D 和 3D 图形。
Canvas 可用于动画、游戏画面、数据可视化、图片编辑以及实现视频处理等方面。
Canvas
的浏览器兼容性还是不错的,能兼容 e9
及其以上版本
Canvas
提供的功能更原始,适合像素处理,动态渲染和数据量大的绘制,如:图片编辑、热力图、炫光尾迹特效等。Canvas
非常适合图像密集的游戏开发,适合频繁重绘许多的对象。Canvas
能够以 .png
或 .jpg
格式保存结果图片,适合对图像进行像素级的处理。Canvas
数量多,而导致内存占用超出了手机的承受能力,导致浏览器崩溃。Canvas
绘图只能通过 JavaScript
脚本操作 (all in js)
。Canvas
是由一个个像素点构成的图形,放大会使图形变得颗粒状和像素化,导致模糊。Canvas
支持两种方式来绘制矩形:"矩形方法" 和 "路径方法"。
路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合,
除了矩形,其他的图形都是通过一条或者多条路径组合而成的
通常我们会通过众多的路径来绘制复杂的图形。
下面是常见的绘制方法:
fillRect(x, y, width, height)
: 绘制一个填充的矩形strokeRect(x, y, width, height)
: 绘制一个矩形的边框clearRect(x, y, width, height)
: 清除指定矩形区域,让清除部分完全透明。Canvas
绘制一个矩形:
html<canvas id="tutorial" width="300" height="300px">
你的浏览器不兼容Canvas,请升级您的浏览器!
</canvas>
<script>
window.onload = function() {
let canvasEl = document.getElementById('tutorial')
if(!canvasEl.getContext){
return
}
let ctx = canvasEl.getContext('2d') // 2d | webgl
ctx.fillRect(10,10, 100, 50) // 单位也是不用写 px
}
</script>
效果:
代码解析:
忽略做兼容性的几行代码,上面的代码最终通过 ctx.fillRect(10,10,100,50)
在坐标为 (10,10)
的位置,绘制了一个长 100
宽 50
的实心矩形(默认为黑色)
使用路径绘制图形的步骤:
beginPath
)。arc
绘制圆弧 、lineTo
画直线 )。closePath
, 不是必须)。stroke
) 或 填充路径区域(fill
) 来渲染图形。以下是绘制路径时,所要用到的函数
代码实现:
html<canvas id="tutorial" width="300" height="300px">
你的浏览器不兼容Canvas,请升级您的浏览器!
</canvas>
<script>
window.onload = function () {
let canvasEl = document.getElementById("tutorial");
if (!canvasEl.getContext) {
return;
}
let ctx = canvasEl.getContext("2d"); // 2d | webgl
// 1.创建一个路径
ctx.beginPath();
// 2.绘图指令
// ctx.moveTo(0, 0)
// ctx.rect(100, 100, 100, 50);
ctx.moveTo(100, 100);
ctx.lineTo(200, 100);
ctx.lineTo(200, 150);
ctx.lineTo(100, 150);
// 3.闭合路径
ctx.closePath();
// 4.填充和描边
ctx.stroke();
};
</script>
lineTo
和 arc
两个函数结合既能绘制直线也能绘制圆弧,因此路径方法还可以绘制许多图形,比如三角形、菱形、梯形、椭圆形、圆形等等。。。
效果:
如果我们想要给图形上色,有两个重要的属性可以做到:
fillStyle = color
: 设置图形的填充颜色,需在 fill()
函数前调用。strokeStyle = color
: 设置图形轮廓的颜色,需在 stroke()
函数前调用。一旦设置了 strokeStyle
或者 fillStyle
的值,那么这个新值就会成为新绘制的图形的默认值。
如果你要给图形上不同的颜色,你需要重新设置 fillStyle
或 strokeStyle
的
除了可以绘制实色图形,我们还可以用 canvas 来绘制半透明的图形。
1. 方式一:strokeStyle
和 fillStyle
属性结合 RGBA
:
js// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";
2. 方式二:globalAlpha
属性
js// 针对于Canvas中所有的图形生效
ctx.globalAlpha = 0.3
// 2.修改画笔的颜色
// ctx.fillStyle = 'rgba(255, 0, 0, 0.3)'
ctx.fillRect(0,0, 100, 50) // 单位也是不用写 px
ctx.fillStyle = 'blue'
ctx.fillRect(200, 0, 100, 50)
ctx.fillStyle = 'green' // 关键字, 十六进制, rbg , rgba
ctx.beginPath()
ctx.rect(0, 100, 100, 50)
ctx.fill()
globalAlpha = 0 ~ 1
✓ 这个属性影响到 canvas
里所有图形的透明度
✓ 有效的值范围是 0.0
(完全透明)到 1.0
(完全不透明),默认是 1.0
。
调用 lineTo()
函数绘制的线条,是可以通过一系列属性来设置线的样式。
常见的属性有:
lineWidth = value
: 设置线条宽度。lineCap = type
: 设置线条末端样式。lineJoin = type
: 设定线条与线条间接合处的样式。lineWidth
1.0px
,不需单位。( 零、负数、Infinity
和 NaN
值将被忽略)(3,1)
到 (3,5)
,宽度是 1.0
的线条,你会得到像第二幅图一样的结果。
路径的两边各延伸半个像素填充并渲染出 1
像素的线条(深蓝色部分)
两边剩下的半个像素又会以实际画笔颜色一半色调来填充(浅蓝部分)
实际画出线条的区域为(浅蓝和深蓝的部分),填充色大于 1
像素了,这就是为何宽度为 1.0
的线经常并不准确的原因。
1px
的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5 ,1)
到 (3.5, 5)
的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0
的线条。lineCap
: 属性的值决定了线段端点显示的样子。它可以为下面的三种的其中之一:
butt
截断,默认是 butt。
round
圆形
square
正方形
如下图所示:
lineJoin
: 属性的值决定了图形中线段连接处所显示的样子。它可以是这三种之一:
round
圆形bevel
斜角miter
斜槽规,默认是 miter
。如下图所示:
canvas
提供了两种方法来渲染文本:
fillText(text, x, y [, maxWidth])
(x,y)
位置,填充指定的文本strokeText(text, x, y [, maxWidth])
(x,y)
位置,绘制文本边框文本的样式(需在绘制文本前调用)
font = value
: 当前绘制文本的样式。这个字符串使用和 CSS font
属性相同的语法。默认的字体是:10px sans-serif
。
textAlign = value
:文本对齐选项。可选的值包括:start
, end
, left
, right
or center
. 默认值是 start
textBaseline = value
:基线对齐选项。可选的值包括:
top
hanging
middle
alphabetic
ideographic
bottom
。✓ 默认值是 alphabetic
。
下面是一个绘制文本你的例子:
html<canvas id="tutorial" width="300" height="300px">
你的浏览器不兼容Canvas,请升级您的浏览器!
</canvas>
<script>
window.onload = function () {
let canvasEl = document.getElementById("tutorial");
if (!canvasEl.getContext) {
return;
}
let ctx = canvasEl.getContext("2d"); // 2d | webgl
ctx.font = "60px sen-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.strokeStyle = "red";
ctx.fillStyle = "red";
// 将字体绘制在 100, 100 这个坐标点
ctx.fillText("Ay", 100, 100);
// ctx.strokeText("Ay", 100, 100);
};
</script>
绘制图片,可以使用 drawImage
方法将它渲染到 canvas
里。drawImage
方法有三种形态:
drawImage(image, x, y)
image
是 image
或者 canvas
对象,x
和 y
是其在目标 canvas
里的起始坐标。drawImage(image, x, y, width, height)
2
个参数:width
和 height
,这两个参数用来控制 当向 canvas
画入时应该缩放的大小drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
canvas
的引用。其它 8
个参数最好参照下边的图解来理解,前 4
个是定义图像源的切片位置和大小,后 4
个则是定义切片的目标显示位置和大小。(剪切)HTMLImageElement
:这些图片是由 Image()
函数构造出来的,或者任何的 <img>
元素。HTMLVideoElement
:用一个 HTML
的 <video>
元素作为你的图片源,可以从视频中抓取当前帧作为一个图像。HTMLCanvasElement
:可以使用另一个 <canvas>
元素作为你的图片源。Canvas
绘画状态是当前绘画时所产生的样式和变形的一个快照,Canvas
在绘画时,会产生相应的绘画状态,其实我们是可以将某些绘画的状态存储在栈中来为以后复用,Canvas
绘画状态的可以调用 save
和 restore
方法是用来保存和恢复,这两个方法都没有参数,并且它们是成对存在的。
保存和恢复(Canvas)
绘画状态
save()
:保存画布 (canvas
) 的所有绘画状态restore()
:恢复画布 (canvas
) 的所有绘画状态Canvas
绘画状态包括:
strokeStyle
, fillStyle
, globalAlpha
, lineWidth
, lineCap
, lineJoin
, miterLimit
, shadowOffsetX
, shadowOffsetY
, shadowBlur
, shadowColor
, font
, textAlign
, textBaseline
......clipping path
)translate
方法,它用来移动 canvas
和它的原点到一个不同的位置。
translate(x, y)
x
是左右偏移量,y
是上下偏移量(无需单位)。canvas
原点的好处
translate
方法,那么所有矩形默认都将被绘制在相同的(0,0)
坐标原点。translate
方法可让我们任意放置图形,而不需要手工一个个调整坐标值。移动矩形案例一:形变( 没有保存状态)
html<script>
///1.形变( 没有保存状态)
ctx.translate(100, 100);
ctx.fillRect(0, 0, 100, 50); // 单位也是不用写 px
ctx.translate(100, 100);
ctx.strokeRect(0, 0, 100, 50);
</script>
效果:
移动矩形案例一:形变(保存形变之前的状态)
HTML<script>
// 2.形变(保存形变之前的状态)
ctx.save();
ctx.translate(100, 100);
ctx.fillRect(0, 0, 100, 50); // 单位也是不用写 px
ctx.restore(); // 恢复了形变之前的状态( 0,0)
ctx.save(); // (保存形变之前的状态)
ctx.translate(100, 100);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 50, 30);
ctx.restore();
</script>
rotate方法,它用于以原点为中心旋转 canvas,即沿着 z轴 旋转。
rotate(angle)
(angle)
,它是顺时针方向,以弧度为单位的值。JS
表达式:弧度= ( Math.PI / 180 )
* 角度 ,即 **1
角度 = Math.PI/180
** 个弧度。旋转 90°:Math.PI / 2
;旋转 180°:Math.PI
;旋转 360°:Math.PI * 2
;旋转 -90°:-Math.PI / 2
;canvas
的原坐标点,如果要改变它,我们需要用到 translate
方法。html<SCRIPT>
// 保存形变之前的状态
ctx.save()
// 1.形变
ctx.translate(100, 100)
// 360 -> Math.PI * 2
// 180 -> Math.PI
// 1 -> Math.PI / 180
// 45 -> Math.PI / 180 * 45
ctx.rotate(Math.PI / 180 * 45)
ctx.fillRect(0, 0, 50, 50)
// ctx.translate(100, 0)
// ctx.fillRect(0, 0, 50, 50)
// 绘图结束(恢复形变之前的状态)
ctx.restore()
ctx.save()
ctx.translate(100, 0)
ctx.fillRect(0, 0, 50, 50)
ctx.restore()
// ....下面在继续写代码的话,坐标轴就是参照的是原点了
<SCRIPT>
scale(x, y)
方法可以缩放画布。可用它来增减图形在 canvas
中的像素数目,对图形进行缩小或者放大。x
为水平缩放因子,y
为垂直缩放因子,也支持负数。
html<script>
// 保存形变之前的状态
ctx.save()
// 1.形变
ctx.translate(100, 100) // 平移坐标系统
ctx.scale(2, 2) // 对坐标轴进行了放大(2倍)
ctx.translate(10, 0) // 10px -> 20px
ctx.fillRect(0, 0, 50, 50)
// 绘图结束(恢复形变之前的状态)
ctx.restore()
// ....下面在继续写代码的话,坐标轴就是参照的是原点了
</script>
jswindow.onload = function () {
let canvasEl = document.getElementById("tutorial");
if (!canvasEl.getContext) {
return;
}
let ctx = canvasEl.getContext("2d"); // 2d | webgl
let sun = new Image();
sun.src = "https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/640b4df0a5074e9a9bf4777fdf1fd74e~tplv-k3u1fbpfcp-watermark.image";
let earth = new Image();
earth.src = "https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4ad71733e934b818a52bcfea56a683f~tplv-k3u1fbpfcp-watermark.image";
let moon = new Image();
moon.src = "https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/05bc3992bd5044448f029b7d68049b38~tplv-k3u1fbpfcp-watermark.image";
requestAnimationFrame(draw);
/**
1秒钟会回调 61次
*/
function draw() {
console.log("draw");
ctx.clearRect(0, 0, 300, 300);
ctx.save();
// 1.绘制背景
drawBg();
// 2.地球
drawEarth();
ctx.restore();
requestAnimationFrame(draw);
}
function drawBg() {
ctx.save();
ctx.drawImage(sun, 0, 0); // 背景图
ctx.translate(150, 150); // 移动坐标
ctx.strokeStyle = "rgba(0, 153, 255, 0.4)";
ctx.beginPath(); // 绘制轨道
ctx.arc(0, 0, 105, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
function drawEarth() {
let time = new Date();
let second = time.getSeconds();
let milliseconds = time.getMilliseconds();
ctx.save(); // earth start
ctx.translate(150, 150); // 中心点坐标系
// 地球的旋转
// Math.PI * 2 一整个圆的弧度
// Math.PI * 2 / 60 分成 60 份
// Math.PI * 2 / 60 1s
// Math.PI * 2 / 60 / 1000 1mm
// 1s 1mm
// Math.PI * 2 / 60 * second + Math.PI * 2 / 60 / 1000 * milliseconds
ctx.rotate(
((Math.PI * 2) / 10) * second +
((Math.PI * 2) / 10 / 1000) * milliseconds
);
ctx.translate(105, 0); // 圆上的坐标系
ctx.drawImage(earth, -12, -12);
// 3.绘制月球
drawMoon(second, milliseconds);
// 4.绘制地球的蒙版
drawEarthMask();
ctx.restore(); // earth end
}
function drawMoon(second, milliseconds) {
ctx.save(); // moon start
// 月球的旋转
// Math.PI * 2 一圈 360
// Math.PI * 2 / 10 1s(10s一圈)
// Math.PI * 2 / 10 * 2 2s(10s一圈)
// Math.PI * 2 / 10 / 1000 1mm 的弧度
// 2s + 10mm = 弧度
// Math.PI * 2 / 10 * second + Math.PI * 2 / 10 / 1000 * milliseconds
ctx.rotate(
((Math.PI * 2) / 2) * second +
((Math.PI * 2) / 2 / 1000) * milliseconds
);
ctx.translate(0, 28);
ctx.drawImage(moon, -3.5, -3.5);
ctx.restore(); // moon end
}
function drawEarthMask() {
// 这里的坐标系是哪个? 圆上的坐标系
ctx.save();
ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
ctx.fillRect(0, -12, 40, 24);
ctx.restore();
}
};
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!