这里的canvas是指html5 中的canvas,如有雷同,恕不解释。

文章更新:

2013.08.28:

此次更新是修复上一次更新的遗留问题,即”有返回值的函数无法得到正确的返回值”,比如getImageData,isPointInPath等,这个问题是因为为了实现链式语法,函数会总是返回this.其实要修复这个问题是很简单的,就是判断函数执行后是否有返回值,有的话就返回这个返回值,没有就继续返回this(大多数情况下都没有).

不过由于这个判断的原因,可能会对整体效率有那么一点点的影响;而且,有返回值的函数的后面,能继续链式语法.

另外我把原来的用来放函数名的数组变成了一个字符串,因为这样可以少写很多引号.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var XtendCanvas = function () {
    var pro = 'save,restore,scale,rotate,translate,transform,createLinearGradient,createRadialGradient,getLineDash,clearRect,fillRect,beginPath,closePath,moveTo,lineTo,quadraticCurveTo,bezierCurveTo,arcTo,rect,arc,fill,stroke,clip,clearShadow,fillText,strokeText,strokeRect,drawImage,drawImageFromRect,putImageData,createPattern,createImageData,textBaseLine,strokeStyle,lineWidth,globalAlpha,fillStyle,font,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,lineCap,lineJoin,miterLimit,getImageData,isPointInPath'.split(',');
    function fn (canvas) {
        this.context = canvas.getContext('2d');
    }
    var old = document.createElement('CANVAS').getContext('2d');
    for(var i = 1,p=pro[0];p;p=pro[i++]) {
        // console.log(i +' >> '+ p + ' >> ' + typeof CanvasRenderingContext2D.prototype[p]);
        fn.prototype[p] = function  (p) {
            return (typeof old[p] === 'function') ? function  () {
                    var r = this.context[p].apply(this.context,arguments);
                    return r === undefined ? this : r;
                } : function  () {
                    this.context[p] = Array.prototype.join.call(arguments);
                    return this;
                };
        }(p);
    }  
    return function  (canvas) {
        return new fn(canvas);
    };  
}()

2013.03.19:

优化了XtendCanvas结构,数组中去掉了一些没必要存在的函数(如isPointInPath),修复了Firefox下报错的问题,并尽量减少了判断与循环的次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var XtendCanvas = function () {
    var pro = ['save','restore', 'scale', 'rotate', 'translate', 'transform',  'createLinearGradient', 'createRadialGradient', 'getLineDash', 'clearRect', 'fillRect', 'beginPath', 'closePath', 'moveTo', 'lineTo', 'quadraticCurveTo', 'bezierCurveTo', 'arcTo', 'rect', 'arc', 'fill', 'stroke', 'clip',  'measureText', 'clearShadow', 'fillText', 'strokeText',   'strokeRect', 'drawImage', 'drawImageFromRect',  'putImageData', 'createPattern', 'createImageData', 'getImageData','textBaseLine','strokeStyle','lineWidth','globalAlpha','fillStyle','font','shadowOffsetX','shadowOffsetY','shadowBlur','shadowColor','lineCap','lineJoin','miterLimit'];
    function fn (canvas) {
        this.context = canvas.getContext('2d');
    }
    var old = document.createElement('CANVAS').getContext('2d');
    for(var i = 1,p=pro[0];p;p=pro[i++]) {
        // console.log(i +' >> '+ p + ' >> ' + typeof CanvasRenderingContext2D.prototype[p]);
        fn.prototype[p] = function  (p) {
            return (typeof old[p] === 'function') ? function  () {
                    this.context[p].apply(this.context,arguments);
                    return this;
                } : function  () {
                    this.context[p] = Array.prototype.join.call(arguments);
                    return this;
                };
        }(p);
    }  
    old = null;
    return function  (canvas) {
        return new fn(canvas);
    };
   
}()

说实话我对canvas的原生绘图语法很不满意。以下是一段很平常的canvas绘图代码:继续阅读

本文属于《html5 Canvas画图系列教程

