马黑黑 发表于 2024-4-16 10:38

在canvas画布中绘制三次贝塞尔曲线

<style>
.papa { font: normal 18px/24px sans-serif; }
.papa p { margin: 10px 0; }
.papa canvas { border: 1px solid gray; }
.mum { position: relative; margin: 0; padding: 10px; font: normal 16px/20px Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; color: black; background: rgba(240, 240, 240,.95); box-shadow: 2px 2px 4px gray; border: thick groove lightblue; border-radius: 6px; }
.mum ::selection { background-color: rgba(0,100,100,.35); }
.mum div { margin: 0; padding: 0; }
.mum cl-cd { display: block; position: relative; margin: 0 0 0 50px; padding: 0 0 0 10px; white-space: pre-wrap; overflow-wrap: break-word; border-left: 1px solid silver; }
.mum cl-cd::before { position: absolute; content: attr(data-idx); width: 50px; color: gray; text-align: right; transform: translate(-70px); }
.tRed { color: red; }
.tBlue { color: blue; }
.tGreen { color: green; }
.tDarkRed { color: darkred; }
.tMagenta { color: magenta; }
</style>

<div class="papa">
        <p>三次贝塞尔曲线和二次贝塞尔曲线本质上道理相通,前者仅是多一个控制点,因此本文的行文方式与介绍二次贝塞尔曲线的帖子是差不多的。</p>
        <p>三次贝塞尔曲线需要四个点的坐标数据:曲线起始点坐标(x1,y1)、曲线第一个控制点坐标(cpx1,cpy1)、曲线第二个控制点坐标(cpx2,cpy2)和曲线终点坐标(x2,y2)。在 canvas 画布中,起始点坐标默认使用上一次绘制图形的路径终点坐标,若不存在路径终点坐标则起始点坐标为(0,0),也可通过 moveTo(x,y) 指令重新设定。在canvas画布中,绘制三次贝塞尔曲线有专门的指令,<span class="tRed">bezierCurveTo(cpx1, cpy1, cpx2, cpy2, endX, endY)</span>, 其中,cpx1 和 cpy1、cpx2 和 cpy2 分别为曲线两个控制点坐标,endX 和 endY 为曲线终点坐标,曲线起点坐标取上一回路径终点坐标或通过 moveTo(x,y) 指令定义。假设画笔标识为 ctx,我们来绘制一条三次贝塞尔曲线:</p>
        <blockquote>ctx.moveTo(50, 100);<br>ctx.bezierCurveTo(40, 5, 120, 0, 250, 100);</blockquote>
        <p>这表示,曲线从(50,100)出发,到(250,100)停车,曲线的第一个控制点是(40, 5),第二个控制点是(120, 10),效果如下示例所示(小圆点用于标识四个点的位置):</p>
        <canvas id="canv"></canvas>
        <p>影响三次贝塞尔曲线的外观主要是控制点,上例中的两个小绿点就是两个控制点。控制点可以在曲线上,若此,绘制出来的将不是曲线而是一条直线;控制点可以为正负数、可以超出画布的范围,不同的数值将直接影响曲线的曲率和最终外观。</p>
        <p>早些时候,各大浏览器只有Chrome较早支持绘制椭圆指令,为了兼容,大佬们经常使用三次贝塞尔曲线来绘制椭圆。现在我们可以直接使用 ellipse() 方法来实现椭圆的绘制了,不过重现一下用三次贝塞尔曲线画椭圆,对学习canvas画布绘制三次贝塞尔曲线还是有好处的。请看下面的绘制效果:</p>
        <canvas id="canv1"></canvas>
        <p>代码和解释如下:</p>
        <div class='mum'>
