一、canvas应用场景:
1.游戏:很多页面H5游戏都是用canvas绘制出来的。
2.图表
二、如何使用canvas
1.首先在html中创建一个canvas元素,告诉浏览器我要开始绘图了。
<canvas id="myCanvas" width="500" height="500"></canvas>
2.获得canvas元素
var canvas =document.getElementById('myCanvas')
3.获得canvas的上下文
var ctx = canvas.getContext('2d');
因为是在平面绘图,所以是2d而不是3d。
-
这里需要区分两个对象:元素对象和上下文
-
元素对象是canvas元素,相当于我们的画布
-
上下文对象时通过getContext(‘2d’)获取的
-
context
是一个状态机。你可以改变context
的若干状态,而几乎所有的渲染操作,最终的效果与context
本身的状态有关系。比如,调用strokeRect
绘制的矩形边框,边框宽度取决于context
的状态lineWidth
,而后者是之前设置的。
4、canvas模糊
有时候我们的canvas绘制出来的图片放到html 会失真, 这个时候把画布调大, 然后放到html 中就不会出现模糊了。
由此我们可以看出来,canvas其实是位图,它会出现失真的现象。
三、绘制图案
绘制形状
1.线段 moveTo——lineTo
-
其中moveTo就是将画笔移动到某一个点
-
lineTo就是从刚才移动到的点画到另外一个点。
-
不过canvas绘制还需要多两步:
beginPath() // 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径
clothPath() // 闭合路径之后图形绘制命令又重新指向到上下文中
重点:
1.fill和stroke方法都是作用在当前的所有子路径 fill 通过填充路径的内容区域生成实心的图形 stroke 通过线条来绘制图形轮廓。
当你调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()函数。但是调用stroke()时不会自动闭合 。
2.完成一条路径之后要开启另一条路径必须要beginPath()
ctx.lineWidth = 10;
ctx.fillStyle = "yellow";
ctx.beginPath();
ctx.moveTo(50,50);
ctx.lineTo(100,100);
ctx.closePath();
ctx.fill(); // 通过填充路径的内容区域生成实心的图形
ctx.strokeStyle = 'red';
ctx.stroke();//通过线条来绘制图形轮廓。
2.矩形
矩形也有三种方法:
1.rect(x,y,width,height);
2.fillRect(x,y,width,height);
3.strokeRect(x,y,width,height)
4.clearRect(x, y, width, height)
- 清除指定矩形区域,让清除部分完全透明。
由于第一种没有给路径上色,所以我们一般使用后两种,第二个是绘制并填充矩形,第三个是绘制并描边
3.弧形
arc(x, y, r, 起始弧度,结束弧度,弧形的方向 ) 其中0是顺时针 1是逆时针
arc方法将当前点和弧形和起点用一条直线连接
arcTo(x1, y1, x2, y2, r)
6.渐变
createLinearGradient(x1, y1, x2, y2);线性渐变
7. 二次贝塞尔曲线及三次贝塞尔曲线
-
quadraticCurveTo(cp1x, cp1y, x, y)
-
绘制二次贝塞尔曲线,
cp1x,cp1y
为一个控制点,x,y为
结束点。 -
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
-
绘制三次贝塞尔曲线,
cp1x,cp1y
为控制点一,cp2x,cp2y
为控制点二,x,y
为结束点。
二次贝塞尔曲线有一个开始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。
// 二次贝塞尔曲线 对话气泡
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
// 三次贝塞尔曲线 绘制心形
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
8、 clip 裁剪路径
-
绘制图形 有
stroke
和fill
方法,这里介绍第三个方法clip
将当前正在构建的路径转换为当前的裁剪路径。
我们使用 clip()
方法来创建一个新的裁切路径。
默认情况下,canvas 有一个与它自身一样大的裁切路径(也就是没有裁切效果)
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0,0,150,150);
ctx.translate(75,75);
// Create a circular clipping path
ctx.beginPath();
ctx.arc(0,0,60,0,Math.PI*2,true);
ctx.clip();
// draw background
var lingrad = ctx.createLinearGradient(0,-75,0,75);
lingrad.addColorStop(0, '#232256');
lingrad.addColorStop(1, '#143778');
ctx.fillStyle = lingrad;
ctx.fillRect(-75,-75,150,150);
// draw stars
for (var j=1;j<50;j++){
ctx.save();
ctx.fillStyle = '#fff';
ctx.translate(75-Math.floor(Math.random()*150),
75-Math.floor(Math.random()*150));
drawStar(ctx,Math.floor(Math.random()*4)+2);
ctx.restore();
}
}
function drawStar(ctx,r){
ctx.save();
ctx.beginPath()
ctx.moveTo(r,0);
for (var i=0;i<9;i++){
ctx.rotate(Math.PI/5);
if(i%2 == 0) {
ctx.lineTo((r/0.525731)*0.200811,0);
} else {
ctx.lineTo(r,0);
}
}
ctx.closePath();
ctx.fill();
ctx.restore();
}
裁切路径创建之后
所有出现在它里面
的东西才会画出来。在画线性渐变时我们就会注意到这点。然后会绘制出50 颗随机位置分布(经过缩放)的星星,当然也只有在裁切路径里面
的星星才会绘制出来。
四、性能优化
1·、在离屏 canvas上预渲染
相似的图形或者重复的对象
发现自己在每个动画帧上重复了一些相同的绘制操作
,请考虑将其分流到屏幕外的画布
上。 然后,您可以根据需要
频繁地将屏幕外图像渲染到主画布
上,而不必首先重复生成该图像
的步骤.
myEntity.offscreenCanvas = document.createElement("canvas");
myEntity.offscreenCanvas.width = myEntity.width;
myEntity.offscreenCanvas.height = myEntuty.heihgt;
myEntity.offscreenContext = myEntity.offscreenCanvas.getContext("2d");
...
myEntity.render(myEntity.offscreenContext);
2、避免浮点数坐标点 发生子像素渲染, 用整数替代
ctx.drawImage(myImage, 0.3, 0.5);
浏览器为了达到抗锯齿的效果会做额外的运算。为了避免这种情况,请保证在你调用drawImage,用Math.floor()对坐标取整
3、在离屏canvas中缓存图片的不同尺寸时候 不要在用drawImage
时缩放图像
4、使用多层画布去画一个复杂的场景
某些对象需要经常移动或更改 假设您有一个游戏,其UI位于顶部,中间是游戏性动作,底部是静态背景。 在这种情况下,您可以将游戏分成三个<canvas>
层。 UI将仅在用户输入时发生变化,游戏层随每个新框架发生变化,并且背景通常保持不变。
<div id="stage">
<canvas id="ui-layer" width="" height=""/>
<canvas id="game-layer" width="" height=""/>
<canvas id="backgground-layer" width="" height=""/>
<div/>
<style>
#stage {
width: 480px;
height: 320px;
position: relative;
border: 2px solid black
}
canvas { position: absolute; }
#ui-layer { z-index: 3 }
#game-layer { z-index: 2 }
#background-layer { z-index: 1 }
</style>
5、可以避免在每一帧在画布上绘制大图 用CSS设置大的背景图 将它置于画布元素之后
6、 用CSS transforms特性缩放画布 CSS transforms使用GPU,速度更快
最好的情况是不直接缩放画布,或者具有较小的画布并按比例放大,而不是较大的画布并按比例缩小。
7、 如果你的游戏使用画布而且不需要透明 关闭透明度 帮助浏览器进行内部优化
var ctx = canvas.getContext('2d', { alpha: false });
8、有动画,请使用window.requestAnimationFrame()
而非window.setInterval()
浏览器可以优化并行
的动画动作,更合理的重新排列动作序列
,并把能够合并的动作
放在一个渲染周期
内完成,从而呈现出更流畅的动画效果。比如,通过requestAnimationFrame()
,JS动画
能够和CSS动画/变换
或SVG SMIL动画
同步发生。另外,如果在一个浏览器标签页里运行一个动画,当这个标签页不可见
时,浏览器会暂停它,这会减少CPU,内存
的压力,节省电池电量。
9. 避免大量计算造成阻塞, 可以使用 Worker 和拆分任务的方法避免复杂算法阻塞动画运行
10、 将画布的函数调用集合到一起, 不要频繁调度beginPath, closePath, stroke,fill,同时减少调用canvas的api。
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.stroke();
}
====>
context.beginPath();
for (var i = 0; i < points.length - 1; i++) {
var p1 = points[i];
var p2 = points[i+1];
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
}
context.stroke();
11、尽量少改变CANVAS状态机
for (var i = 0; i < STRIPES; i++) {
context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
context.fillRect(i * GAP, 0, GAP, 480);
}
====>
context.fillStyle = COLOR1;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2) * GAP, 0, GAP, 480);
}
context.fillStyle = COLOR2;
for (var i = 0; i < STRIPES / 2; i++) {
context.fillRect((i * 2 + 1) * GAP, 0, GAP, 480);
}
12、局部重绘 渲染画布中的不同点,而非整个新状态
由于 Canvas 的绘制方式是画笔式的,在 Canvas 上绘图时每调用一次 API 就会在画布上进行绘制,一旦绘制就成为画布的一部分。绘制图形时并没有对象保存下来,一旦图形需要更新,需要清除整个画布重新绘制 Canvas 局部刷新的方案:
- 清除指定区域的颜色,并设置 clip(不了解可以看上面的clip 介绍)
- clip() 确定绘制的的裁剪区域,区域之外的图形不能绘制,详情查看 CanvasRenderingContext2D.clip()
- clearRect(*x*, *y*, *width*, *height*) 擦除指定矩形内的颜色,查看 CanvasRenderingContext2D.clearRect()
- 所有同这个区域相交的图形重新绘制
13、尽可能避免 shadowBlur特性 阴影渲染的性能开销通常比较高
14、尽可能避免text rendering
// 一个填充文本的示例
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "48px serif";
ctx.fillText("Hello world", 10, 50);
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "48px serif";
ctx.strokeText("Hello world", 10, 50);
}
15、请谨慎使用大型物理库
16、尝试不同的方法来清除画布(clearRect()
vs. fillRect()
vs. 调整canvas大小)
性能依次提升。