整合营销服务商

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

免费咨询热线:

HTML5(六)-Canvas 高级操作

一篇文章《HTML5(五)——Canvas API》介绍 canvas 绘制基本图形,这节开始介绍canvas的高级操作。

一、canvas 转换

canvas 转换常用的几种方法介绍,如下:

方法

描述

scale()

缩放当前绘图至更大或更小。

rotate()

旋转当前绘图。

translate()

重新映射画布上的 (0,0) 位置。

transform()

替换绘图的当前转换矩阵。

setTransform()

将当前转换重置为单位矩阵。然后运行 transform()。

1.1 、scale - 缩放

使用语法:scale(x,y)

x:表示水平方向的缩放倍数

y:表示垂直方向的缩放倍数

eg:canvas 绘制的矩形框放大两倍,代码如下:

var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
ctx.scale(2,2)
ctx.fillStyle="red"
ctx.fillRect(0,0,200,200)

1.2、translate - 画布平移

使用语法:translate(x,y)

x:添加到水平坐标上的位置

y:添加到垂直坐标上的位置

设置之后开始绘制的图片位置从(x,y)算起。

eg:绘制两个一样的矩形,一个在平移前绘制,一个在平移后绘制,代码如下:

var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
ctx.fillStyle="red"
        
ctx.fillRect(0,0,200,200)
//平移
ctx.translate(200,200)
ctx.fillRect(0,0,200,200)

运行结果如图:

1.3 、rotate - 旋转

使用语法:rotate(angle)

angle 旋转弧度,如果想使用角度,可以把角度转成弧度,公式为:deg * Path.PI/180。

eg:将一个矩形旋转45度,代码如下:

var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
//旋转45度
ctx.rotate(45*Math.PI/180)
ctx.fillStyle="red"
ctx.fillRect(0,0,200,200)

运行结果如图:

根据上述结果我们发现旋转的时候,默认原点是画布的起始点,我们想要的旋转是在矩形框中心为原点的旋转,此时我们需要借助上translate平移,重置一下原点,修改上述代码为:

<canvas width="400" height="400" id="canvas"></canvas>

var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")

ctx.translate(200,200)
ctx.rotate(45*Math.PI/180)
ctx.translate(-200,-200)

ctx.fillStyle="red"
ctx.fillRect(100,100,200,200)

运行结果如图:

1.4、transform - 矩阵变换

使用语法:transform(a,b,c,d,e,f)

  • a:水平缩放
  • b:水平倾斜
  • c:垂直倾斜
  • d:垂直缩放
  • e:水平移动
  • f:垂直移动

transform可以替代前边平移、缩放、旋转三者,如下:

// 平移
translate(x,y) <=> transform(1,0,0,1,x,y) <=> transform(0,1,1,0,x,y)
// 缩放
sacle(x,y) <=> transform(x,0,0,y,0,0) 
// 旋转
rotate(angle) <=> transform(Math.cos(angle*Math.PI/180), Math.sin(angel*Math.PI/180), -Math.sin(angle*Math.PI/180), Math.cos(angle*Math.PI/180))

1.5、setTransform - 矩阵变换

setTransform()方法将变换的矩阵进行重置,它把当前的变换矩阵重置为单位矩阵
使用语法:transform(a,b,c,d,e,f)
各参数说明:水平旋转、水平倾斜、垂直倾斜、垂直缩放、水平移动、垂直移动

setTransform() 方法把当前的变换矩阵重置为单位矩阵,然后以相同的参数运行 transform()

二、canvas 操作图片

drawImage() 在画布上绘制图像、画布或视频。也能够绘制图片的一部分,增加或减少图像的尺寸。以下是三种常见使用语法:

  • 语法1:drawImage( img , x , y )
  • 语法2:drawImage( img , x , y , width , height )
  • 语法3:drawImage( img , sx , sy , swidth, sheight , x , y , width , height )

上述参数表示的意义如下:

参数

描述

img

规定要使用的图像、画布或视频。

sx

可选。开始剪切的 x 坐标位置。

sy

可选。开始剪切的 y 坐标位置。

swidth

可选。被剪切图像的宽度。

sheight

可选。被剪切图像的高度。

x

在画布上放置图像的 x 坐标位置。

y

在画布上放置图像的 y 坐标位置。

width

可选。要使用的图像的宽度。(伸展或缩小图像)