<div class="tGreen"><cl-cd data-idx="1">/* 使用三次贝塞尔曲线绘制平躺的椭圆</cl-cd>
<cl-cd data-idx="2">   要点:</cl-cd>
<cl-cd data-idx="3">   ① 两个控制点中,cpx1、cpx2和曲线起点、终点相一致,cpy1、cpy2两值相同</cl-cd>
<cl-cd data-idx="4">   ② 绘制两条曲线,第一条凸点朝上,第二条凸点朝下,第一条cpy均为 0,第二条</cl-cd>
<cl-cd data-idx="5">      cpy均为画布的高度</cl-cd>
<cl-cd data-idx="6">   ③ 上述方式绘制的椭圆为闭合椭圆,可以使用 fill() 填充颜色</cl-cd>
<cl-cd data-idx="7">*/</cl-cd></div>
<cl-cd data-idx="8">ctx.moveTo(50, 75);</cl-cd>
<cl-cd data-idx="9">ctx.bezierCurveTo(50, 0, 250, 0, 250, 75);</cl-cd>
<cl-cd data-idx="10">ctx.bezierCurveTo(250, 150, 50, 150, 50, 75);</cl-cd>
<cl-cd data-idx="11">ctx.stroke();</cl-cd>
</div>
        <p>【附】正统绘制椭圆效果和代码</p>
        <p>效果:</p>
        <canvas id="canv2"></canvas>
        <p>代码和解释:</p>
        <div class='mum'>
<div class="tGreen"><cl-cd data-idx="1">/* 使用 ellipse() 方法绘制椭圆</cl-cd>
<cl-cd data-idx="2">   语法 :ellipse(x, y, rx, ry, rotation, startAngle, endAngle, anticlockwise</cl-cd>
<cl-cd data-idx="3">   其中:</cl-cd>
<cl-cd data-idx="4">      ① x,y 分别为椭圆的圆心xy坐标</cl-cd>
<cl-cd data-idx="5">      ② rx,ry 分别为椭圆的长轴半径和短轴半径</cl-cd>
<cl-cd data-idx="6">      ③ rotation 为椭圆的旋转弧度(需将角度换算为弧度,下同)</cl-cd>
<cl-cd data-idx="7">      ④ startAngle,endAngle 分别为椭圆的起始和终止弧度</cl-cd>
<cl-cd data-idx="8">      ⑤ anticlockwise 为是否以逆时针方向绘制(true/false)</cl-cd>
<cl-cd data-idx="9">*/</cl-cd></div>
<cl-cd data-idx="10">ctx.ellipse(150, 75, 100, 56, 0, 0, 2 * <span class="tRed">Math</span>.PI, true);</cl-cd>
<cl-cd data-idx="11">ctx.stroke();</cl-cd>
</div>

</div>

<script>
let ctx = canv.getContext('2d'), ctx1 = canv1.getContext('2d'), ctx2 = canv2.getContext('2d');

let drawCurveLine = (x1,y1,cpx1,cpy1,cpx2,cpy2,x2,y2) => {
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(x1,y1);
        ctx.bezierCurveTo(cpx1,cpy1,cpx2,cpy2,x2,y2);
        ctx.stroke();
        ctx.closePath();
        ctx.restore();
}

let drawCircle = (x,y,r,color) => {
        ctx.save();
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.arc(x,y,r,0,2*Math.PI);
        ctx.fill();
        ctx.restore();
};

let drawEllipse = () => {
        ctx1.moveTo(50, 75);
        ctx1.bezierCurveTo(50, 0, 250, 0, 250, 75);
        ctx1.bezierCurveTo(250, 150, 50, 150, 50, 75);
        ctx1.stroke();
};

let drawEllipse1 = () => {
        ctx2.ellipse(150, 75, 100, 56, 0, 0, 2 * Math.PI, true);
        ctx2.stroke();
};

drawCurveLine(50, 100, 40, -10, 120, 10, 250, 100);
drawCircle(50, 100, 3, 'red');
drawCircle(40, 5, 3, 'green');
drawCircle(120, 10, 3, 'green');
drawCircle(250, 100, 3, 'red');
drawEllipse();
drawEllipse1();
</script>

红影 发表于 2024-4-16 16:08

这个椭圆是两个圆弧,上边的从起点顺时针到终点,然后下边的从终点顺时针到起点。

红影 发表于 2024-4-16 16:16

黑黑还用和ellipse()对照的方法来讲解,真好。{:4_199:}
从这个例子里看下来,还是用canvas画布的三次贝塞尔曲线画椭圆更简单了,四个点贴边就画出来了,而使用ellipse(),我甚至不知道这种情况下对应的短轴半径是56{:4_173:}