继续讲canvas中画曲线的方法,今天讲quadraticCurveTo

说实话这个方法有点吓人,单从函数名称上都可以初体验。话说,我觉得有必要把这个函数名缩短。

quadratic的意思是二次,即数学中二次元方程那个二次。而ctx.quadraticCurveTo的参数如下:

ctx.quadraticCurveTo(x1,y1,x,y);

其中x,y是终点的坐标,而x1,y1是曲线控制点的坐标?什么?你问我起点在哪里?起点在此之前用moveTo确定。

我之所以把控制点的坐标带上序号1,是因为后面讲的某个画曲线的函数有两个控制点,也就有x2,y2了,所以这里先打个预防针。继续阅读

本文属于《html5 Canvas画图系列教程

上一篇文章讲了canvas的arc方法,这一篇讲和他有关的arcTo方法。

arc与arcTo,从名字都能看出来相似。arcTo也是画曲线的方法,而且他画出的曲线也是正圆的一段弧线。但他的参数和arc简直是不共戴天~

ctx.arcTo(x1,y1,x2,y2,radius);

arcTo的参数中包括两个点,而且这两个点中并没有表示圆心的点,仅仅最后的参数是圆的半径,表示arcTo和圆有那么点关系。

网上关于arcTo的文章很少,好不容易找到一篇还是外国的;而且canvas画图木有直观工具,只能靠猜,arcTo害我猜了半天。。

为了直观的描述,我采取了一种辅助办法:arcTo画到哪里,我就用lineTo也画到相应的点,以查看他们的关系。就是画辅助线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var x0=100,
y0=400,
x1 = 500,
y1 = 400,
x2 = 450,
y2 = 450;
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.strokeStyle = "#f00";
ctx.lineWidth = 2;
ctx.arcTo(x1,y1,x2,y2,20);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "rgba(0,0,0,0.5)";
ctx.lineWidth = 1;
ctx.moveTo(x0,y0);
ctx.lineTo(x1,y1);
ctx.fillText('x1,y1',x1+10,y1+10)
ctx.lineTo(x2,y2);
ctx.fillText('x2,y2',x2+10,y2)
ctx.stroke();

看起来代码有点多,其实很简单。我用了几个变量来保存坐标值,其余的都是canvas的操作了。

变量说明:x0,y0是起点坐标,x1,y1是第一个点坐标,x2,y2就是第二个点坐标。其中lineTo画的直线是半透明的1px黑线,arcTo画的线条是2px的红线

刷新页面,即可看到下图。

canvas arcTo

不得不说这条红线很像一个钩子。
于是arcTo的规律就找到了,他其实是通过起点,第1点,第2点的两条直线,组成了一个夹角,而这两条线,也是参数圆的切线

其中圆的半径决定了圆会在什么位置与线条发生切边。就像一个球往一个死角里面滚,球越小就滚得越进去,越靠近死角;球大则反之。

这是一个很严肃的学术问题,大家可不要YY。

让我们把球球变大吧!

1
ctx.arcTo(x1,y1,x2,y2,50); //半径改成50

canvas arcTo
如图,你可以看到圆弧变得很大,甚至都不和直线相切了。

当然,实际上他们还是相切的,因为切线是无限延长的。

不过这时候这个钩子的钩变得比柄还长了。

我们继续探索,把圆继续变大,把起点与第1点的距离缩短。

1
2
3
var x0=400; //起点x坐标从100变400
...
ctx.arcTo(x1,y1,x2,y2,100); //圆的半径变大到100

然后你就会看到这么个奇特的图形。

canvas arcTo

本来是个钩子,现在被硬生生的掰弯了,还掰到反方向了!有点像酒瓶架了。

不过,大家注意看,这个圆依然是和两条线相切的!只是现在两条线的长度都满足不了这个圆了!他已经把两条线都无线延长了!

这个钩子柄什么时候会发生反转呢?如果你几何学的好,你可以试着理解一下点与圆的切线方程。。