height

可选。要使用的图像的高度。(伸展或缩小图像)

eg:利用语法3,进行绘制图片的部分内容,实现如下效果:

给上述兔子顺便加一个点击屏幕暂停开始功能,完整代码如下:

<canvas width="400" height="400" id="canvas"></canvas>
<script>
 var canvas = document.getElementById("canvas")
 var ctx = canvas.getContext("2d")
 var img = new Image()
 let pause = false,frameCounter = 0,i=0;
 img.src = "./rotate.png"
 img.onload = function(){
  requestAnimationFrame(next)
 }
function next(){
 ctx.clearRect(0,0,canvas.width,canvas.height)
 if(frameCounter%5 == 0){ //frameCounter 控制动画速度
  i++
  if(i==11)i=0
 }
 ctx.drawImage(img,
  0,i*240,240,240,
  0,0,240,240) // 每张图片宽高都是240,具体参数根据图片而定
  frameCounter ++
  if(!pause)requestAnimationFrame(next)
 }
window.onclick = function(){
 pause = !pause
 next()
}
</script>

eg:使用 canvas 画布处理视频,使用定时器绘制视频的当前帧,连续起来就是一个视频,需要注意的是必须处理暂停和开始播放两种操作,具体代码如下:

var v=document.getElementById("video1");
var c=document.getElementById("myCanvas");
ctx=c.getContext('2d');
v.addEventListener('play',function() {var i=window.setInterval(function() 
{ctx.drawImage(v,0,0,270,135)},20);},false);
v.addEventListener('pause',function() {window.clearInterval(i);},false);
v.addEventListener('ended',function() {clearInterval(i);},false);

三、像素级操作

常见的像素级的操作有三种:

  • getImageData() - 获取画布像素数据
  • createImageData() - 创建新的、空白像素
  • putImageData() - 图像像素数据放回画布

3.1、getImageData

使用语法:getImageData( x , y , width , height )

  • x:要被提取的图像数据矩形区域的左上角 x 坐标。
  • y:要被提取的图像数据矩形区域的左上角 y 坐标。
  • width:被提取的图像数据矩形区域的宽度
  • height:被提取的图像数据矩形区域的高度

返回一个 imageData 对象,用来描述 canvas 区域隐含的像素数据,这个区域通过像素表示,起点是( x , y ),宽高为 widht 和 height 。

imageData 对象包含三个属性:

  • imageData.height - 像素描述的矩形实际高度。
  • imageData.width - 像素描述的矩形实际宽度。
  • imageData.data - 包含 rgba 顺序数据的一个数组,数据使用0-255直接整数表示。

3.2、createImageData

使用语法:

createImageData( width , height )

创建一个空白的 imageData 对象,新对象的默认像素值 transparent black。对于imageData对象中的每个像素值,都存在 rgba 这四方面的信息,即:

  • r - 红色(0-255)
  • g - 绿色(0-255)
  • b - 蓝色(0-255)
  • a - alpha(0-255,0是透明,255是完全可见)

新对象默认像素值为(0,0,0,0)。

eg:如果我们想把 imageData 中一个像素变为红色时,可以改变第一和第四位信息,代码如下:

var imageData = ctx.createImageData(100,100)
imageData.data[0] = 255
imageData.data[1] = 0
imageData.data[2] = 0
imageData.data[3] = 255

3.1、putImageData

使用语法:

putImageData( imgData , x , y , dirtyX , dirtyY , dirtyWidth ,dirtyHeight );

参数及意义:

参数

描述

imgData

规定要放回画布的 ImageData 对象。

x

ImageData 对象左上角的 x 坐标,以像素计。

y

ImageData 对象左上角的 y 坐标,以像素计。

dirtyX

可选。水平值(x),以像素计,在画布上放置图像的位置。

dirtyY

可选。水平值(y),以像素计,在画布上放置图像的位置。

dirtyWidth

可选。在画布上绘制图像所使用的宽度。

dirtyHeight

可选。在画布上绘制图像所使用的高度。

通过 getImageData 复制的指定矩形像素数据,编辑之后,通过 putImageData 方法将图像数据放回画布上。

eg:添加滤镜效果:上述兔子是白色的变换成红色兔子,这时需要把绿色和蓝色都设置成0即可,代码如下:

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()方法填充了圆形的颜色。

、什么是Canvas?

