canvas入门学习草稿纸1——canvas画线和矩形
HTML中的元素canvas只支持一种原生的图形绘制:矩形。 跟着MDN教程一步一脚印。
先看一个例子,更多例子(持续更新):
canvas画线
使用canvas画线,会用到ctx.stroke()
。步骤一般会经历这些:
ctx.beginPath()
新开始一个路径ctx.moveTo(x, y)
将笔头移到某个起点- 想象绘制过程,如绘制直线
ctx.lineTo(x, y)
、绘制圆弧ctx.arc(x, y, r, start, end, anticlockwise)
、绘制贝塞尔曲线ctx.quadraticCurveTo
和ctx.bezierCurveTo
。 ctx.stroke()
其他还有些方法,如设置线条颜色ctx.strokeStyle = 'rgba(x,x,x,a)'
,ctx.closePath
首尾闭合,ctx.fill()
默认会先闭合,再填充闭合区域等等方法。
这里踩过的注意点坑是:
- 笔触不想连起来的话,记得下一次画之前使用
ctx.moveTo()
- 如果想两次绘制的线样式不同,需要先
ctx.beginPath()
重制一下
设置线的样式
线有什么样式好设置的?仔细想想也有不少,比如线的:粗细、颜色、类型(点、虚线、实线等)等。需要注意的是,这些属性都是【赋值】所以都是【属性=值】这种写法,而不是调用方法(除了类型)。这些canvas都有涉及提供:
- 粗细,
ctx.lineWidth
- 颜色,
ctx.strokeStyle
- 类型,
ctx.setLineDash()
绘制矩形
和svg不一样,HTML中的元素canvas只支持一种原生的图形绘制:矩形。如果需要绘制其他形状,可以自己用线绘制,连接。
方法:
ctx.strokeRect(x, y, width, height)
绘制矩形的框子ctx.fillRect(x, y, width, height)
绘制实心的矩形
canvas入门学习草稿纸2——canvas画复杂的图形
圆形
使用canvas画圆形,ctx.arc()
方法: 1. ctx.beginPath()
新开始一个路径 2. ctx.moveTo(x, y)
将笔头移到圆上 3. ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise)
: 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针, bool)来生成。 4. ctx.arcTo(x1, y1, x2, y2, radius)
: 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点 5. ctx.stroke()
画线
arc()函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式: 弧度=(Math.PI/180)*角度。
椭圆
椭圆的画法,有几种:
-
canvas自带的方法
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)
绘制 -
利用
arc()
方法画圆,然后用scale
,以下贴一段demo代码
var canvas = document.getElementById("canvas");
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext("2d");
ctx.lineWidth = 10;
drawEllipseWithArcAndScale(ctx, 110, 250, 100, 30, 'orange'); //椭圆
function drawEllipseWithArcAndScale(ctx, cx, cy, rx, ry, style) {
ctx.save(); // save state
ctx.beginPath();
ctx.translate(cx-rx, cy-ry);
ctx.scale(rx, ry);
ctx.arc(1, 1, 1, 0, 2 * Math.PI, false);
ctx.restore(); // restore to original state
ctx.save();
if(style) ctx.strokeStyle=style;
ctx.stroke();
ctx.restore();
}
这个方法其实是有问题的,1是无法准确的绘制指定的椭圆,2是scale压缩会把压缩地方的线绘制的更粗
- 使用Bezier曲线绘制
drawEllipseWithBezier(ctx, 10, 10, 200, 60, 'blue');
function drawEllipseWithBezier(ctx, x, y, w, h, style) {
var kappa = .5522848,
ox = (w / 2) * kappa,
oy = (h / 2) * kappa,
xe = x + w,
ye = y + h,
xm = x + w / 2,
ym = y + h / 2;
ctx.save();
ctx.beginPath();
ctx.moveTo(x, ym);
ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
if(style)
ctx.strokeStyle=style;
ctx.stroke();
ctx.restore();
}
或者另一种,根据中心点去绘制贝塞尔曲线。
drawEllipseWithBezierByCenter(ctx, 110, 110, 200, 60, '#0099ff');
function drawEllipseWithBezierByCenter(ctx, cx, cy, w, h, style) {
drawEllipseWithBezier(ctx, cx - w/2.0, cy - h/2.0, w, h, style);
}
- 使用
ctx.quadraticCurveTo()
补充
quadraticCurveTo
写法:开始点:moveTo(20,20
) 控制点:quadraticCurveTo(20,100
,200,20) 结束点:quadraticCurveTo(20,100,200,20
)
参考文档: 绘制椭圆代码jsbin
多边形
多边形,其实都只是直线的连接。只要计算好每个点的坐标,就能够绘制出来。
其他
如❤️形等。使用贝塞尔曲线绘制吧。
ctx.beginPath()
ctx.moveTo(280, 140)
ctx.bezierCurveTo(260, 120, 260, 150, 280, 170)
ctx.bezierCurveTo(300, 150, 300, 120, 280, 140)
ctx.stroke()
canvas入门学习草稿纸3——canvas色彩
填充色彩
ctx.fillStyle
赋值颜色: 1. 支持常见颜色名称,如red
、black
、gray
等等 2. 支持十六进制色彩字符串,如:#ffffff
白色等 3. rgba()
、rgb()
方法 4. hsla()
、hsl()
色相、饱和度、明度三个通道,来配置颜色
渐变色
线性渐变
canvas本身提供了createLinearGradient
的方法来,实现线性渐变,创建渐变-渐变色-应用
var grad = ctx.createLinearGradient(xstart,ystart,xend,yend);
grad.addColorStop(stop,color); //color参考上面填充色彩
ctx.fillStyle = grad
ctx.strokeStyle = grad
径向渐变
canvas本身提供了createRadialGradient
的方法,来实现径向渐变,步骤同线性渐变。
var grad = ctx.createRadialGradient(x0,y0,r0,x1,y1,r1);
grad.addColorStop(stop,color); //color参考上面填充色彩
ctx.fillStyle = grad
ctx.strokeStyle = grad
canvas入门学习草稿纸4——canvas的2d变换
2d图形变换常常结合定时绘图,可以实现一些比较简单的动画效果,毕竟是封装好的变换方法。但是综合使用下,也能够实现一些较为复杂的效果。常用的变换方法主要有三个:
- 平移变换
translate(x,y)
2. 旋转角度变换rotate(deg)
3. 缩放scale(sx,sy)
平移
canvas提供了一个平移的基础方法translate(dx,dy)
,用于将当前的绘制原点进行平行偏移。这里的dx、dy是基于当前的原点位置的偏移。PS.如果当前已经使用过一次平移,再次使用时,是基于当前已偏移的原点,进行二次偏移。如:
ctx.translate(100, 100)
ctx.translate(100, 100) //此时的xy位置都是基于200, 200的xy值
因此,在使用完平移后,建议将原点恢复到0,0,否则定位计算将会变的很乱。。 恢复的方法有两种: 1. 使用负偏移,还原到原点,之前平移了多少,这次就返回多少 2. 使用ctx.save()
,ctx.restore()
方法来还原
var canvas = document.getElementById("my-canvas")
var ctx = canvas.getContext("2d")
ctx.save()
ctx.fillStyle = "#f00"
ctx.translate(100,100)
ctx.fillRect(100,100,200,100)
ctx.restore() //还原之前保存的坐标原点
ctx.save()
ctx.fillStyle = "#ddd"
ctx.translate(200,200)
ctx.fillRect(100,100,200,100)
ctx.restore()
旋转
旋转rotate(deg)
参数是弧度,和arc()
里的最后两个参数一样。从纵轴即y轴向上顺时针旋转,注意的是这里的旋转中心点是原点,即坐标(0, 0)。如果想要在你希望的地方作为旋转中心的话,要结合上面说的平移translate()
平移原点,然后旋转。同时要注意的是,这里也有上面平移同样的⚠️注意点,原点平移后,记得还原。
deg = 2 * Math.PI / 360 * 角度,之前也有说过,2π就是360度。
如下,写一个简单的旋转动画。
var canvas = document.getElementById("my-canvas")
var ctx = canvas.getContext("2d")
ctx.save()
ctx.fillStyle = "#f00"
ctx.translate(100,100)
ctx.fillRect(100,100,100,100)
ctx.restore()
ctx.save()
ctx.translate(150, 150)
ctx.rotate(0.5 * Math.PI)
ctx.restore()
ctx.save()
ctx.fillStyle = "#ddd"
ctx.translate(300,100)
ctx.fillRect(100,200,200,100)
ctx.restore()
var ani = setInterval((function(){
var deg = 0
return function() {
if(deg < Math.PI){
ctx.save()
ctx.translate(500, 350)
ctx.clearRect(-200, -200, 400, 400)
ctx.rotate(deg)
ctx.fillStyle = "#ddd"
ctx.fillRect(-100,-50,200,100)
deg += 2 * Math.PI / 360 * 5
ctx.restore()
} else {
ani && clearInterval(ani)
}
}
})(), 20)
缩放
缩放变换scale(scalewidth,scaleheight)
,其中scalewidth、scaleheight分别是【缩放当前绘图的宽度】 和 【缩放当前绘图的高度】的倍数值。 第一次使用起来,觉得有点奇怪。对绘图进行缩放,1. 所有之后的绘图也会被缩放 2.定位也会被缩放 3. 感觉线的粗细这些值也被缩放了
var canvas=document.getElementById("my-canvas")
var ctx=canvas.getContext("2d")
ctx.strokeRect(5,5,25,15)
ctx.scale(2,2)
ctx.strokeRect(5,5,25,15)
transform
写css动画效果时,经常会用到这个transform: translate/rotate/scale...
,学习canvas时,会发现都是相似的。canvas也提供了一个transform
方法。
/**
* @param a 水平缩放绘图
* @param b 水平倾斜绘图
* @param c 垂直倾斜绘图
* @param d 垂直缩放绘图
* @param e 水平移动绘图
* @param f 垂直移动绘图
*
*/
ctx.transform(a,b,c,d,e,f)
就api来看的话,translate平移对应的参数应该是e和f
,rotate对应的是b和c
,scale对应的是a和d
。且注意,transform
也会基于之前变换后绘图的基础上来进行变换的。
同时,canvas还提供了另一个方法,setTransform()
它时不会基于之前绘图2d变换的基础上变换的
canvas入门学习草稿纸5——canvas文字渲染
对于封装好的渲染功能,(这里想指的是那些不是由计算好定位然后绘制图案的操作,而是由canvas本身提供好的绘制功能。比如这里的文字,文字是自己输入的,而不是自己计算好位置,一横一竖的画出来的功能。),很多都可以和其他的地方进行类比,比如html渲染(包括css的样式控制)
可以类比想象到,如果canvas要实现文字渲染功能,肯定需要支持文字的字体、字号大小、文字颜色、粗细、行高等等基本属性配置,初次之外如果像ps那种立体字、阴影等也有可能会在canvas中支持。
通过学习后,发现确实和其他的渲染有很大的相似性。
通过配置文字属性后,通过ctx.fillText
、ctx.strokeText
来编写文字。
文字基本配置
ctx.font
用于配置文字的绝大多数支持配置的属性,如默认值ctx.font='10px sans-serif'
就是字体大小和文字的字体等,即css中的font-size
和font-family
属性。
通过文档来看,是和css中的font支持的是一样的。ctx.font = 'a b c d e f g h i j k'
按照顺序来说,照着文档来写就行,其实一般情况也用不上那么多。前五个比较常用且常见:
字母 | 含义 | 备注 |
---|---|---|
a | font-style | 规定字体样式normal 可以为normal、italic、oblique |
b | font-variant | 规定字体变体 |
c | font-weight | 规定字体的粗细,同css |
d | font-size / line-height | 规定字号和行高,像素 |
e | font-family | 规定字体系列 |
可以通过@font-family
加载字体来使用。
文字对齐
canvas的文字对齐配置有垂直和水平对齐配置。
- 水平对齐,
ctx.textAlign="center|end|left|right|start"
- 垂直对齐,
ctx.textBaseline="alphabetic|top|hanging|middle|ideographic|bottom"
文字阴影
canvas的阴影shadow不仅仅是对文字有效的,对其他也有效。 - ctx.shadowColor
,配置阴影颜色 - ctx.shadowOffsetX
,阴影x轴位移。正值向右,负值向左 - ctx.shadowOffsetY
,阴影y轴位移。正值向下,负值向上 - ctx.shadowBlur
,阴影模糊滤镜
canvas入门学习草稿纸6——canvas 图片处理
添加图片
ctx.drawImage
用来将图片绘制到 canvas 上。
此处demo
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = "../assets/meinv.jpeg";
img.onload = function() {
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 220, 230, 200, 200);
ctx.translate(240, 260);
ctx.drawImage(img, 20, 30, 160, 160);
};
语法:
ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
参数 | 描述 |
---|---|
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
图片处理
图片处理,需要先获取图片的数据详情,canvas 提供了获取图片像素数据,ctx.getImageData()
方法复制画布上指定矩形的像素数据,处理好像素数据后,然后通过 putImageData()
将图像数据放回画布。
getImageData
获取图片像素数据,方法返回 ImageData 对象,是拷贝了画布指定矩形的像素数据。
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
- R - 红色 (0-255)
- G - 绿色 (0-255)
- B - 蓝色 (0-255)
- A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
反色(负片)
反转图片中所有颜色,使用b = 255 - a
来计算:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, c.width, c.height);
// 反转颜色
for (var i = 0; i < imgData.data.length; i += 4) {
imgData.data[i] = 255 - imgData.data[i];
imgData.data[i + 1] = 255 - imgData.data[i + 1];
imgData.data[i + 2] = 255 - imgData.data[i + 2];
imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);
黑白照片
根据公式处理所有颜色数据: Gray = (Red * 0.3 + Green * 0.59 + Blue * 0.11)
:
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, c.width, c.height);
// 反转颜色
for (var i = 0; i < imgData.data.length; i += 4) {
var red = imgData.data[i],
green = imgData.data[i + 1],
blue = imgData.data[i + 2];
var gray = 0.3 * red + 0.59 * green + 0.11 * blue;
imgData.data[i] = gray;
imgData.data[i + 1] = gray;
imgData.data[i + 2] = gray;
}
ctx.putImageData(imgData, 0, 0);
高斯模糊
咱也不懂,咱就直接按照高斯公式来吧:
G(x,y) = \frac{1}{2\pi\sigma^2}e^(-\frac{x^2+y^2}{2\sigma^2})
摘一段网上的高斯模糊处理图片的数据方法:
function gaussBlur(imgData) {
console.log(imgData);
var pixes = imgData.data;
var width = imgData.width;
var height = imgData.height;
var gaussMatrix = [],
gaussSum = 0,
x,
y,
r,
g,
b,
a,
i,
j,
k,
len;
var radius = 30;
var sigma = 5;
a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
b = -1 / (2 * sigma * sigma);
//生成高斯矩阵
for (i = 0, x = -radius; x <= radius; x++, i++) {
g = a * Math.exp(b * x * x);
gaussMatrix[i] = g;
gaussSum += g;
}
//归一化, 保证高斯矩阵的值在[0,1]之间
for (i = 0, len = gaussMatrix.length; i < len; i++) {
gaussMatrix[i] /= gaussSum;
}
//x 方向一维高斯运算
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = x + j;
if (k >= 0 && k < width) {
//确保 k 没超出 x 的范围
//r,g,b,a 四个一组
i = (y * width + k) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
// 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
// console.log(gaussSum)
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
// pixes[i + 3] = a ;
}
}
//y 方向一维高斯运算
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
r = g = b = a = 0;
gaussSum = 0;
for (j = -radius; j <= radius; j++) {
k = y + j;
if (k >= 0 && k < height) {
//确保 k 没超出 y 的范围
i = (k * width + x) * 4;
r += pixes[i] * gaussMatrix[j + radius];
g += pixes[i + 1] * gaussMatrix[j + radius];
b += pixes[i + 2] * gaussMatrix[j + radius];
// a += pixes[i + 3] * gaussMatrix[j];
gaussSum += gaussMatrix[j + radius];
}
}
i = (y * width + x) * 4;
pixes[i] = r / gaussSum;
pixes[i + 1] = g / gaussSum;
pixes[i + 2] = b / gaussSum;
}
}
console.log(imgData);
return imgData;
}
canvas入门学习草稿纸7——canvas的实际应用
To be continued...