arcTo方法中有个很重要的点,这个重要的点就是代码中的x1,y1,只要他到圆的切点的距离,超过了他到起点(x0,y0)的距离,就会发生反转。

从图中我们可以看到,(x2,y2)这个点的坐标可以无限变化,只要他始终是切线上的一个点,那么在圆的半径不变的情况下,arcTo画出的图形不会有任何变化。这点需要特别注意。

让我用我拿不上台面的几何知识来验证下这个命题吧。为了方便计算,我先把两条线的夹角改成90度。

1
2
3
4
5
6
var x0=100,
y0=400,
x1 = 500,
y1 = 400,
x2 = 500,
y2 = 450;

更改后就是90度张开了哟!我们保持球的半径不变。刷新后:

canvas arcTo
我们把y2变大,也就是延长了一条切线,把他变成550,刷新后:

canvas arcTo
切线是延长了,但arcTo画出的红线没有任何变化。

本文属于《html5 Canvas画图系列教程

canvas画线条这篇文章中,我讲了画直线的方法,按理这篇画曲线的文章早该发了,但由于canvas画曲线比较特殊,我还没摸透,所以要一步步尝试。

canvas里画曲线的难点之一,就在于他连曲线的函数就有4个!分别是arc,arcTo,quadraticCurveTo,bezierCurveTo.我从最简单的arc方法讲起吧。

arc的作用是画一个正规的圆弧,可以是一个完整的圆,也可以是一个圆的某一段弧线。

arc的语法如下:

context.arc(x, y, radius, startAngle, endAngle, anticlockwise)

他的参数我解释一下,即

arc(圆心x,圆心y,半径,开始的角度,结束的角度,是否逆时针)

如果我们用arc画一个完整的圆,该怎么搞?大家注意到参数中有个开始角度与结束角度,如果我们开始角度是0,结束角度是360,不就是个正圆了?

正解!但要注意的是,这里的角度是用“弧度”来表示的(Flash也是如此),一个完整的圆即360度,就是2PI弧度

所以我们这么写:

1
2
3
ctx.arc(400,400,20,0,Math.PI*2);
ctx.fill();
ctx.stroke();

和lineTo一样,arc也是画的路径,所以我们要在他后面调用填充或描边的方法才能显出图形(图中采用了红色的strokeStyle与半透明红色的fillStyle)。

现在我们来画一个1/4圆,角度嘛,就是90度。前面说了,360度角即2PI弧度,那么一度角就是2PI/360=PI/180弧度,那么90度就是2PI/360*90=PI/2弧度(其他的角度请自行计算)。

1
ctx.arc(400,400,20,0,Math.PI*2/4);


由图我们可以确定arc的0度就是数学上常用的0度,但是角度默认是顺时针张开的,与数学模型相反(跟坐标有关,因为canvas坐标也与数学坐标相反)。

不过前面不是有个参数是确定是否逆时针吗?我们把他设为true如何?

1
ctx.arc(400,400,20,0,Math.PI*2/4,true);


你会看到,角度变成了逆时针展开,导致弧线变成了360-90=270度。

但是!大家要注意一点,就是起点与终点的计算方式,始终是以0度为起点,并顺时针延伸的,不存在顺反的说法。顺反时针只是弧线的绘制方向。

这样不仅仅可以防止顺来逆去的容易混淆,而且更方便计算。

不过,灵活的使用逆时针,有时候很有用。

上面的例子,我们的起点角度都是0,现在我们试试其他的起点角度吧,比如90度。

1
ctx.arc(400,400,20,Math.PI*2/4,Math.PI*2+Math.PI);

如果我们起点是90度而终点也是90度,那结果就是什么都不得画,所以我把终点角度改成了180度,最后得到下图的图形。

问题:如果我们从非0度起点来画一个完整的圆,行不行?当然也可以,只要你把弧线的终点设置为360度+起点角度,如:

1
ctx.arc(400,400,20,Math.PI*2/4,Math.PI*2+Math.PI*2/4); //起点90度,终点360+90度

