整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

HTML5(十)-Canvas 与 SVG 区别

为一名前端攻城狮,Canvas 和 SVG 对于我们并不陌生,canvas 是 HTML5 提供的新元素,而 svg 存在的时间要比 canvas 长很多,svg 并不属于 html,最初的 svg 是由 XML 定义的,在 html 5 中 canvas 与 svg 看着相似,其实不同。

一、基本介绍

Canvas

  • 通过 js 来绘制 2D图形。
  • canvas 图像单位是像素。
  • canvas 图像绘制完毕之后,浏览器将不再关注它,如果位置发生变换,就需要重新绘制。

SVG

  • svg 使用 XML 描述的2D图像。
  • svg 是基于 xml 的,所以 svg 中绘制图形还是使用的元素,js 给元素任意添加事件。
  • svg 绘制的图像是一个对象,如果对象的属性发生改变,浏览器将重新绘制图形。

二、SVG与Canvas比较

  1. svg 是一种矢量图,而 canvas 依赖于分辨率。所以 svg 放大不会失真,但是 canvas 绘制的图形会失真。
  2. svg 支持事件处理器,而 canvas 不支持事件处理器。
  3. svg 中的文字独立于图像,文字可保留,可编辑和可搜索,canvas 的文本渲染能力弱。
  4. canvas 适合图像密集型的游戏,频繁地重绘图像,svg 绘制的复杂度高时减慢渲染的速度。
  5. canvas 绘制的图形可以多种格式 (jpg、png) 保存图片,但是 svg 绘制的只能以 .svg 格式保存,使用时可以引入 html 文件。
  6. canvas 适合开发游戏,svg 不适合游戏应用。

二、如何应用

2.1、功能上来说

canvas 是一个画布,绘制出来的图形是位图,因此 canvas 可以绘制图片,在实际应用中,由于渲染性能高,所以大型游戏开发都用的 canvas 。除此之外,还有统计中常见的柱状图、饼图、雷达图等也使用的 canvas 。而 svg 绘制的是矢量图,放大后不会失真,所以很适合做地图。

2.2、操作方面讲

canvas 绘制的图形,只能给 canvas 整个画布添加事件,而不能给某个图形或文件添加事件处理器,但是 svg 支持事件绑定,如果需要添加带有事件的动画效果时,就需要选择 svg。

TML5 Canvas是HTML5新增的一个元素,它提供了一个可执行JavaScript脚本绘制图形的区域。Canvas元素通过使用JavaScript API,可以在浏览器上绘制图形、渲染动画和实现交互效果等。

使用原理:
HTML5 Canvas通过使用JavaScript API在浏览器中创建一块画布(Canvas),然后可以使用脚本语言(通常是JavaScript)在画布上绘制各种形状、线条、图像和文本等。Canvas使用像素渲染,可以直接操作像素数据,因此在性能方面相比其他图形技术(如SVG)更具优势。

场景:
HTML5 Canvas可以应用于各种需要图形绘制、动画渲染和交互效果的场景,例如:

  1. 游戏开发:Canvas可以用来开发2D或3D游戏,通过绘制游戏场景、角色和动画等实现游戏效果。
  2. 数据可视化:Canvas可以用来绘制各种图表和图形,实现数据可视化效果。
  3. 图像处理:Canvas可以对图像进行像素级别的操作,实现图像处理功能,例如滤镜、裁剪和合成等。
  4. 实时视频处理:Canvas可以结合WebRTC等技术实现实时视频处理,例如在视频通话中添加特效和滤镜等。

代码示例:
以下是一个简单的HTML5 Canvas代码示例,用于在画布上绘制一个矩形和一个圆形:

<!DOCTYPE html>  
<html>  
    <head>  
     			<title>HTML5 Canvas示例</title>  
    </head>  
      <body>  
           <canvas id="myCanvas" width="400" height="400"></canvas>  
             <script>  
                 // 获取Canvas元素和绘图上下文  
                 var canvas = document.getElementById("myCanvas");  
                 var ctx = canvas.getContext("2d");  
                 // 绘制矩形  
                 ctx.fillStyle = "blue";  
                 ctx.fillRect(50, 50, 100, 100);  
                 // 绘制圆形  
                 ctx.beginPath();  
                 ctx.arc(200, 200, 50, 0, Math.PI * 2);  
                 ctx.fillStyle = "red";  
                 ctx.fill();  
             </script>  
      </body>  
</html>

在上述代码中,我们首先获取了Canvas元素和绘图上下文(Context),然后使用fillRect()方法绘制了一个蓝色的矩形,使用arc()方法绘制了一个红色的圆形。最后,我们使用fill()方法填充了圆形的颜色。

SVG是构建XML树的方式来达到绘制图形的,canvas是通过调用相关的方法来绘制图形的。

区别:SVG绘制图形,通过移除或者更改DOM方式来而使用canvas需要把图片从新擦除。

绘制的API在绘制上下文中定义。而不在画布中定义。

需要获得上下文对象的时候,需要调用画布的getContext方法,获得绘画的上下文。

画布元素和上下文,属于两个不同的对象,其中画布元素为canvas画布,而上下文对象为绘制需要的上下文。

关于3D图形,即,webGL 为封装了基本的OPENGL,当调用webGL的时候,其浏览器会调用OpenGL相关的API

绘制圆

<!DOCTYPE html>
<html lang="zh_CN" xmlns="http://www.w3.org/1999/html">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
<div>第一个园</br>
 <canvas id="square" width="10" height="100">
 </canvas>
</div>
<div>
 第二个园
 <canvas id="circle" width="10" height="10">
 </canvas>
</div>
<script src="./js/index.js" charset="UTF-8"></script>
</body>
</html>
// 获取画布元素
let canvas = document.getElementById("square");
// 获取绘制2D元素上下文
let context = canvas.getContext("2d");
// 设置填充颜色为红色
context.fillStyle = "#f00";
// 填充一个正方形
context.fillRect(10,0,10,10);

绘制线段,填充多边形

// 获取画布元素

let canvas = document.getElementById("square");

// 获取绘制2D元素上下文

let context = canvas.getContext("2d");

// 开始一条路径

context.beginPath();

// 从100,100 开始定义一条新的子路径

context.moveTo(100,100);

// 从100 100 到 200 200 绘制一条线段

context.lineTo(200,200);

// 从200 200 到 100 200 绘制一条线段

context.lineTo(100,200);

// 从100 200 到 100 100 绘制一条路径

context.lineTo(100,100);

// 绘制边

context.stroke();

// 进行填充

context.fill();

绘制多边形

以五边形为例子,

var canvas = document.getElementById("square");
var context = canvas.getContext("2d");
// 绘制一个以100,100为中心,半径为20的柜子N变形,每个定点均匀分布在圆角上,第一个定点放置在最上下
// 偏转角度为0
// 开始定义一条子路径
context.moveTo(100 + 20 * Math.sin(0), 100 - 20 * Math.cos(0));
// 计算两个顶点之间夹角
// 其中2π为一个园,除以边数,得到需要旋转的角度
var delta = 2 * Math.PI/5;
console.log(delta);
// 循环剩余每个顶点
var angle = 0;
for(var i = 1; i < 5; i++){
 // 角度累加
 angle += delta;
 // 通过旋转绘制下一个顶点,不断的旋转绘制
 context.lineTo(100 + 20 * Math.sin(angle), 100 - 20*Math.cos(angle));
}
// 最后一个顶点和起点进行连接
context.closePath();
// 从新开始一条新路径
context.stroke();
context.fill();

同理,画圆