HTML5 提供Canvas API,其本质上是一个DOM元素,可以看成是浏览器提供一块画布供我们在上面渲染2D或者3D图形。由于3D绘制上下文(webgl)目前在很多浏览器上兼容性较差,所以我们一般用于绘制2D图形。

<canvas id="canvas"></canvas>


2、为什么使用Canvas?


Canvas是HTML5引入的标签,在此之前我们通常会使用SVG来绘制一些图形,那么两者之间有什么区别呢?SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,两者部分区别:


  • SVG 图像是使用各种元素创建的,这些元素分别应用于矢量图像的结构、绘制与布局;而Canvas本身并不描述图像,而是通过Javascript完成绘制;
  • 如上所述,SVG本身是DOM元素,每一个描述元素也是DOM元素,浏览器在进行渲染时需要进行大量计算以处理每一个元素;而在渲染Canvas的过程中,浏览器只需要渲染一张画布,其余的是通过Javascript引擎执行逻辑来绘制;
  • SVG(矢量图)不依赖分辨率,放大不会失真;而Canvas(位图)依赖分辨率,放大会失真;



由于Canvas是通过Javascript来完成绘制的,所以可控性很强,我们可以比较精确的控制图形渲染的每一帧;从另一方面来说,如果在高频率渲染中要处理过多的DOM元素就意味着性能一定不会太好,渲染速度会下降很多。Canvas的高性能能够保障复杂场景中图形的渲染效率,所以目前很多领域都会使用Canvas,例如动画、游戏图形、数据可视化、照片处理和实时视频处理等。



3、Canvas的基本使用

要使用Canvas,我们需要先获取Canvas元素的引用继而通过getContext()方法获取图形的绘制上下文。

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')


获取到图形绘制上下文后,我们就能使用CanvasRenderingContext2D接口上的绘图API了,接下来我们可以了解一些比较常规的使用。


3.1、画布属性:


  • width、height:画布的宽度以及高度,默认大小为300x150;
  • fillStyle:填充图形的样式,值可以是color string、CanvasGradient对象;
  • strokeStyle:轮廓图形的样式,值可以是color string、CanvasGradient对象;
  • lineWidth:绘制线条的宽度;
  • globalAlpha:画布的透明度,0-1的偏移值;
  • globalCompositeOperation:画布中新老图形重叠时的渲染方式,默认为source-over,新图形覆盖老图形;
  • ......
ctx.width = 300
ctx.height = 300
ctx.fillStyle = '#fff'
ctx.strokeStyle = 'blue'
ctx.lineWidth = 5
ctx.globalAlpha = 0.3
ctx.globalCompositeOperation = 'destination-out' // 新老图形重叠部分变透明
......

3.2、绘制图形:


  • .fillRect(x,y,width,height):绘制一个填充的矩形,矩形左上角的坐标为(x,y),高宽分别为width、height;
  • .strokeRect(x,y,width,height):绘制一个矩形边框,矩形左上角的坐标为(x,y),高宽分别为width、height;
  • .clearRect(x,y,width,height):清除指定矩形区域,让清除部分完全透明;
ctx.fillStyle = 'red' 
ctx.fillRect(100,100,100,100)


ctx.strokeStyle = 'blue'  
ctx.strokeRect(200,200,100,100)


ctx.clearRect(125,125,50,50)
ctx.strokeRect(130,130,40,40)




3.3、绘制路径:


  • .beginPath():开始一段路径的绘制
  • .closePath():从起始点到当前点,结束路径的绘制,非必需
  • .fill():根据路径生成填充图形;
  • .stroke():通过路径生成轮廓图形;
  • .moveTo(x,y):声明一段路径的起始点;
  • .lineTo(x,y):绘制一条从当前坐标到(x,y)的线;
ctx.beginPath()
ctx.moveTo(50,50)
ctx.lineTo(100,100)
ctx.lineTo(100,0)
ctx.fill()


ctx.beginPath()
ctx.moveTo(110,100)
ctx.lineTo(150,100)
ctx.lineTo(150,200)
ctx.lineTo(110,200)
ctx.closePath() // 轮廓图形不会根据从当前坐标到起始坐标生成轮廓,所以需要闭合路径
ctx.stroke()


3.4、绘制圆弧:


  • .arc(x,y,radius,startAngle,endAngle,anticlockwise):画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针,false)来生成;
  • arcTo(x1,y1,x2,y2,radius):根据给定的两条切线中的一组切点坐标生成半径为radius的圆弧;