不过这种做法纯属没事找事,正常人是不会用的。

总结:Canvas的arc方法是画正圆弧线的办法,也只能画正弧线,没法画其他奇怪的弧线,比如S形——虽然我最喜欢S形了。

本文属于《html5 Canvas画图系列教程

一般绘图的方式有两种,即填充和描边,前面的文章已经讲了描边的方法stroke,本文就讲一下Canvas中填充图形的方法。

填充即fill(),很直白吧?而且和strokeStyle表示描边样式一样,fillStyle即表示填充样式。

ctx.fillStyle = '颜色';

默认的填充样式是不透明的黑色

提问:未闭合的路径可以填充吗

可以。Canvas会从你当前路径的终点直接连接到起点,然后填充。如图:

但你可以发现,最后一段没有描边

记得我们前一篇文章用4条线画了一个正方形,但canvas不会这么差劲,连直接绘制矩形的函数都没有。你可以使用fillRect()直接填充一个矩形:

ctx.fillRect(x,y,width,height);

这里的x,y是指矩形的左上角起点的坐标,记住。

说到fillRect,就不得不提strokeRect,你猜对了,他的意思就是直接描边出一个矩形。

还有fillText与strokeText,至于作用嘛,你可能都猜到了,我这里先不讲,大家先预习吧。

Canvas填充渐变色

在Canvas中,渐变色同样分为两种,即线性渐变径向渐变,而且创建他们的方法也是独立的。我们先看如何创建线性渐变。

创建线性渐变=createLinearGradient——看,依然很直接的单词。他的语法如下:

createLinearGradient(x1,y1,x2,y2)

有4个参数呢!看起来好复杂,其实这个挺简单的,因为我们前面已经说了,平面世界里的一个点是由x坐标和y坐标确定的。所以,x1,y1就是表示线性渐变的起点坐标,x2,y2就表示终点坐标。

这样做的好处很明显,如果我们想创建一个斜着的线性渐变,很方便。但我们先创建一个水平的线性渐变试试吧。

1
var linear = ctx.createLinearGradient(100,100,200,100);

渐变好像创建了,那么我们可以填充了吗?————这个渐变是空的,没有颜色。

往渐变条里加颜色的方法是addColorStop(位置,颜色).但要注意了,这个addColorStop不是加在画笔上,而是加在前面的那个保存渐变的变量上,我这里是linear.

1
2
3
4
var linear = ctx.createLinearGradient(100,100,200,100);
linear.addColorStop(0,'#fff');
linear.addColorStop(0.5,'#f0f');
linear.addColorStop(1,'#333');

我这里用了3个addColorStop,即为渐变条加上了3个颜色。

注意:addColorStop的位置参数,永远是介于0-1之间的数字,可以是两位小数,表示百分比。他没法接收’3px’这样的参数。

这时候,我们就可以填充渐变色了,但我们必须先把定义好的渐变赋给fillStyle.

1
2
3
4
5
6
7
var linear = ctx.createLinearGradient(100,100,200,100);
linear.addColorStop(0,'#fff');
linear.addColorStop(0.5,'#f0f');
linear.addColorStop(1,'#333');
ctx.fillStyle = linear; //把渐变赋给填充样式
ctx.fillRect(100,100,100,100);
ctx.stroke();

注意,fillRect与strokeRect画出的都是独立的路径,如上面的代码,在fillRect后调用描边,并不会把刚刚画出的矩形描边,strokeRect同理。

经过测试后,发现了一个很蛋疼的问题,就是线性渐变的坐标是相对于整个Canvas范围而言的。比如我这里,我的线性渐变起点就是100,100,如果我画了一个矩形在0,0的位置,用这个渐变来填充,就会发现没有填充——因为我的渐变的范围根本就超过了矩形的范围。

这真是一个坑爹的设定。

提问:渐变起点之前与渐变终点之后还会填充颜色吗

会。起点之前的颜色就是起点色,终点之后的颜色一直是终点色。

