渐变
线性渐变
在一条直线上进行渐变
- 调用createLinearGradient()方法创建一个linearGradient对象
var gnt = cxt.createLinearGradient(x1, y1, x2, y2);
- x1、y1表示渐变色开始点的坐标
- x2、y2表示渐变色结束点的坐标
- 添加渐变(开始)
gnt.addColorStop(value1,color1);
- value1 渐变开始的位置,取值 0~1
- color1 渐变开始的颜色
- 添加渐变(结束)
gnt.addColorStop(value2,color2);
- value2 渐变结束的位置,取值 0~1
- color2 渐变结束的颜色
第2和第3步可以反复多次添加多个渐变,每次渐变都以上一个渐变的结束位置为开始位置
- 填充渐变
cxt.fillStyle = gnt; cxt.fill();
var gnt = cxt.createLinearGradient(0,150, 200, 150); gnt.addColorStop(0, "HotPink"); gnt.addColorStop(1, "white"); cxt.fillStyle = gnt; cxt.fillRect(0, 0, 200, 150);
var text = "绿叶学习网"; cxt.font = "bold 50px 微软雅黑"; var gnt = cxt.createLinearGradient(0, 75, 200, 75); gnt.addColorStop(0, "HotPink"); gnt.addColorStop(1, "LightSkyBlue"); cxt.fillStyle = gnt; cxt.fillText(text, 10, 90);
径向渐变(用得少)
径向渐变,是一种从起点到终点、颜色从内到外进行的圆形渐变(从中间向外拉,像圆一样)。径向渐变是圆形渐变或椭圆形渐变,颜色不再沿着一条直线渐变,而是从一个起点向所有方向渐变。
- 调用createLinearGradient()方法创建一个radialGradient对象
var gnt = cxt.createRadialGradient(x1,y1,r1,x2,y2,r2);
- (x1,y1)表示渐变开始圆心的坐标,r1表示渐变开始圆心的半径。
- (x2,y2)表示渐变结束圆心的坐标,r2表示渐变结束圆的半径。
- 当起点圆与终点圆的圆心坐标相同时,会有一种圆形渐变的效果
- 添加渐变(开始)
gnt.addColorStop(value1,color1);
- value1 渐变开始的位置,取值 0~1
- color1 渐变开始的颜色
- 添加渐变(结束)
gnt.addColorStop(value2,color2);
- value2 渐变结束的位置,取值 0~1
- color2 渐变结束的颜色
第2和第3步可以反复多次添加多个渐变,每次渐变都以上一个渐变的结束位置为开始位置
- 填充渐变
cxt.fillStyle = gnt; cxt.fill();
fill()可以改为fillRect()或fillText()。其中fillRect()表示矩形渐变,fillText()表示文字渐变。
//画圆 cxt.beginPath(); cxt.arc(80, 80, 50, 0, Math.PI * 2, true); cxt.closePath(); //渐变 var gnt = cxt.createRadialGradient(100, 60, 10, 80, 80, 50); gnt.addColorStop(0, "white"); gnt.addColorStop(0.9, "orange"); gnt.addColorStop(1, "rgba(0,0,0,0)"); //填充 cxt.fillStyle = gnt; cxt.fill();
gradient = cxt.createRadialGradient(60, 60, 0, 60, 60, 60); gradient.addColorStop("0", "magenta"); gradient.addColorStop("0.25", "blue"); gradient.addColorStop("0.50", "green"); gradient.addColorStop("0.75", "yellow"); gradient.addColorStop("1.0", "HotPink"); cxt.fillStyle = gradient; cxt.fillRect(0, 0, 120, 120);
var i = 0; setInterval(function () { gradient = cxt.createRadialGradient(60, 60, 0, 60, 60, 60); gradient.addColorStop(i * 0, "magenta"); gradient.addColorStop(i * 0.25, "blue"); gradient.addColorStop(i * 0.50, "green"); gradient.addColorStop(i * 0.75, "yellow"); gradient.addColorStop(i * 1.0, "HotPink"); cxt.fillStyle = gradient; i = i + 0.1; if (i >= 1) { //超过颜色点值后,自动归0 i = 0; } cxt.fillRect(0, 0, 120, 120); }, 50);
阴影
属性 |
说明 |
shadowOffsetX |
阴影与图形的水平距离,默认值为0。大于0时向右偏移,小于0时向左偏移 |
shadowOffsetY |
阴影与图形的垂直距离,默认值为0。大于0时向下偏移,小于0时向上偏移 |
shadowColor |
阴影的颜色,默认值为黑色 |
shadowBlur |
阴影的模糊值,默认值为0。该值越大,模糊度越强;该值越小,模糊度越弱 |
Canvas阴影属性使用的也是W3C坐标系,y 轴向下
//设置左上方向的阴影 cxt.shadowOffsetX = -5; cxt.shadowOffsetY = -5; cxt.shadowColor = "LightSkyBlue "; cxt.shadowBlur = 1; cxt.fillStyle = "HotPink"; cxt.fillRect(30, 30, 50, 50); //设置右下方向的阴影 cxt.shadowOffsetX = 5; cxt.shadowOffsetY = 5; cxt.shadowColor = "LightSkyBlue "; cxt.shadowBlur = 10; cxt.fillStyle = "HotPink"; cxt.fillRect(100, 30, 50, 50);
//定义文字 var text = "绿叶学习网"; cxt.font = "bold 60px 微软雅黑"; //定义阴影 cxt.shadowOffsetX = 5; cxt.shadowOffsetY = 5; cxt.shadowColor = "LightSkyBlue "; cxt.shadowBlur = 10; //填充文字 cxt.fillStyle = "HotPink"; cxt.fillText(text, 10, 90);
//创建image对象 var image = new Image(); image.src = "images/princess.png"; image.onload = function () { //定义阴影 cxt.shadowOffsetX = 5; cxt.shadowOffsetY = 5; cxt.shadowColor = "HotPink"; cxt.shadowBlur = 10; cxt.fillRect(40, 15, 120, 120); cxt.drawImage(image, 40, 15); }
四个方向的阴影效果,只需要shadowOffsetX和shadowOffsetY这两个属性的值都定义为0就可以了。
//创建image对象 var image = new Image(); image.src = "images/princess.png"; image.onload = function () { //定义阴影 cxt.shadowOffsetX = 0; cxt.shadowOffsetY = 0; cxt.shadowColor = "HotPink"; cxt.shadowBlur = 10; cxt.fillRect(40, 15, 120, 120); cxt.drawImage(image, 40, 15); }
路径
方法 |
说明 |
beginPath() |
开始一条新的路径 |
closePath() |
关闭当前路径 |
isPointInPath() |
判断某一个点是否存在于当前路径内 |
beginPath()
cxt.beginPath();
用于开始一个新路径(同时也是结束上一个路径),刚开始绘图时,其实都默认执行了一次 beginPath() 开始了一个新路径,直到再次遇到 beginPath() 才会结束默认创建的路径,而开始一段新路径。
Canvas基于“状态”绘制图形。每一次绘制(stroke()或fill()),Canvas会检测整个程序定义的所有状态,这些状态包括strokeStyle、fillStyle、lineWidth等。当一个状态值没有被改变时,Canvas就一直使用最初的值,当一个状态值被改变时,需要分两种情况考虑。
(1)如果使用beginPath()开始一个新的路径,则不同路径使用不同的值。
(2)如果没有使用beginPath()开始一个新的路径,则后面的值会覆盖前面的值(后来者居上原则)。
cxt.lineWidth = 5; //第1条直线 cxt.moveTo(50, 40); cxt.lineTo(150, 40); cxt.strokeStyle = "red"; cxt.stroke(); //第2条直线 cxt.moveTo(50, 80); cxt.lineTo(150, 80); cxt.strokeStyle = "green"; cxt.stroke(); //第3条直线 cxt.moveTo(50, 120); cxt.lineTo(150, 120); cxt.strokeStyle = "blue"; cxt.stroke();
三条直线都属于同一个路径,所以cxt.strokeStyle=‘green’;会覆盖cxt.strokeStyle=‘red’;,然后cxt.strokeStyle=‘blue’;会覆盖cxt.strokeStyle=‘green’;。因此strokeStyle属性最终取值为blue,即三条直线都是blue。
cxt.lineWidth = 5; //第1条直线 cxt.beginPath(); cxt.moveTo(50, 40); cxt.lineTo(150, 40); cxt.strokeStyle = "red"; cxt.stroke(); //第2条直线 cxt.beginPath(); cxt.moveTo(50, 80); cxt.lineTo(150, 80); cxt.strokeStyle = "green"; cxt.stroke(); //第3条直线 cxt.beginPath(); cxt.moveTo(50, 120); cxt.lineTo(150, 120); cxt.strokeStyle = "blue"; cxt.stroke();
使用beginPath()后,三条直线将位于不同的路径中。因此,不同路径中定义的状态不会像上一个例子那样发生覆盖。
判断是否属于同一路径的标准是:是否使用了beginPath(),而不是视觉上是否有首尾连线。
Canvas中的绘制方法如stroke()、fill()等,都是以“之前最近的beginPath()”后面所有定义的状态为基础进行绘制的。
closePath()
cxt.closePath();
用于关闭路径,即将同一个路径的起点与终点连接起来,使其成为一个封闭的图形。
常用于绘制多边形的最后一步。
如果Canvas只有一条线段的话,那么closePath()方法就什么都不做。
cxt.arc(70, 70, 50, 0, -90 * Math.PI / 180, true); cxt.stroke(); cxt.closePath();
cxt.lineWidth = 10; cxt.strokeStyle = "HotPink"; cxt.moveTo(40, 60); cxt.lineTo(100, 60); cxt.lineTo(100, 30); cxt.lineTo(150, 75); cxt.lineTo(100, 120); cxt.lineTo(100, 90); cxt.lineTo(40, 90); cxt.lineTo(40, 60); cxt.stroke();
将lineWidth定义得足够大(10px)时,如果使用lineTo()方法来关闭图形,会有一个如图所示的“缺口”小问题。有两种方法可以解决:
- 定义lineCap属性值为square。
- 在stroke()方法之前使用closePath()关闭图形(推荐)
isPointInPath()
cxt.isPointInPath(x , y);
用于判断点 x,y 是否在当前路径中,若在返回true,否则返回false,仅对 rect()方法有效,对 strokeRect() 和 fillRect() 无效。
cxt.strokeStyle = "HotPink"; cxt.rect(50, 50, 80, 80); cxt.stroke(); if (cxt.isPointInPath(100, 50)) { alert("点(100,100)存在于当前路径中"); }
cxt.moveTo(50, 50); cxt.lineTo(150, 50); cxt.stroke(); if (cxt.isPointInPath(100, 50)) { alert("点(50,100)存在于当前路径中"); }
上面代码只在IE浏览器中运行中有效果,但是在Google浏览器和Firefox中不会弹出对话框。实际上,当我们想要使用isPointInPath()方法判断某个点是否位于一条直线上时,在Goole和Firefox浏览器中都是无法实现的。不过可以使用isPointInPath()方法判断某个点是否位于一个图形(如矩形、圆形等)上面。
状态
Canvas基于“状态”来绘制图形。每一次绘制(stroke()或fill()),Canvas会检测整个程序定义的所有状态,这些状态包括strokeStyle、fillStyle、lineWidth等。当一个状态值没有被改变时,Canvas就会一直使用最初的值。当一个状态值被改变时,分两种情况考虑:
(1)如果使用beginPath()开始一个新的路径,则不同路径使用不同的值。
(2)如果没有使用beginPath()开始一个新的路径,则后面的值会覆盖前面的值(后来者居上原则)。
状态的保存和恢复
Canvas提供了两个操作状态的方法:
save() 保存“当前状态”,如剪切状态、变换状态和绘图状态(各种绘图样式属性),不能保存路径状态(开始新的路径,只有beginPath()一个方法),也不能保存图形(Canvas只有当前一个上下文环境,如果想要恢复图形,就只能清空画布再重绘。)。
restore() 恢复“之前保存的状态”。
save()和restore()一般情况下都是成对配合使用,使用场景如下:
(1)图形或图片裁切。
(2)图形或图片变换。
(3)以下属性改变的时候:fillStyle、font、globalAlpha、globalCompositeOperation、lineCap、lineJoin、lineWidth、miterLimit、shadowBlur、shadowColor、shadowOffsetX、shadowOffsetY、strokeStyle、textAlign、textBaseline。
应用一:清除剪切区域
创建剪切区域 clip()
创建剪切区域后,绘制的图形都只限于这个剪切区域之内,超出剪切区域的部分不会显示(被剪切掉了)。
把整个画布(Canvas)看成一个房子,clip()方法的剪切区域则可以看成一扇窗户。即使房子再大,最终透过窗户所能看到的空间也就只有窗户这么大。
cxt.clip();
- 绘制一个基本图形
- 调用 clip() 创建一个剪切区域
clip()方法也不支持Canvas自带的两个方法:strokeRect()、fillRect()。如果要使用strokeRect()和fillRect(),请使用rect()方法来代替。
//绘制一个"描边圆",圆心为(50,50),半径为40 cxt.beginPath(); cxt.arc(50, 50, 40, 0, 360 * Math.PI / 180, true); cxt.closePath(); cxt.strokeStyle = "HotPink"; cxt.stroke(); //使用clip(),使得"描边圆"成为一个剪切区域 cxt.clip(); //绘制一个"填充矩形" cxt.beginPath(); cxt.fillStyle = "#66CCFF"; cxt.fillRect(50, 50, 100, 80);
若注释掉 cxt.clip();
清除剪切区域
在图形或者图片剪切(clip())之前使用save()方法来保持当前状态,然后在剪切(clip())之后使用restore()方法恢复之前保存的状态。
如果不使用save()和restore(),即便使用clearRect()方法清空画布,后面绘制的所有图形或图片也都会限制在这个剪切区域内。
//save()保存状态 cxt.save(); //使用clip()方法指定一个圆形的剪切区域 cxt.beginPath(); cxt.arc(70, 70, 50, 0, 360 * Math.PI / 180, true); cxt.closePath(); cxt.stroke(); cxt.clip(); //绘制一张图片 var image = new Image(); image.src = "images/princess.png"; image.onload = function () { cxt.drawImage(image, 10, 20); } $$("btn").onclick = function () { //restore()恢复状态 cxt.restore(); //清空画布 cxt.clearRect(0, 0, cnv.width, cnv.height); //绘制一张新图片 var image = new Image(); image.src = "images/Judy.png"; image.onload = function () { cxt.drawImage(image, 10, 20); } }
删除 cxt.save()和cxt.restore() 后效果为:
应用二:图片/图形的变换
cxt.fillStyle = "HotPink"; cxt.translate(30, 30); cxt.fillRect(0, 0, 100, 50); cxt.fillStyle = "LightSkyBlue "; cxt.translate(60, 60); cxt.fillRect(0, 0, 100, 50);
蓝色矩形预期坐标为 (60,60),最终效果却是 (90,90),因为之前 translate(30, 30),整个坐标系的原点已经发生了位移变化 !
解决方案是使用 save()和restore()来实现
cxt.save(); cxt.fillStyle = "HotPink"; cxt.translate(30, 30); cxt.fillRect(0, 0, 100, 50); cxt.restore(); cxt.fillStyle = "LightSkyBlue "; cxt.translate(60, 60); cxt.fillRect(0, 0, 100, 50);
在变换操作(平移、缩放、旋转)中,一般都是在操作之前使用save()方法保存当前状态,其中当前状态包括参考坐标、图形大小等。然后再使用restore()方法来恢复之前保存的状态。
Canvas对象
属性
- globalAlpha 透明度
默认值为1.0(完全不透明),取值范围为:0.0~1.0。其中0.0表示完全透明,1.0表示完全不透明。globalAlpha属性必须在图形绘制之前定义才有效。 - width 宽度
- height 高度
常用于文字水平居中对齐和清空画布
cxt.clearRect(0, 0, cnv.width, cnv.height);
方法
- getContext(“2d”) 获取Canvas 2D上下文环境对象
- toDataURL() 获取Canvas对象产生的位图的字符串,即将Canvas画布转换为图片的base64格式的字符串。
cnv.toDataURL(type);
type 可选,默认值为image/png类型
直接在Canvas画布上点击鼠标右键,在弹出的快捷菜单中也能“另存为”图片为本地图片,为什么还要那么麻烦地使用toDataURL()呢?
事实上,很多旧版本的浏览器并不具备这个功能。因此为了兼容性,建议使用toDataURL()方法进行处理。
canvas保存图片时,谷歌浏览器Chrome报错【解决方案】Not allowed to navigate top frame to data URL
https://blog.csdn.net/weixin_41192489/article/details/124386704
图形的重叠模式
默认情况下,后绘制的图形会覆盖之前绘制的图形。
通过 globalCompositeOperation 可以修改重叠模式
cxt.globalCompositeOperation = 属性值;
不同属性值,效果如下图:
属性值 |
说明 |
source-over |
默认值,新图形覆盖旧图形 |
copy |
只显示新图形,旧图形作透明处理 |
darker |
两种图形都显示,在重叠部分,颜色由两个图形的颜色值相减后形成 |
destination-atop |
只显示新图形与旧图形重叠部分以及新图形的其余部分,其他部分作透明处理 |
destination-in |
只显示旧图形中与新图形重叠部分,其他部分作透明处理 |
destination-out |
只显示旧图形中与新图形不重叠部分,其他部分作透明处理 |
destination-over |
与source-over属性相反,旧图形覆盖新图形 |
lighter |
两种图形都显示,在图形重叠部分,颜色由两个图形的颜色值相加后形成 |
source-atop |
只显示旧与新图形重叠部分及旧图形的其余部分,其他部分作透明处理 |
source-in |
只显示新图形中与旧图形重叠部分,其他部分作透明处理 |
source-out |
只显示新图形中与旧图形不重叠部分,其余部分作透明处理 |
xor |
两种图形都绘制,其中重叠部分透明处理 |
cxt.globalCompositeOperation = "xor"; //绘制矩形 cxt.fillStyle = "HotPink"; cxt.fillRect(30, 30, 60, 60); //绘制圆形 cxt.beginPath(); cxt.arc(100, 100, 40, 0, Math.PI * 2, true); cxt.closePath(); cxt.fillStyle = "LightSkyBlue"; cxt.fill();
cxt.globalCompositeOperation = "xor"; //绘制第1个矩形 cxt.fillStyle = "HotPink"; cxt.fillRect(30, 30, 60, 60); //绘制圆形 cxt.beginPath(); cxt.arc(100, 100, 40, 0, Math.PI * 2, true); cxt.closePath(); cxt.fillStyle = "LightSkyBlue"; cxt.fill(); //绘制第2个矩形 cxt.fillStyle = "HotPink"; cxt.fillRect(110, 30, 60, 60);