马黑黑 发表于 2024-4-16 16:58

红影 发表于 2024-4-16 16:16
黑黑还用和ellipse()对照的方法来讲解,真好。
从这个例子里看下来,还是用canvas画布的三次贝塞 ...

椭圆有圆心,长轴半径指圆心到椭圆圆周最远点的距离、短轴半径则是到椭圆圆周最近点的距离,是多少根据设计来,我这是个大概数,目测的距离

马黑黑 发表于 2024-4-16 17:00

红影 发表于 2024-4-16 16:08
这个椭圆是两个圆弧,上边的从起点顺时针到终点,然后下边的从终点顺时针到起点。

在画布里,或其他作图软件里,所有的圆、椭圆,都是特殊的圆弧,换言之,是用绘制圆弧的方式绘制出来的。

南无月 发表于 2024-4-16 20:52

{:4_173:}
看完了,大概能看懂。但要让自己整,那是两种方法一种也不会。。
还是等现成的。

马黑黑 发表于 2024-4-16 21:34

南无月 发表于 2024-4-16 20:52
看完了,大概能看懂。但要让自己整,那是两种方法一种也不会。。
还是等现成的。

这是基本功

红影 发表于 2024-4-16 22:00

马黑黑 发表于 2024-4-16 16:58
椭圆有圆心,长轴半径指圆心到椭圆圆周最远点的距离、短轴半径则是到椭圆圆周最近点的距离,是多少根据设 ...

哦哦,还以为是算出来的值呢{:4_173:}

红影 发表于 2024-4-16 22:02

马黑黑 发表于 2024-4-16 17:00
在画布里,或其他作图软件里,所有的圆、椭圆,都是特殊的圆弧,换言之,是用绘制圆弧的方式绘制出来的。

黑黑的这个总结性的说法特别好{:4_187:}

马黑黑 发表于 2024-4-16 23:25

红影 发表于 2024-4-16 22:02
黑黑的这个总结性的说法特别好

这是官网总结的,不是我的

马黑黑 发表于 2024-4-16 23:26

红影 发表于 2024-4-16 22:00
哦哦,还以为是算出来的值呢

也可以算算

南无月 发表于 2024-4-17 17:48

马黑黑 发表于 2024-4-16 21:34
这是基本功

{:4_170:}
基本看功。。
没有写功和创作功

南无月 发表于 2024-4-17 17:49

昨天还想这个三次呗能整出啥来。今天就看到了。效果真是惊艳。。。

马黑黑 发表于 2024-4-17 19:16

南无月 发表于 2024-4-17 17:49
昨天还想这个三次呗能整出啥来。今天就看到了。效果真是惊艳。。。

用途很多的,只要有创作力

马黑黑 发表于 2024-4-17 19:16

南无月 发表于 2024-4-17 17:48
基本看功。。
没有写功和创作功

都是功

红影 发表于 2024-4-17 19:28

马黑黑 发表于 2024-4-16 23:25
这是官网总结的,不是我的

不管怎么说,也是从你这里知道的啊。{:4_187:}

南无月 发表于 2024-4-17 19:32

马黑黑 发表于 2024-4-17 19:16
用途很多的,只要有创作力

天哪,我是想象力有限门的。。{:4_170:}
主要是老师有构思,有创意,还有实现手法。。完美。。

南无月 发表于 2024-4-17 19:34

马黑黑 发表于 2024-4-17 19:16
都是功

我这个功太差了。这个画布300*150,对我来说,整清楚这个数据有助于理解几个控制点。{:4_170:}

红影 发表于 2024-4-17 19:56

马黑黑 发表于 2024-4-16 23:26
也可以算算

不知道它的标准公式是什么,算不出来啊{:4_173:}

马黑黑 发表于 2024-4-17 20:37

红影 发表于 2024-4-17 19:56
不知道它的标准公式是什么,算不出来啊

那就用卷尺量量
页: [1] 2 3 4 5 6
查看完整版本: 在canvas画布中绘制三次贝塞尔曲线