注意:arc函数中的角度的单位是弧度而不是度,弧度=(Math.PI/180)*度

// 圆左上部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI,Math.PI*3/2,false)
ctx.strokeStyle = '#ff6700'
ctx.stroke()


// 圆右上部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI*3/2,0,false)
ctx.strokeStyle = '#6700ff'
ctx.stroke()


// 圆右下部分
ctx.beginPath()
ctx.arc(100,100,50,0,Math.PI/2,false)
ctx.strokeStyle = '#00FFFF'
ctx.stroke()


// 圆左下部分
ctx.beginPath()
ctx.arc(100,100,50,Math.PI/2,Math.PI,false)
ctx.strokeStyle = '#8B008B'
ctx.stroke()


// 两条切线的交点坐标为(0,0)
ctx.beginPath()
ctx.moveTo(100,0)
ctx.arcTo(0,0,0,100,100)
ctx.fillStyle = 'blue'
ctx.fill()



3.5、渐变对象:


  • .createLinearGradient(x1, y1, x2, y2):创建一个沿参数坐标指定的直线的渐变,开始坐标为(x1,y1),结束坐标为(x2,y2);
  • .createRadialGradient(x1, y1, r1, x2, y2, r2):创建根据参数确定两个圆的坐标的放射性渐变,开始圆形圆心为(x1,y1),半径为r1;结束圆形圆心为(x2,y2),半径为r2;

创建好渐变对象之后,可以通过渐变对象上的.addColorStop(offset,color)为每一个渐变阶段填充颜色,offset为0-1的偏移值。

const gradient = ctx.createLinearGradient(50, 50, 250, 50)
gradient.addColorStop(0, 'blue')
gradient.addColorStop(0.5, 'green')
gradient.addColorStop(1, 'red')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 300, 90)


const radialGradient = ctx.createRadialGradient(200,200,100,200,200,50);
radialGradient.addColorStop(0,"yellow");
radialGradient.addColorStop(1,"green");
ctx.fillStyle = radialGradient;
ctx.fillRect(100,100,200,200);




3.6、像素操作:


  • .drawImage(image,x,y,width,height):image可以是image对象、canvas元素、video元素;
  • .getImageData(x,y,width,height):获取坐标为(x,y)一定区域内图像的像素数据;
const div = document.querySelector('div')
let mousedown = false;


function getRandom() {
  return Math.round(255 * Math.random());
}


function getColor() {
  return `rgb(${getRandom()},${getRandom()},${getRandom()})`;
}


const gradient = ctx.createLinearGradient(0, 0, 300, 300);
gradient.addColorStop(0, getColor());
gradient.addColorStop(0.6, getColor());
gradient.addColorStop(1, getColor());


function clear() {
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}


ctx.beginPath();
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 300);


function selector(x = 150, y = 150) {
  clear();
  ctx.beginPath();
  ctx.arc(x, y, 5, 0, Math.PI * 2);
  ctx.strokeStyle = "#fff";
  ctx.stroke();
  const { data } = ctx.getImageData(x, y, 1, 1); // 获取(x,y)点对应的imageData
  const color = `rgba(${data[0]},${data[1]},${data[2]},${data[3] / 255})`
  div.innerText = `color: ${color}`;
  div.style.backgroundColor = color
}


function handleSelector(e) {
  const x = e.offsetX;
  const y = e.offsetY;
  selector(x, y);
}


canvas.addEventListener("mousedown", (e) => {
  mousedown = true;
  handleSelector(e)
});


canvas.addEventListener("mouseup", () => {
  mousedown = false;
});


canvas.addEventListener("mousemove", (e) => {
  if (mousedown) {
    handleSelector(e)
  }
});


selector();




3.7、画布状态:


  • .save():将当前画布的状态推入到栈中,例如fillStyle、2D转换等;
  • .restore():将栈顶元素弹出,恢复上一次推入栈中画布的状态;

当我们需要通过空间转换来绘制图形时,保存与恢复画布的状态是很关键的,因为我们是在同一块画布上绘制图形,而变换都是基于画布的,这与我们平时使用到的CSS 2D转换截然不同,所以我们在下一步绘制时要确认此时画布的状态是否是我们的理想状态。