如何终止终点色,你可以在结束色之后再填一个透明的结束色。如:

1
2
linear.addColorStop(0.99,'#333');
linear.addColorStop(1,'rgba(51,51,51,0)');

依照前面的计划,我再建一个倾斜的线性渐变试试。只需要改createLinearGradient的参数即可.

1
var linear = ctx.createLinearGradient(100,100,200,200);

效果如图:

然后,让我们来试试径向渐变(圆形渐变)。与createLinearGradient类似,创建径向渐变的方法是:createRadialGradient,但他们的参数可大不相同:

createRadialGradient(x1,y1,r1,x2,y2,r2)

其中的x1,y1,x2,y2依旧表示起点和终点,不过这里的起点和终点都是一个圆,而x,y则是圆心的坐标。所以,r1与r2分别是起点圆的半径和终点圆的半径。如图:

在我的印象中,貌似径向渐变就是一个圆,圆心就是起点,圆的半径就是终点。但canvas里面的径向渐变竟然搞的不一样了,起点一个圆,终点一个圆,和我的理解有差距。

我们从最简单的做起。首先,做一个很正规的径向渐变,即渐变圆形的圆心就是渐变的起点。由于正规的径向渐变,中心即圆心,所以我们应该尽量避免发生偏斜。那么,我们把终点圆的圆心与起点圆的圆心重合吧?

1
2
3
4
5
var radial = ctx.createRadialGradient(55,55,10,55,55,55); //重合的圆心坐标
radial.addColorStop(0,'#fff');
radial.addColorStop(0.5,'#ff0');
radial.addColorStop(0.9,'#555');
radial.addColorStop(1,'#f00');

这里我设置的径向渐变起点圆和终点圆的圆心坐标相同,而起点圆半径为10,终点圆半径为55.最后画出的径向渐变范围是一个宽高110的圆形,说明渐变范围以终点圆的范围为准。

(可以看到终点圆范围之外还是有颜色,这个颜色就是终点色,但是,如果你企图使用radial.addColorStop(1.5,’#0f0′);这样来定义渐变范围之外的颜色,你还是会收到一个错误).

那么,起点圆的半径有什么用呢?——本来正常的径向渐变的中心(姑且称之为“变心”…吧)只是一个点,不应该是一个圆的。其实我们没错,这个起点圆就相当于一个圆点而已,只是可能比较大。

让我们把起点圆的半径变得很大,接近于终点圆的半径:

1
var radial = ctx.createRadialGradient(55,55,50,55,55,55); //很接近

其他的colorStop都不变,然后图形就变成了这个样子。

也就是说,canvas中径向渐变的起点色,是从起点圆的范围之外绘制的,而起点圆的整个颜色都是起点色。

我们把起点圆的半径设为0,那径向渐变的“变心”,就真的是一个点了。

大部分时候我们都不需要很正规的径向渐变,反而希望他的变心是偏移的,类似下图:

这个时候,canvas径向渐变两个圆的优势就出来了,只要我们起点圆与终点圆的圆心不重合,那变心也就会偏移:

1
var radial = ctx.createRadialGradient(75,75,0,55,55,55);

但此时的渐变范围依然是终点圆的范围哈。

很多人天生就有一种搞破坏的心理,比如这里,终点圆的半径总比起点圆大,但如果把他们反过来会怎么样呢?

1
var radial = ctx.createRadialGradient(75,75,55,55,55,0);

经测试,这样不会报错,只是原来的从内到外的渐变反过来变成了从外到内的渐变了。这是个好用法。

还有个问题,如果我们把起点圆的圆心偏移了,并且起点圆的范围超出了终点圆的范围,

这时会发生什么?

啊!?这是什么情况?!

当起点圆和终点圆只有部分重叠的时候,就会出现这种情况。所以,如果你需要正常的径向渐变,请保证其中一个圆完全包住了另一个圆

另外,既然渐变可以赋给fillStyle,那么也可以赋给strokeStyle。效果你懂的。