分享到微信朋友圈

打开微信,“扫一扫”功能,
即可将网页分享至好友/朋友圈。

文章索引

返回顶部
  / 学习笔记 / H5 CSS3 / canvas 画布,用的不多的就多看看,以备不时之需!

canvas 画布,用的不多的就多看看,以备不时之需!

本文为秃头大叔 小虎的个人博客 原创内容,转载请注明出处,有问题可联系本人!
本文地址:http://www.wuxiaohu.com/2017/04/18/738.html

H5新元素——<canvas>画布,在工作中第一次用的时候还是“参考”了别人的代码修改而来;于是乎在这之后偷了点空闲时间做点功课和小结,指不定哪天就得自己去写呢!

H5的绘图能力除了<canvas>还有个<svg>,它们都是可以用来绘制2D的平面图形,3D立体图形?这就只能等着未来的扩展支持了;

区别的话:

Canvas

Svg

基于Canvas的用途大部分都在游戏领域,其中涉及到许多API性能、渲染问题,也不是简简单单就能深入了解的;

所以暂时学点皮毛,绘制点基本图形、表格什么的权当学习了!

创建画布

<canvas> 标签只是图形容器,必须使用JS脚本来绘制,通过多种方法使用Canva绘制路径,盒、圆、字符以及添加图像。

一个画布在网页中是一个矩形框,通过 <canvas> 元素来绘制;默认情况下 <canvas> 元素没有边框和内容。

// 获取节点对象
var c = document.getElementById("myCanvas");

// 定义绘画环境
var ctx = c.getContext("2d");
/*
    getContext:当前唯一的合法值是 "2d",指定了二维绘图;返回一个 CanvasRenderingContext2D 对象,使用它可以绘制到 Canvas 元素中。
*/

之后的操作基本都是JS控制,根据需求选用不同的方法来绘制想要的图形:

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.moveTo(20,20);     // 可理解为“起点”  
ctx.lineTo(20,100);    // 可理解为“终点”  
ctx.lineTo(70,100);    // 再确定一个“终点”  
ctx.stroke();          // 用 stroke() 画出确的路径

所以,不同的绘制方法其实就是 CanvasRenderingContext2D 所提供的图形函数:

一:路径

画图方法 stroke()  :用于在画布上绘制确切的图形;

创建路径:

1.1  线段:moveTo() 、 lineTo()

// 以下为JS代码,ctx即为前面定义的画布对象
ctx.moveTo(20,20);     // 可理解为“起点”  
ctx.lineTo(20,100);    // 可理解为“终点” 
ctx.lineTo(70,100);    // 再确定一个“终点”  
ctx.stroke();          // 用 stroke() 画出确的路径

运行结果:

1.2  曲线:quadraticCurveTo() 、 bezierCurveTo()

这两个曲线分别是 二次贝塞尔曲线(quadraticCurveTo())、三次贝塞尔曲线(bezierCurveTo()),具体它们是什么原理,要么去问问自己曾经的数学老师,要么就去百度一下吧;反正我也不是很懂;

要绘制贝塞尔曲线,首先要有两个点用来确定路径的起点和终点,这里又用 moveTo() 来确定一个起点,而终点就在曲线函数里确定:

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.moveTo(20,20);                     // 路径“起点”  
ctx.quadraticCurveTo(20,100,200,20);   // 后两个数值确定路径“终点”(20,100),前面两个数值确定曲线走向 (20,100)  
ctx.stroke();                          // 用 stroke() 画出确定路径

运行结果:

三次贝塞尔曲线(bezierCurveTo()):

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.moveTo(20,20);                          // 路径“起点”  
ctx.bezierCurveTo(20,100,200,100,200,20);   // 对比二次曲线,多了一个点后(200,100)用来确定曲线走向 
ctx.stroke();                               // 用 stroke() 画出确定路径

运行结果:

而曲线曲度究竟是如何确定的呢?!二次贝塞尔曲线倒是很好理解,三次贝塞尔曲线的原理现在我也是一知半解,所以就偷个懒了、、、

1.3  圆弧:arc() 、 arcTo()

画圆形或部分圆请认准 arc() :

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.arc( 50,50, 50, 0.5*Math.PI, 2*Math.PI); // 就是这么简单:  
    //  (50,50) 为原点  
    // 半径为 50(px)  
    // 起始角0.5π,结束角2π,角度计算为顺时针方向  
ctx.stroke();                                // 用 stroke() 画出确定路径

运行结果:

画弧线、切弧线就用 arcTo():

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.beginPath();                // beginPath() 方法用于重置或开始一个新路径,这里用来辅助说明 arcTo() 的用法  
ctx.moveTo(100,50);             // 创建一个点 P1  
ctx.arcTo(150,150,0,150,100);   // 用 arcTo(P2, P3, r) 创建一个弧,P2、P3为两个点,r 为半径  
ctx.stroke();                   // 最后用 stroke() 画出确定的路径   
ctx.beginPath();                // 画出用到的点,直观 arcTo() 的实现原理  
ctx.moveTo(100,50);             // 点P1  
ctx.lineTo(150,150);            // 点P2  
ctx.lineTo(0,150);              // 点P3  
ctx.strokeStyle="red";          // 用红色画出点之间的连线  
ctx.stroke();                   // 用 stroke() 画出确定路径

运行结果:

简单直观,arcTo() 的绘图就是由两条直线:P1P2、P2P3 相切的半径为 r 的圆弧!!

1.4 矩形:rect() 、strokeRect() 、fillRect()

三个绘制方法都接受四个属性值 (x, y, width, height) :

x, y :矩形左上角的起点坐标

width, height :矩形的大小,宽度和高度

rect() 相对而言是最灵活的,因为它也可以实现其它两个方法想要的结果,绘制矩形:

rect() :绘制矩形路径,但是需要用 stroke() 或 fill() 来实际绘制

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.rect(20,20,150,100);     // 绘制矩形路径  
ctx.stroke();                // 用 stroke() 则效果和 strokeRect() 类似;用 fill() 则效果和 fillRect() 类似

运行结果:

strokeRect()实际绘制矩形图形,非填充矩形,带边框;

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.strokeRect(20,20,150,100);     // 直接绘制想要的非填充矩形

运行结果:

fillRect()实际绘制"已填充"的矩形;默认的填充颜色是黑色

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.fillRect(20,20,150,100);               // 直接绘制填充矩形,默认填充为黑色

运行结果:

除了绘制矩形,还有一个清除矩形 clearRect()

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.fillRect(0,0,300,150);        // 用 fillRect() 填充一个矩形  
ctx.clearRect(20,20,100,50);      // 再用 clearRect() 清除一个矩形

运行结果:

路径绘制可能会用到的其他方法:

beginPath() :用于重置或开始一个新路径,前面用到了一次;

closePath() :创建从当前点回到起始点的路径,可用来创建闭合路径;

clip() :从原始画布剪切任意形状和尺寸的区域;一般用在绘图函数(如stroke())之后;一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内;

isPointInPath() :如果指定的点位于当前路径中,则返回 true,否则返回 false;路径的话,需要在路径上(不是闭合路径的情况,需要把 closePath() 创建的闭合路径算在里面);矩形的话,只需要在矩形内即可;

以上说到的几种路径绘制方法,很多细节都可以微调,如线条拐角、线条宽度、线条颜色、填充颜色、渐变填充、设置阴影、、、放到最后!!

二:文本

文本书写方法:fillText()strokeText()

实际从名字就可以分别出两者的区别,“fill” 表示填充,“stroke” 表示路径;

这两个方法用法一样,都接受三个参数 (string, x, y) :

string :需要画的文本;

x :开始画文本的横坐标;

y :开始画文本的纵坐标,但是,是以文本的字母基线(alphabetic)计算;

// 以下为JS代码,ctx即为前面定义的画布对象    
ctx.font="20px Georgia";           // 对文本大小、字体进行设置;  
ctx.fillText("Hello World!",10,50);   // fillText() 填充式画文字,默认为黑色

运行结果:

如果以上用 fillText("Hello World!",10,10)  就会让文本显示不全:

// 以下为JS代码,ctx即为前面定义的画布对象    
ctx.font="20px Georgia";           // 对文本大小、字体进行设置;  
ctx.strokeText("Hello World!",10,50);   // strokeText() 非填充书写文字,默认为黑色

运行结果:

要写自己想要的文字,当然要有一些字体设置属性:

font :可设置字体属性, 一般包括 (font-style, font-variant, font-weight, font-size/line-height, font-family)

font-style :规定字体样式(normal/italic/oblique),italic 和 oblique 都是斜体,但是 oblique 倾斜更大;

 font-variant :规定字体变体(normal/small-caps),small-caps 为大写英文格式;原本是小写就会变成小型的大写格式;

font-weight :规定字体的粗细,和 CSS 一样的设置参数;