ctx.save() // 保存画布初始状态
ctx.translate(100,100) // 将画布原点转移至(100,100)
ctx.fillStyle = 'red'
ctx.fillRect(0,0,50,50)


ctx.restore() // 恢复画布状态,此时画布原点为(0,0)
ctx.fillStyle = 'blue'
ctx.fillRect(0,0,50,50)


3.8、几何变化:


  • .translate(x,y):画布默认的原点是(0,0),此方法可以切换原点到(x,y)而不需要手动更改绘制图形的坐标;
  • .rotate(angle):将画布旋转一定的角度,angle单位为弧度;
  • .scale(sx,sy):sx为水平方向的缩放比例,sy为竖直方向的缩放比例;
  • .transform(a,b,c,d,e,f):依次为水平缩放、垂直倾斜、水平倾斜、垂直缩放、水平移动、垂直移动;


const colors = ['red','orange','yellow','green','blue','purple'];
ctx.translate(150,150)
    
for(let i = 0; i < 6; i++) {
  ctx.beginPath()
  ctx.fillStyle = colors[i]
  ctx.moveTo(0,0)
  ctx.lineTo(100,0)
  ctx.lineTo(100,50)
  ctx.rotate(Math.PI/3)
  ctx.fill()
}


4、综合实战


const p = Math.PI;


function clock() {
  const date = new Date();
  const hour = date.getHours()
  const s = date.getSeconds();
  const m = date.getMinutes();
  const h = !!(hour % 12) ? hour % 12 : 12;
  ctx.clearRect(0, 0, canvas.width, canvas.height);


  ctx.save(); // 保存画布初始状态


  ctx.translate(150, 150);
  ctx.rotate(-p / 2);


  // 轮廓
  ctx.beginPath();
  ctx.lineWidth = 5;
  ctx.strokeStyle = "#76b2ff";
  ctx.arc(0, 0, 80, 0, p * 2);
  ctx.stroke();


  // 圆心
  ctx.beginPath();
  ctx.arc(0, 0, 2, 0, p * 2);
  ctx.fill();


  // 分针、秒针刻度
  for (let i = 0; i < 60; i++) {
    ctx.beginPath();
    ctx.rotate(p / 30);
    ctx.moveTo(75, 0);
    ctx.lineWidth = 4;
    ctx.strokeStyle = "#89f086";
    ctx.lineTo(80, 0);
    ctx.stroke();
  }


  // 时针刻度
  for (let i = 0; i < 12; i++) {
    ctx.beginPath()
    ctx.rotate(p / 6)
    ctx.moveTo(70, 0)
    ctx.lineTo(80, 0)
    ctx.stroke()
  }


  ctx.save(); // 保存画布变换之后的状态


  // 秒针
  ctx.beginPath();
  ctx.rotate(s * (p / 30));
  ctx.lineWidth = 2
  ctx.strokeStyle = '#ff6700'
  ctx.moveTo(0, 0);
  ctx.lineTo(80, 0);
  ctx.stroke();


  // 恢复之前的状态再保存,时针、分针、秒针都是基于原点以及画布方向变换后绘制
  ctx.restore();
  ctx.save();


  // 分针
  ctx.beginPath();
  ctx.rotate(m * (p / 30));
  ctx.lineWidth = 3;
  ctx.strokeStyle = '#6700ff'
  ctx.moveTo(0, 0);
  ctx.lineTo(70, 0);
  ctx.stroke();


  ctx.restore();


  // 时针
  ctx.beginPath();
  ctx.rotate(h * (p / 6));
  ctx.lineWidth = 4;
  ctx.moveTo(0, 0);
  ctx.lineTo(60, 0);
  ctx.stroke();


  ctx.restore(); // 恢复画布最初状态


  document.querySelector('div').innerText = `Now:${h} : ${m} : ${s}  ${hour > 12 ? 'pm' : 'am'}`


  window.requestAnimationFrame(clock);
}
clock();




5、小结


随着互联网的高速发展,用户对页面的视觉和交互有着越来越高的要求,传统的web开发无法得到满足,利用Canvas强大的绘图能力,可以让网页显示的内容更加的丰富多彩,也能给用户带来更好的视觉体验。

作者:LLS-FE团队

来源:微信公众号:流利说技术团队

出处:https://mp.weixin.qq.com/s/bvkx3wOeMvIUU64cktX6iA