var canvas = document.getElementById("square");
var context = canvas.getContext("2d");
// 绘制一个以100,100为中心,半径为20的柜子N变形,每个定点均匀分布在圆角上,第一个定点放置在最上下
// 偏转角度为0
// 开始定义一条子路径
context.moveTo(100 + 20 * Math.sin(0), 100 - 20 * Math.cos(0));
// 计算两个顶点之间夹角
// 其中2π为一个园,除以边数,得到需要旋转的角度
var delta = 2 * Math.PI/500000;
console.log(delta);
// 循环剩余每个顶点
var angle = 0;
for(var i = 1; i < 500000; i++){
 // 角度累加
 angle += delta;
 // 通过旋转绘制下一个顶点,不断的旋转绘制
 context.lineTo(100 + 20 * Math.sin(angle), 100 - 20*Math.cos(angle));
}
// 最后一个顶点和起点进行连接
context.closePath();
// 从新开始一条新路径
context.stroke();
context.fill();

非零绕数原则

要检测一个点p是否在路径内部,使用非零绕数原则,即,一条从点p出发沿着任意方向无限延伸,或者一直延伸到路径所在的区域外某点的射线,现在从0开始初始化一个计数器,对穿过这条射线的路径进行枚举,每当一条路径顺时针方向穿过射线的时候,计数器加1,逆时针减1,最后,枚举完所有路径以后,如果计时器的值不是0,那么就认为p在路径内,反过来,计数器的值为0,p在路径外。

js根据非零绕数原则确定那个在路径内,那个在路径外,用于进行填充。

图形属性

可以通过设置画布上下文的fillStyle等属性,设置图形的属性,例如对画布上下文的fillStyle的属性进行设置,即,可以设置出填充时的颜色,渐变,图案等样式。

对于canvas来说,每次获取上下文对象的时候,都会返回同一个上下文对象,即,上下文对象为单例的。

还可以使用save方法,把当前的状态,压入已经保存的栈中,调用restore方法,把状态进行恢复,即弹栈。

画布尺寸坐标

画布的默认的坐标系为左上角的坐标原点(0,0),右边数值大,下数值大,使用浮点数指定坐标,但不会自动转换为整数,会用反锯齿的方式,模拟填充部分元素。

画布尺寸不能随意改变,对任意属性进行操作,都会清空整个画布。

坐标系变换

每一个点的坐标都会映射到css像素上,css像素会映射到一个或多个设备像素。

画布中的特定操作,属性使用默认坐标系。

画布还有当前变换矩阵。

画布还有当前变换矩阵,当前变换矩阵作为图形状态的一部分。矩阵定义了当前画布的坐标系。

画布的操作会把该点映射到当前的坐标系中。

坐标变换

当调用c.translate(dx,dy)方法的时候,会进行如下变换

translate会进行坐标的上下移动

x' = x + dy;
y' = y + dy;

缩放

如要进行缩放,进行的是如下的变换

x' = sx * x;
y' = sy * y;

进行旋转操作,进行的是如下变换

x' = x * cos(a) - y * sin(a);
y' = y * cos(a) - x * sin(a);

如果要先变换再伸缩,进行如下变换

需要先把现有坐标系映射成为坐标系中的点x’, y’ 然后再变换到x‘’ , y‘’

x'' = sx*x + dx;
y'' = sy*y + dy;

如果变换顺序相反进行如下变换

x'' = sx*(x + dx);
y'' = sy*(y + dy);

这种变换称为仿射变换,并且仿射变换会修改点的距离和线段间的夹角。对于平行线来说,仿射变换也会保持平行。仿射变换用6个参数描述成为如下表述

x' = ax + cy + e;
y' = bx + dy + f;

通过传入参数实现仿射变换

对于坐标变换来说,除非进行刷新,否则,已经绘制的图形,不会进行消失,所有的变换,都不能对已经绘制的图形进行更改。栗子如下

