要介绍路径绘图相关的api之前,必须要先理解什麽叫做『路径』。
有学过电脑绘图软件,例如Adobe Photoshop, Adobe Illustrator的同学可能对『路径』这个词相当的熟悉,同时也可能可以更快速掌握2D渲染环境路径绘图的概念,但是考量到大多数人都没有美术学经历背景,所以这边还是简单做点说明~
路径是使用绘图工具建立的任意形状的曲线,用它可勾勒出物体的轮廓,所以也称之为轮廓线。 为了满足绘图的需要,路径又分为开放路径和封闭路径。 --维基百科
如果要白话一点的解释『路径(Path)』这个概念,可以想像成他是由一条透明的曲线所圈出来的一块(非)开放区域,在canvas中我们可以利用(接下来会提到的)上色填充相关api为已经成形的路径设定填充色(fill)/边框色(stroke)。
有学过SVG相关知识的同学应该马上就会发现这其实就跟SVG的Path 属性是一样的东西~没错,路径(Path)其实是计算机绘图领域的概念,并不是Canvas独有的。
(图片说明:在上图我们可以看到我们必须要先有一个叶子形状的Path,然後接着才可以对这个Path施加Fill和Stroke)
接下来我们要藉由实作的方式来加速学习api的使用方式,藉由实际操作API来画一条线/一个圆/一个不规则形状来加深对API的认识。
在开始之前,有一个特别需要注意的地方,那就是『绘制路径』这个行为过程其实有点类似於用笔尖在纸面上作画。
这个『笔尖』会是一个实际存在的座标(但是你看不到),打个比方:假设我们现在画了一段由A点画向B点的路径,那麽『笔尖』最後也会停在B上面。
这时候要注意,如果没有利用API去移动笔尖,而是直接在别的地方画了一个新的形状,那麽先被画出来的形状和後被画出来的形状就会产生多余的连线。
很遗憾的, IT邦似乎没有提供embed codepen iframe 的功能,所以我只能把源码写在这里了
function draw(){
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
ctx.beginPath(); //宣告开始绘制路径
// 把笔尖的座标移动到50,50
ctx.moveTo(50,50);
// 从当前笔尖座标开始画Path,一路画到200,50
ctx.lineTo(200,50);
// 设定边框颜色
ctx.strokeStyle="#fff";
// 赋予框线
ctx.stroke();
ctx.closePath(); //宣告结束绘制路径, 这时『路径』就不复存在,只会留下stroke 所带来的颜色
}
(()=>{
draw();
})()
codepen连结:
https://codepen.io/mizok_contest/pen/XWgeKoJ
任何的路径在开始画之前,最好都要先使用ctx.beginPath()来宣告『嘿,我要开始画路径罗』;
然後在结束路径绘制时,也最好使用ctx.closePath() 来宣告结束路径的绘制(如果有手动把路径连回原点,或用别的方法把路径闭合,那也可以不用closePath);
一般来说如果不宣告结束,那麽路径就会一直存续下去,这样就没有办法画出个别独立的图形(例如个别独立颜色不同的方块)
另外一提,ctx.fill() (填充颜色的api) 本身自带closePath的效果,所以如果先执行了fill(),则可以不用额外宣告结束路径绘制。
// API 用法
void ctx.arc(x, y, radius, startAngle, endAngle [, counterclockwise]);
// x: 圆心X座标
// y: 圆心Y座标
// radius : 半径
// startAngle: 起始角度=> 记得是顺时针为正喔(而且必须要是径度量)
// endEngle: 结束角度=> 记得是顺时针为正喔(而且必须要是径度量)
// counterClockwise : 是否以逆时针方向作画
这边我提出了一个错误的范例,和一个正确的范例,让大家可以参考一下错误的原因和正确的写法。
function drawCircle1(){
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
ctx.beginPath();
// 把笔尖的座标移动到50,50
ctx.moveTo(50,50);
// 从当前笔尖座标为圆心画一个半径50的圆形Path
ctx.arc(50,50,50,0,Math.PI*2,false)
// 设定边框颜色
ctx.strokeStyle="#fff";
// 赋予框线
ctx.stroke();
ctx.closePath();
// 这边会发现圆形跟预期的不太一样,多了一条线,那就是因为我们提到的笔尖没有移动而产生的问题
}
function drawCircle2(){
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
ctx.beginPath();
// 这边我们把笔尖改成移动到实际画圆的起始座标
// 把笔尖的座标移动到250,200
ctx.moveTo(250,200);
// 从当前笔尖座标为圆心画一个半径50的圆形Path
ctx.arc(200,200,50,0,Math.PI*2,false)
// 设定边框颜色
ctx.strokeStyle="#fff";
// 赋予框线
ctx.stroke();
ctx.closePath();
// 这边会发现这样就正常了
}
(()=>{
drawCircle1();
drawCircle2();
})()
codepen连结:
https://codepen.io/mizok_contest/pen/RwgLGPQ
这边我们利用画二次曲线的API来画一个由三条曲线构成的形状,接着填充并且赋予框线。
这边可以稍微理解一下Canvas 的API ~ ctx.curveTo是怎麽定义二次曲线的参数需求。
简单来说这个api把一段二次曲线看作是一个由三个点所构成的曲线,三个点分别是:
void ctx.quadraticCurveTo(cpx, cpy, x, y);
// cpx: 曲线控制点X座标
// cpy: 曲线控制点Y座标
// x: 曲线结束点X座标
// y: 曲线结束点Y座标
function drawBlob(){
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
ctx.beginPath();
// 把笔尖的座标移动到200,200
ctx.moveTo(200,200);
// 从当前笔尖座标画一条二次曲线到250,250(可以想像成抛物线),这时画完後笔尖座标会移动到250,250
ctx.quadraticCurveTo(500,300,250,250);
// 从当前笔尖座标再画一条二次曲线到100,100(可以想像成抛物线),这时画完後笔尖座标会移动到100,100
ctx.quadraticCurveTo(100,100,30,200);
// 最後再连回起始点200,200
ctx.quadraticCurveTo(30,30,200,200);
// 设定边框颜色
ctx.strokeStyle="#fff";
// 赋予框线
ctx.stroke()
// 设定边框颜色
ctx.fillStyle="red";
// 赋予框线
ctx.fill(); // 事实上fill会自带closePath的效果
ctx.closePath(); // 也就是说这一行可以不写也没差
}
(()=>{
drawBlob();
})()
codepen连结:
https://codepen.io/mizok_contest/pen/ExXwNZL
我们在前面的三个范例都有去调整过渲染环境当前的fillStyle和 strokeStyle 来改变填充色和边框的颜色。
(有电脑绘图经验的同学可能很快的就注意到了--这两个东西其实就是Illustrator的前景色和背景色吧!)
要知道,Canvas的Property在同一时间底下是只有一个唯一值的,也就是说填充色在同一瞬间只能被指定一个hex作为类似全域变数的概念,
所以如果今天有一个需求,要先画出一条红色的线,接着再画出一条蓝色的线,流程便会是:
虽然这样的场景很单纯看起来没什麽,但是如果今天到了很复杂的状况,例如初始颜色是透过random函数随机决定的,而绘制过程中突然有需求回归原本random到的那个颜色,那就会需要有能够复原Property的需求。
上述的场景虽然可以透过把字串值存取道临时变数来达成,但是别忘了,Canvas 的Property 远远不止strokeStyle 一个,如果任何一个Property都要存一个变数,想必程序码会变得很乱。
这时我们就可以透过Canvas 的原生API,也就是ctx.save()与 ctx.restore() (存档与读档) 来达成上述需求。
这边我提出了一个范例,范例中我先random 了一个hex色码来作为初始颜色,画一条线,接着把颜色改为蓝色再画一条线,最後我则是回归原本ramdom到的颜色画第三条线。
function randomColor(){
const color = '#' + Math.floor(Math.random()*16777215).toString(16);
return color;
}
function drawLines(){
let cvs = document.querySelector('canvas');
let ctx = cvs.getContext('2d');
// 把笔尖的座标移动到200,200
ctx.beginPath();
ctx.moveTo(200,200);
// 拉线到300,200
ctx.lineTo(300,200)
ctx.closePath();
//画一条随机颜色的线
// 设定边框颜色
ctx.strokeStyle=randomColor();
ctx.save(); //存档
// 赋予框线
ctx.stroke()
// 把笔尖的座标移动到200,220
ctx.beginPath();
ctx.moveTo(200,220);
// 拉线到300,220
ctx.lineTo(300,220)
ctx.closePath();
// 设定边框颜色
ctx.strokeStyle="blue";
// 赋予框线
ctx.stroke()
// 把笔尖的座标移动到200,240
ctx.beginPath();
ctx.moveTo(200,240);
// 拉线到300,220
ctx.lineTo(300,240)
ctx.closePath();
// 设定边框颜色
ctx.restore(); //读档
// 赋予框线
ctx.stroke()
}
(()=>{
drawLines();
})()
codepen连结:
https://codepen.io/mizok_contest/pen/PojJWaE
实际上,canvas 关於绘制路径的api还远不止上述提到的这几种。
例如曲线还有 ctx.bezierCurveTo()(贝兹曲线), 设定边框粗细可以用 ctx.lineWidth, 设定端点类型可以用ctx.lineJoin...,etc
这些api/property 如果要在文章中一一介绍其实多少会变得有点流水帐,所以我比较倾向让大家自己去搜寻自己需要的api
推荐在查询api 的时候可以多使用MDN~ MDN 会是学习Canvas基础的好帮手。
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
>>: Day06 WebRTC 中的 Signaling Server
【从零开始的Swift开发心路历程-Day30】认识GCD多执行绪Part3(完) 昨天我们用程序码...
前言 昨天我们使用预训练模型EfficientNet去提取一张表情的高阶特徵图(1280张特徵图),...
闲聊一下: 这个系统是我学习PHP的成果(因为专题要做这个,我负责PHP相关的部分,其他人负责架SE...
接续上一篇 我们再把 keras.engine.base_layer_v1 加入到 hiddenim...
每一个动作都是函数 语法 A::install.packages("aa") ...