font-size/line-height :规定字号和行高,以像素计,和 CSS 一样的设置参数;

font-family :规定字体系列,和 CSS 一样的设置参数;

其他还有几个设置参数,因为不常见也就算了吧,需要的时候再去查查把!

textAlign :设置文本内容的水平对齐方式,以 (string, x, y) 中设置 x 坐标为基点

start/left :文本内容以指定位置开始(坐标位置仅靠文本左侧)

center :文本的中心被放置在指定位置(坐标在文本长度中点)

end/right :文本内容在指定位置结束(坐标位置仅靠文本右侧)

textBaseline :设置文本内容的垂直对齐方式,文本基线;可选的值有:alphabetic(默认)/top/hanging/middle/ideographic/bottom:

文本绘制中,还有一个方法,用来获取文本的长度(width),返回的数值是个精确到小数点后十位的浮点数:measureText()

使用:ctx.measureText(txt).width ,即返回 txt 文本的长度,以 px 计算但不带单位的浮点数;

三:图像

画布图像绘制:drawImage()

用法:ctx.drawImage( img, sx, sy, swidth, sheight, x, y, width, height );

img :规定要使用的图像(也可以是画布或视频),JS中的DOM对象,如:img = document.getElementById("scream")

sx :可选。开始剪切的 x 坐标位置,只针对原图尺寸,和标签内设置 width 或者 CSS 设置的大小无关

sy :可选。开始剪切的 y 坐标位置,只针对原图尺寸,和标签内设置 height 或者 CSS 设置的大小无关

swidth :可选,被剪切图像的宽度,只针对原图尺寸,和标签内设置 width 或者 CSS 设置的大小无关

sheight :可选,被剪切图像的高度,只针对原图尺寸,和标签内设置 height 或者 CSS 设置的大小无关

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

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

width :可选,画布上显示的图像宽度(伸展或缩小图像)

height :可选,画布上显示的图像高度(伸展或缩小图像)

drawImage() 的应用基本都是游戏领域,毕竟没接触过,这一块确实不太了解;

不过无论应用在什么环境,drawImage() 更准确的说就是一种复制加工,所以原文件一定要存在才能调用这个方法实现想要的结果;

图像绘制中,一定要确保图片已下载,一般都采用这种模式:

// 以下为JS代码,ctx即为前面定义的画布对象    
document.getElementById("scream").onload=function()   // <img id="scream" src="xxx.jpg">  
{
    var img=document.getElementById("scream");        // 定义图像 DOM,用于 drawImage() 调用
    ctx.drawImage(img,0,0,200,200,0,0,200,200);       // 以原图 (0,0) 左上角开始,剪切 200*200 的区域,在画布的 (0,0)左上角开始绘制, 大小为 200*200 的图像
};

说起这个图像绘制的功能,有一个应用之前倒是接触过:视频绘制

这里直接链接一个 菜鸟教程的 视频绘制实例,可惜的是,画布终究只是呈现图像的,所以复制出来的“视频”的就是无声的了!!

最后,drawImage() 只要处理的原文件存在了,不管是否隐藏(display:none)或是修改宽高大小,它只看原始文件!!

四:扩展(线条样式、阴影、渐变、转换、组合)

简单的绘图模式,总结出来也就 “fill”(填充) 和 “stroke”(非填充)两种模式;

于是就有了:

4.1 绘制属性:fillStyle 、strokeStyle

前者针对填充内容进行属性修改,后者针对笔触路径进行属性修改;

它们都接受三种类型的属性值:

color :指定填充色/笔触色,设置方式同CSS颜色值一样

gradient :指定绘图的渐变对象,需要自定义对象,后面再说怎么自定义

pattern :需要自定义对象;用 createPattern() 方法在指定的方向内重复指定的元素:

createPattern() 用法:ctx.createPattern(img,"repeat | repeat-x | repeat-y | no-repeat")

img :规定要使用的图像(也可以是画布或视频),JS中的DOM对象,如:img = document.getElementById("scream")

repeat :该模式在水平和垂直方向重复,默认为 repeat ;该设置唯一,仅可设置一种重复方式,不可不设置

repeat-x :该模式只在水平方向重复

repeat-y :该模式只在垂直方向重复

no-repeat :该模式只显示一次(不重复)

 说到stroke绘制线条的话,还可以设置线条样式:

lineCap :设置或返回线条末端线帽的样式

butt :默认,向线条的每个末端添加平直的边缘

round :向线条的每个末端添加圆形线帽,实际会使长度加长