var canvas = document.getElementById("square");
var context = canvas.getContext("2d");
// 通过坐标变换实现科赫雪花
// 开始一条路径
context.beginPath();
// 开始绘制子路径
context.moveTo(100,100);
// 继续绘制
context.lineTo(200,200);
// 继续绘制
context.lineTo(200,200);
// 进行绘制边
context.stroke();
context.translate(200,200);
// 开始一条路径
context.beginPath();
// 开始绘制子路径
context.moveTo(100,100);
// 继续绘制
context.lineTo(200,200);
// 继续绘制
context.lineTo(200,200);
// 进行绘制边
context.stroke();

已经绘制的图形不会进行改变,改变的是已经绘制的图形

科赫雪花

var canvas = document.getElementById("square");

var context = canvas.getContext("2d");

// 通过坐标变换实现科赫雪花

// 当前状态入栈

function leg(n) {

// 保存状态

context.save();

// 递归画

if(n == 0){

context.lineTo(50, 0);

}else{

// 定义为v字型

context.scale(1/2,1/2);

// 递归第一条

context.rotate(60 * (Math.PI / 180));

leg(n - 1);

context.rotate(-120 * (Math.PI / 180));

leg(n - 1);

}

// 坐标恢复变换

context.restore();

// 恢复下一个坐标为0,0

context.translate(50, 0);

}

context.save();

context.moveTo(50, 50);

// 绘制第一条

leg(1);

context.stroke();

绘制填充曲线

绘制一些常见的图形

var canvas = document.getElementById("square");
var context = canvas.getContext("2d");
// 工具函数,角度转弧度
function rads(x) {
 return Math.PI * x / 180;
}
// 绘制园
context.beginPath();
context.arc(100,100,40, 0, rads(360), false);
context.stroke();
context.fill();

同理绘制贝塞尔曲线也是同理。

颜色,透明度,渐变,图案

绘制一个渐变

需要使用createLinearGradient获取一个进行渐变的上下文,对这个上下文进行处理。然后其颜色设置为这个渐变的上下文,即,fillStyle属性。

线段绘制

封顶

对于线段,有三种封顶方式,即,butt,square,round

在绘制图形以后,会参数尖角,圆角,平角,三种。

lineCap属性

文本

和css类似,基线问题。

裁剪

直接调动clip即可,当前路径也会被裁剪进入,路径外的统统不会显示。

阴影

设置shadow属性即可

图片

画布API支持位图图片,同时也支持canvas导出成为图片。

// 创建一个img元素
let img = document.createElement("img");
// 设置src属性
img.src = canvas.toDataURL();
// 追加到文档后面
document.body.appendChild(img);

合成

一些api不在阐述

像素操作

调用getImageDate方法返回ImageDate对象

使用createImageDate()可以创建像素容器

进行动态模糊先获取像素的ImageDate对象,然后再获取该对象的data属性,该data为一个数组。为一个维数组。每四个元素代表红色分量,绿色分量,蓝色分量,透明度分量。(Alpha分量)

其色素直为0-1,即,数组元素中保存的数组为色素值。

每四个每四个元素遍历。然后把其色素值的1/ n + 上一个色块的m/n 然后赋值给新的色块,代码如下

// row为行数
for(var row = 0; row < height; row++){
 // 获得每行第二个元素的偏移量,其中width为行的色素块。
 var i = row * width * 4;
 // 每4个的色素值进行处理
 for(var col = 1; col < width; col++, i+=4){
 // 对红色分量处理
 data[i] = (data[i] + data[i - 4] * m) / n;
 // 对绿色分量处理
 data[i + 1] = (data[i + 1] + data[i + 1 - 4] * m) / n;
 // 对蓝色分量处理
 data[i + 2] = (data[i + 2] + data[i + 2 - 4] * m) / n;
 // 对透明度分量处理
 data[i + 3] = (data[i + 3] + data[i + 3 - 4] * m) / n;
 }
}

然后把其色素块进行复制回去即可。

其中每个像素占据一个字节,一个四个字节。

命中检测

isPointInPath方法用来确定一个点是否落在当前路径中。

即命中检测。

命中检测可以和鼠标事件相互转化

但是坐标需要进行转换。