本周我讲课,一开始不知道讲什么,觉得canvas听不错的就讲canvas,为了这次讲讲个canvas,我特意学了基本满三天的canvas;
一般网页如果做炫酷粒子动画的话,那肯定很花费资源,而canvas能大大减少这中资源消耗,当然,如果canvas动画写的太多的话那肯定也会花费大量资源消耗,但是绝对要比利用盒子做粒子动画要节省资源很多
canvas基本介绍
历史上,canvas最早是由Apple Inc. 提出的,在Mac OS Xwebkit中创建控制板组件使用,而在canvas称为HTML草案及标准之前,我们是通过一些替代方式去绘图的,比如为人所诟病的Flash,以及非常强大的SVG(ScalableVector Graphics,可伸缩的矢量标记图),还有只能在IE(IE 5.0以上的版本)中使用的VML(Vector Markup Language,矢量可标记图)。甚至于有些前端可以使用div+css来完成绘图。
总的来说,没有canvas的时候,在浏览器绘制图形是比较复杂的,而在canvas出现之后,绘制2D图形相对变得容易了。NOTE: 用div绘制一些简单的图形,如矩形,圆形,三角形,梯形,倒也算是没那么复杂。 但canvas也有缺点。因为canvas本质上是一个与 分辨率相关 的 位图画布,也就注定了在不同分辨率下,canvas绘制的内容显示的时候会有所不同。此外,canvas绘制的内容 不属于任何DOM元素 ,在浏览器的元素查看器中也找不到,那自然无法检测鼠标点击了canvas中的哪个内容,很显然,这两方面,canvas都是不如SVG的
举个例子:如果使用CSS设置canvas元素的尺寸,那可能会导致绘制出来的图形变得扭曲,如长方形变正方形,圆形变椭圆等,这是因为画布尺寸和元素尺寸是不一样的,画布会自动适应元素的尺寸,如果二者是成比例的,那么画布就会等比例缩放,不会出现扭曲。
这么说来,canvas有这么明显的缺点,那直接使用SVG岂不是更好? No,听过一句话吗?没有完美的方案,只有适不适合. SVG是基于XML的,那么就说明,SVG里面的元素都可以认为是 DOM元素 ,可以启用DOM操作,同时,SVG中每个绘制的图像均被视为对象,若SVG对象属性变化,浏览器会自动重现图形.
以上是SVG的优势,但通过这个优势,我们也能发现一些问题:
通常,过度使用DOM的应用都会变得很慢,所以,复杂的SVG会导致渲染速度变慢。但是像地图这类的应用,首选是SVG。
浏览器的重排发生在浏览器窗口发生变化,元素尺寸位置变化,字体变化等等。
即使可以启用DOM操作,但DOM操作的代价还是比较昂贵的(DOM和JS的实现是分开的)。 回到主题。 canvas是通过JavaScript进行2D图形的绘制,而 canvas签本身是没有任何绘制能力的,它仅仅是一个容器。在绘制时,canvas是逐像素的进行渲染的,一旦图形绘制完成,该元素就不再被浏览器所关注(脚本执行结束,绘制的图形也不属于DOM)。
值得注意的是,在HTML标准(whatwg标准)中明确的指出: Authors should not use the canvas element in a document when a more suitable element is available. 所以,不要滥用元素。
canvas目前几乎被所有的浏览器支持,但是IE 9.0 之前的版本不支持 canvas元素
canvsa基本API
- 主要分为颜色、样式、阴影
- 线条样式
- 矩形
- 路径
- 转换
- 文本
- 图像绘制
- 像素操作
- 合成
- 其他
我主要讲矩形,路径,线条,文本这几种,本周目前就学了这点,但也可做出酷炫动画了
基本样式,颜色
填充颜色
fillStyle
描边颜色
strokeStyle()
画笔大小
lineWidth
lineCap
可选’butt | round | square’
效果:线段尽头的样式
线段连接处的样式
lineCap可选‘butt | round | square’
线性渐变
let gradient = ctx.createLinearGradient(0,0,1300,900);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'blue');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
矩形
1.绘制一个填充矩形
fillRect(x, y, width, height)
填充一个矩形,默认式黑色填充
2.绘制一个矩形边框
strokeRect(x, y, width, height)
画一个描边矩形,默认式黑色
3.清除指定矩形区域
fillRect(x, y, width, height)
清除指定矩形区间的所有动画效果,
路径
1.beginPath()
新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。
2.closePath():
闭合路径之后图形绘制命令又重新指向到上下文中
3.stroke():
通过线条来绘制图形轮廓
4.fill():
通过填充路径的内容区域生成实心的图形。
5.moveTo(x,y)
将画笔“抬起来”移到某个位置
6.lineTo(x,y)
将画笔在画布上滑倒某个位置
注意一条路径一定要有 beginPath() 开始,由closePath()结束,如果不这样写的话他会分不清哪个是哪个路径,画动画的时候会产生意想不到的错误效果
圆形
arc(x,y,半径,开始角度,结束角度,方向默认顺时针)
画一个以(x,y)为圆心的以半径的圆弧,从开始 开始到结束,按照 给定的方向(默认为顺时针)来生成
arcTo(x1,y1,x2,y2,radiu)
根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点
二次贝塞尔曲线及三次贝塞尔曲线
quadraticCurveTo(cp1x, cp1y, x, y)
绘制二次贝塞尔曲线,cp1x,cp1y 为一个控制点,x,y 为结束点。
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。
- 关于这个单说的话比较难理解,建议看看动态视频,自己试试,一般可以将折线编成有弧度的线
文字
文字默认是左下角对齐
绘制实心
fillText(text,x,y,maxWidth);
绘制空心文字
strokeText(text, x, y, maxWidth);
文字纵向基准
textBaseline=‘top | hanging | middle | alphabetic | ideographic | bottom’
文字横向基准
textAlign=‘start | left | center | right |end’
canvas注意事项
- canvas通过width,height或js修改画布像素大小,
不加px
,用px只是将其横向或是纵向放大,会造成canvas模糊.不要用css设置canvas宽高,这只是对长度和宽度的放大缩小,图像会发生变形 通过canvas.getContext判断浏览器是否支持canvas
- 在画画是如果更改canvas的宽高canvas内容会被清除
- 受到屏幕分辨率影响
- canvas 的默认宽度是300px,默认高度是150px。
- 暂时只有
IE 9 以上
才支持 canvas
附上一份自己写的代码,可以粘贴复制看看什么效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
* {
margin: 0 !important;
}
#canvas {
position: fixed;
background-color: black;
}
</style>
<body>
<canvas id="canvas"></canvas>
</body>
<script>
let canavs = document.getElementById('canvas')
let ctx = canavs.getContext('2d');
console.log(ctx)
function init() {
canavs.width = window.innerWidth * devicePixelRatio / 2;
canavs.height = window.innerHeight * devicePixelRatio / 2;
}
function getRandom(begin, end) {
return Math.floor(Math.random() * (end - begin) + begin);
}
class Point {
constructor() {
this.r = 3;
this.x = getRandom(this.r, canavs.width - this.r);
this.y = getRandom(this.r, canavs.height - this.r);
this.speedx = getRandom(-10, 10) / 10;
this.speedy = getRandom(-10, 10) / 10;
}
draw() {
this.x += this.speedx;
this.y += this.speedy;
if (this.x > canavs.width || this.x < 0) {
this.speedx = -this.speedx;
}
if (this.y > canavs.height || this.y < 0) {
this.speedy = -this.speedy;
}
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, true);
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath()
}
}
class Linner {
constructor(num = 10, maxdis = 800) {
this.points = new Array(num).fill(0).map(() => new Point());
this.maxdis = maxdis;
}
draw() {
requestAnimationFrame(() => {
this.draw();
})
ctx.fillStyle = 'rgba(0,0,0,0.01)'
ctx.fillRect(0, 0, canavs.width, canavs.height);
// ctx.clearRect(0, 0,canvas.width, canavs.height);
for (let i = 0; i < this.points.length; i++) {
const pi = this.points[i];
for (let j = i + 1; j < this.points.length; j++) {
const pj = this.points[j];
const d = Math.sqrt((pi.x - pj.x) ** 2 + (pi.y - pj.y) ** 2);
const dXY = Math.sqrt((pi.x - x) ** 2 + (y - pj.y) ** 2);
if (d > this.maxdis) {
continue;
}
ctx.beginPath();
ctx.moveTo(pi.x, pi.y);
ctx.lineTo(pj.x, pj.y);
if (dXY< this.maxdis) {
ctx.moveTo(pi.x, pi.y);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.strokeStyle = 'red';
ctx.stroke();
}
pi.draw()
}
}
}
init()
let x, y;
window.onmousemove = e => { x = e.clientX; y = e.clientY }
let linner = new Linner(100, 150);
linner.draw()
</script>
</html>