square :向线条的每个末端添加正方形线帽,实际会使长度加长

lineJoin :设置或返回两条线相交时,所创建的拐角类型:bevel(斜角)、round(圆角)、miter (尖角)

lineWidth :设置或返回当前的线条宽度,以像素计

miterLimit :正数,规定最大斜接长度;如果斜接长度超过 miterLimit 的值,边角会以 lineJoin 的 "bevel" 类型来显示

4.2 画布阴影:shadowColor 、shadowBlur

阴影设置时,shadowColor 和 shadowBlur 两个必须都设置,才能生效;同时还有两个可选属性,用于设置画布阴影的偏移:

shadowColor :必须,设置或返回用于阴影的颜色

shadowBlur :必须,设置或返回用于阴影的模糊级别

shadowOffsetX :可选,设置或返回阴影与形状的水平距离,默认为 0

shadowOffsetY :可选,设置或返回阴影与形状的垂直距离,默认为 0

虽然阴影是针对画布而言,但是它要有实际绘画内容才能生效的,所以阴影设置需要在绘制方法前面确定

// 以下为JS代码,ctx即为前面定义的画布对象
ctx.shadowBlur = 20;              // 优先设置阴影模糊,在没有beginPath()重置的情况下,对后面的绘制都有效 
ctx.fillStyle = "red";            // fillStyle 设置填充颜色为 red(红色)
ctx.strokeStyle = "red";          // strokeStyle 设置笔触颜色为 red(红色)
ctx.shadowColor = "black";        // 先设置一个阴影颜色为 black(黑色)
ctx.fillRect(20,20,100,80);       // 绘制图像,结果就是 20模糊 的 黑色 阴影
ctx.shadowColor = "blue";         // 再设置阴影颜色 blue(蓝色)
ctx.strokeRect(140,20,100,80);    // 绘制图像,结果就是 20模糊 的 蓝色 阴影

运行结果:

4.3 渐变对象:createLinearGradient() 、createRadialGradient()

createLinearGradient 为线性渐变,createRadialGradient 为放射状/环形的渐变;

首先我们要创建一个渐变对象:

// 以下为JS代码,ctx即为前面定义的画布对象
var grdL = ctx.createLinearGradient(0,0,170,0);          // 这是一个线性渐变对象,长度为 170px 的水平线,命名为 grdL
var grdR = ctx.createRadialGradient(10,10,0,10,10,140);  // 这是一个放射渐变对象,命名为 grdR

渐变对象创建用于确定渐变的路径,渐变的颜色则需要 addColorStop() 来确定;

它是渐变对象的方法,用来规定渐变对象中的颜色和位置;它接受两个参数,方法为 grdX.addColorStop(stop, color) :

stop :取值 [0, 1] 小数点后一位的小数,表示渐变中开始与结束之间的位置,针对渐变对象的长度,与画布长度无关

color :在 stop 位置显示的颜色,设置方式同CSS颜色值一样

再来看看 createLinearGradient 线性渐变接受的四个参数 (x0, y0, x1, y1) :

x0, y0 :渐变开始点的坐标

x1, y1 :渐变结束点的坐标

createLinearGradient 两个点的坐标是会影响绘图结果的,如果两个点都不在绘图区域内,那么渐变也是看不到的

// 以下为JS代码,ctx即为前面定义的画布对象,grdL即为前面定义的线性渐变对象
grdL.addColorStop(0,"blue");    // 颜色渐变的起始点 为 渐变对象的起始点
grdL.addColorStop(0.5,"red");   // 颜色渐变的结束点 为 渐变对象的 0.5 长度,即一半的地方
ctx.fillStyle=grdL;             // fillStyle 填充渐变对象
ctx.fillRect(20,20,150,100);    // 绘图结果

运行结果:

因为颜色渐变只设置到 0.5 的位置,所以一半的部分,颜色都显示为 红色;这里说的一半是渐变对象的一半,即画布的横坐标 170/2 = 85 px 处;

绘图矩形的宽度为 150 px,为画布的 20px - 170px 的占宽,所以结果中的渐变宽度要比右侧小 20px!

createRadialGradient 放射渐变接受六个参数 (x0, y0, r0, x1, y1, r1) :

x0, y0 :渐变开始圆的圆点坐标

r0 :渐变开始圆的半径

x1, y1 :渐变结束圆的圆点坐标

r1 :渐变结束圆的半径

// 以下为JS代码,ctx即为前面定义的画布对象
grdR.addColorStop(0,"red");   // 颜色渐变的起始点 为 渐变对象的起始点;即起始圆的圆周,半径为零即为点
grdR.addColorStop(1,"white"); // 颜色渐变的结束点 为 渐变对象的结束点;即结束圆的圆周,半径为零即为点
ctx.fillStyle = grdR;         // fillStyle 填充渐变对象
ctx.fillRect(10,10,140,140);  // 绘图结果

运行结果:

创建的渐变圆为同心圆,圆心都是 (10,10);

起始圆半径为零,实则就为点 (10,10);结束圆半径为 140px ,渐变色为白色,所以超出结束圆范围的部分都是白色(并不是无色)!

上例中的起始圆在结束圆内,效果可以容易理解;如果起始圆与结束圆相交、分离的情况、、、

这时候,借助一下辅助线来理解;首先假设起始圆为红色,结束圆为蓝色:

相离的情况 :

相交的情况 :

好,以上俩图纯属个人理解,具体正确与否,其实还有待验证!!就单单从几个简单案例来看还是没什么问题的!!

4.4 转换:简易转换、矩阵转换

就因为转换的方法有简单的、也有复杂的,所以我把它分类为俩:

4.4.1 简易转换:scale()、rotate()、translate()

从方法名就能知道其转换用途:

scale() :缩放绘图,接受两个参数,用来设置宽度和高度大小:ctx.scale(scalewidth, scaleheight)

scalewidth / scaleheight :设置宽高的缩放倍数;取值规律为1=100%,0.5=50%,2=200%,依次类推

rotate() :旋转绘图;只接受角度参数:ctx.rotate(angle),可以为数字度数,也可以为弧度值;旋转角度不是以绘画的中的某个点为圆点,具体以那个为圆点我也还不是很清楚

translate() :重新映射画布上的 (0,0) 位置;接受两个参数:context.translate(x,y),即新定义的“源点”位置

如果,画一个矩形,想让它绕着矩形左上角旋转,那么只有 translate() 配合使用,而且translate() 定位“圆点”必须要在 rotate() 旋转之前

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.translate(50,20)  // 先重新定义“原点”(0,0) 为 (50,20)  
ctx.rotate(45*Math.PI/180); // 执行旋转,角度为(1/4)π  
ctx.fillRect(0,0,100,50); // 绘制出矩形

 运行结果:

4.4.2 矩阵转换:transform()、setTransform()

两个方法都可以进行缩放、旋转、移动并倾斜当前的环境,用法相同;唯一不同的就是参照物不同;

transform() :只会影响 transform() 方法调用之后的绘图;在此之前的由 rotate()、scale()、translate() 或 transform() 完成的其他变换仍然有效

setTransform() :变换只会影响 setTransform() 方法调用之后的绘图;与之前的任何变形没有关系

二者共接收6个参数:ctx.transform(a,b,c,d,e,f)/ctx.setTransform(a,b,c,d,e,f) :

a :水平缩放绘图

b :水平倾斜绘图

c :垂直倾斜绘图

d :垂直缩放绘图

e :水平移动绘图

f :垂直移动绘图

有没有觉得这个参数的设置有点不合“常规”?其实,这只是数学的矩阵运算规律,具体怎么运算来的那就。。。不多说了;copy个例子过来看看,看懂了就是懂了:

// 以下为JS代码,ctx即为前面定义的画布对象  
ctx.fillStyle="yellow"; // 第一个矩形,不进行变换
ctx.fillRect(0,0,250,100) // 绘制
ctx.fillStyle="red"; // 红色
ctx.transform(1,0.5,-0.5,1,50,0); // 第二个矩形,红色,不缩放、水平倾斜 0.5、垂直倾斜 -0.5、水平移动 50、垂直不动
ctx.fillRect(0,0,250,100); // 绘制
ctx.fillStyle="blue"; // 蓝色
ctx.transform(1,0.5,-0.5,1,80,20); // 第三个矩形,蓝色,不缩放、水平倾斜 0.5、垂直倾斜 -0.5、水平移动 80、垂直移动20;以上 transform 变换仍然有效
ctx.fillRect(0,0,250,100); // 绘制

运行结果:

好吧好吧,canvas能读懂以上内容,我觉得也算入门了;接下来就靠自己好好实践吧!!

终于还是被你发现了

o(* ̄▽ ̄*)ブ  大叔的卖萌!  o(* ̄▽ ̄*)ブ

评论(0)

沙发空缺中,赶紧抢了吧

发表评论