马黑黑 发表于 2024-4-14 14:23

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

本帖最后由 马黑黑 于 2024-4-14 14:28 编辑 <br /><br /><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>二次贝塞尔曲线需要三个点的坐标数据:曲线起始点坐标(x1,y1)、曲线控制点坐标(cpx,cpy)和曲线终点坐标(x2,y2)。在 canvas 画布中,起始点坐标默认使用上一次绘制图形的路径终点坐标,没有路径终点坐标时起始点坐标为(0,0),也可以使用 moveTo(x,y) 指令重新定义。canvas 绘制二次贝塞尔曲线有专门的指令,<span class="tRed">quadraticCurseTo(cpx, cpy, endX, endY)</span>, 其中,cpx 和 cpy 为曲线控制点坐标,endX 和 endY 为曲线终点坐标,曲线起点坐标取上一回路径终点坐标或通过 moveTo(x,y) 指令定义。假设画笔标识为 ctx,我们来绘制一条二次贝塞尔曲线:</p>
        <blockquote>ctx.moveTo(50, 100);<br>ctx.quadraticCurveTo(120, 0, 250, 100);</blockquote>
        <p>这表示,曲线从(50,100)出发,到(250,100)停车,曲线的控制点是(120, 10),效果如下示例所示(小圆点用于标识三个点的位置):</p>
        <canvas id="canv"></canvas>
        <p>影响二次贝塞尔曲线的外观主要是控制点,上例中的小绿点就是控制点。控制点可以在曲线上,若此,绘制出来的将不是曲线而是一条直线;控制点可以为正负数、可以超出画布的范围,不同的数值将直接影响曲线的曲率和最终外观。</p>
        <p>单纯使用二次贝塞尔曲线,我们就可以绘制出奇妙的动态图案。以下代码,我们仅来回改变控制点(cpx,cpy)中的cpy值,作出的动态图案十分令人惊艳,它还有无限可能:</p>
        <div class='mum'>
<cl-cd data-idx="1">&lt;<span class="tDarkRed">canvas</span> <span class="tRed">id</span>=<span class="tMagenta">"canv"</span> width=<span class="tMagenta">"300"</span> height=<span class="tMagenta">"200"</span>&gt;&lt;<span class="tDarkRed">/canvas</span>&gt;</cl-cd>
<cl-cd data-idx="2">&nbsp;</cl-cd>
<cl-cd data-idx="3">&lt;<span class="tDarkRed">script</span>&gt;</cl-cd>
<cl-cd data-idx="4"><span class="tBlue">let</span> ctx = canv.getContext(<span class="tMagenta">'2d'</span>);</cl-cd>
<cl-cd data-idx="5"><span class="tBlue">let</span> ww = canv.width, hh = canv.height;</cl-cd>
<div class="tGreen"><cl-cd data-idx="6">/* 全局变量</cl-cd>
<cl-cd data-idx="7">   ctrY :控制点Y坐标</cl-cd>
<cl-cd data-idx="8">   step :控制点Y坐标变化幅度</cl-cd>
<cl-cd data-idx="9">   total :曲线总数</cl-cd>
<cl-cd data-idx="10">   raf :关键帧动画标识</cl-cd>
<cl-cd data-idx="11">*/</cl-cd></div>
<cl-cd data-idx="12"><span class="tBlue">let</span> ctrY = hh / 2, step = 0.25, total = 20, raf = null;</cl-cd>
<cl-cd data-idx="13">&nbsp;</cl-cd>
<cl-cd data-idx="14"><span class="tGreen">//函数 :生成随机rgb颜色</span></cl-cd>
<cl-cd data-idx="15"><span class="tBlue">let</span> mkRgba = (opacity=0.5) =&gt; {</cl-cd>
<cl-cd data-idx="16">      <span class="tBlue">let</span> ar = .map((item) =&gt; <span class="tRed">Math</span>.floor(<span class="tRed">Math</span>.random() * item));</cl-cd>
<cl-cd data-idx="17">      ar.push( opacity || (.5 + <span class="tRed">Math</span>.random() * .5).toFixed(1));</cl-cd>
<cl-cd data-idx="18">      <span class="tBlue">return</span> <span class="tMagenta">'rgba('</span> + ar.join(<span class="tMagenta">','</span>) + <span class="tMagenta">')'</span>;</cl-cd>
<cl-cd data-idx="19">}</cl-cd>
<cl-cd data-idx="20">&nbsp;</cl-cd>
<cl-cd data-idx="21"><span class="tGreen">//创建一个曲线类</span></cl-cd>
<cl-cd data-idx="22"><span class="tBlue">class </span>curveLine {</cl-cd>
<cl-cd data-idx="23">&nbsp; &nbsp; <span class="tGreen">//类构造</span></cl-cd>
<cl-cd data-idx="24">&nbsp; &nbsp; constructor(x1, y1, cpx, cpy, x2, y2) {</cl-cd>
<cl-cd data-idx="25">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.x1 = x1;</cl-cd>
<cl-cd data-idx="26">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.y1 = y1;</cl-cd>
<cl-cd data-idx="27">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.cpx = cpx;</cl-cd>
<cl-cd data-idx="28">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.cpy = cpy;</cl-cd>
<cl-cd data-idx="29">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.x2 = x2;</cl-cd>
<cl-cd data-idx="30">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.y2 = y2;</cl-cd>
<cl-cd data-idx="31">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.color = <span class="tMagenta">'green'</span>;</cl-cd>
<cl-cd data-idx="32">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">this</span>.lineWidth = 4;</cl-cd>
<cl-cd data-idx="33">&nbsp; &nbsp; };</cl-cd>
<cl-cd data-idx="34">&nbsp; &nbsp; <span class="tGreen">//类的绘制行为</span></cl-cd>
<cl-cd data-idx="35">&nbsp; &nbsp; draw(ctx) {</cl-cd>
<cl-cd data-idx="36">&nbsp; &nbsp; &nbsp; &nbsp; ctx.save();</cl-cd>
<cl-cd data-idx="37">&nbsp; &nbsp; &nbsp; &nbsp; ctx.strokeStyle = <span class="tBlue">this</span>.color;</cl-cd>
<cl-cd data-idx="38">&nbsp; &nbsp; &nbsp; &nbsp; ctx.lineCap = <span class="tMagenta">'round'</span>;</cl-cd>
<cl-cd data-idx="39">&nbsp; &nbsp; &nbsp; &nbsp; ctx.lineWidth = <span class="tBlue">this</span>.lineWidth;</cl-cd>
<cl-cd data-idx="40">&nbsp; &nbsp; &nbsp; &nbsp; ctx.beginPath();</cl-cd>
<cl-cd data-idx="41">&nbsp; &nbsp; &nbsp; &nbsp; ctx.moveTo(<span class="tBlue">this</span>.x1, <span class="tBlue">this</span>.y1);</cl-cd>
<cl-cd data-idx="42">&nbsp; &nbsp; &nbsp; &nbsp; ctx.quadraticCurveTo(<span class="tBlue">this</span>.cpx, <span class="tBlue">this</span>.cpy, <span class="tBlue">this</span>.x2, <span class="tBlue">this</span>.y2);</cl-cd>
<cl-cd data-idx="43">&nbsp; &nbsp; &nbsp; &nbsp; ctx.stroke();</cl-cd>
<cl-cd data-idx="44">&nbsp; &nbsp; &nbsp; &nbsp; ctx.closePath();</cl-cd>
<cl-cd data-idx="45">&nbsp; &nbsp; &nbsp; &nbsp; ctx.restore();</cl-cd>
<cl-cd data-idx="46">&nbsp; &nbsp; };</cl-cd>
<cl-cd data-idx="47">};</cl-cd>
<cl-cd data-idx="48">&nbsp;</cl-cd>
<cl-cd data-idx="49"><span class="tGreen">//批量绘制曲线</span></cl-cd>
<cl-cd data-idx="50"><span class="tBlue">let</span> draw = () =&gt; {</cl-cd>
<cl-cd data-idx="51">&nbsp; &nbsp; ctx.clearRect(0, 0, ww, hh);</cl-cd>
<cl-cd data-idx="52">&nbsp; &nbsp; <span class="tBlue">let</span> add = ww - 20;</cl-cd>
<cl-cd data-idx="53">&nbsp; &nbsp; <span class="tBlue">for</span>(<span class="tBlue">let</span> i = 0; i &lt; total; i ++) {</cl-cd>
<cl-cd data-idx="54">&nbsp; &nbsp; &nbsp; &nbsp; <span class="tBlue">let</span> cl = <span class="tBlue">new</span> curveLine(); <span class="tGreen">//创建曲线类实例</span></cl-cd>
<cl-cd data-idx="55">&nbsp; &nbsp; &nbsp; &nbsp; cl.x1 = ww / 2;</cl-cd>
<cl-cd data-idx="56">&nbsp; &nbsp; &nbsp; &nbsp; cl.y1 = hh - 5;</cl-cd>
<cl-cd data-idx="57">&nbsp; &nbsp; &nbsp; &nbsp; cl.cpx = (ww + add) / total * i - add / 2 + 5;</cl-cd>
<cl-cd data-idx="58">&nbsp; &nbsp; &nbsp; &nbsp; cl.cpy = ctrY;</cl-cd>
<cl-cd data-idx="59">&nbsp; &nbsp; &nbsp; &nbsp; cl.x2 = ww / 2;</cl-cd>
<cl-cd data-idx="60">&nbsp; &nbsp; &nbsp; &nbsp; cl.y2 = 5;</cl-cd>
<cl-cd data-idx="61">&nbsp; &nbsp; &nbsp; &nbsp; cl.color = mkRgba(0.7);</cl-cd>
<cl-cd data-idx="62">&nbsp; &nbsp; &nbsp; &nbsp; cl.lineWidth = 4;</cl-cd>
<cl-cd data-idx="63">&nbsp; &nbsp; &nbsp; &nbsp; cl.draw(ctx);&nbsp; &nbsp; </cl-cd>
<cl-cd data-idx="64">&nbsp; &nbsp; }</cl-cd>
<cl-cd data-idx="65">};</cl-cd>
<cl-cd data-idx="66">&nbsp;</cl-cd>
<cl-cd data-idx="67"><span class="tGreen">//渲染效果</span></cl-cd>
<cl-cd data-idx="68"><span class="tBlue">let</span> render = () =&gt; {</cl-cd>
<cl-cd data-idx="69">&nbsp; &nbsp; ctrY += step;</cl-cd>
<cl-cd data-idx="70">&nbsp; &nbsp; <span class="tBlue">if</span>(ctrY &gt; hh || ctrY &lt; 0) step = -step;</cl-cd>
<cl-cd data-idx="71">&nbsp; &nbsp; draw();</cl-cd>
<cl-cd data-idx="72">&nbsp; &nbsp; raf = requestAnimationFrame(render); <span class="tGreen">//调用关键帧动画接口</span></cl-cd>
<cl-cd data-idx="73">};</cl-cd>
<cl-cd data-idx="74">&nbsp;</cl-cd>
<cl-cd data-idx="75">render();</cl-cd>
<cl-cd data-idx="76">&nbsp;</cl-cd>
<cl-cd data-idx="77"><span class="tGreen">//画布单击事件 :暂停或启用动画</span></cl-cd>
<cl-cd data-idx="78">canv.onclick = () =&gt; {</cl-cd>
<cl-cd data-idx="79">&nbsp; &nbsp; raf = raf ? cancelAnimationFrame(raf) : requestAnimationFrame(render);</cl-cd>
<cl-cd data-idx="80">};</cl-cd>
<cl-cd data-idx="81">&lt;<span class="tDarkRed">/script</span>&gt;</cl-cd>
        </div>
        <p>以上代码,可以保存为本地 .html 文档或将代码拿到 <a href="http://mhh.52qingyin.cn/api/pcode/" target="_blank">pencil code</a> 运行以查看效果,提醒一下,动画是可以暂停和继续运行的,方法是单击画布。</p>
</div>

<script>
let ctx = canv.getContext('2d');

let drawCurveLine = (x1,y1,cpx,cpy,x2,y2) => {
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(x1,y1);
        ctx.quadraticCurveTo(cpx,cpy,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();
};

drawCurveLine(50, 100, 120, 10, 250, 100);
drawCircle(50, 100, 3, 'red');
drawCircle(120, 10, 3, 'green');
drawCircle(250, 100, 3, 'red');

</script>

小辣椒 发表于 2024-4-14 15:34

黑黑辛苦,教程讲的很仔细{:4_199:}

小辣椒 发表于 2024-4-14 15:34

预览了效果好像灯笼一样的在旋转。。。。

红影 发表于 2024-4-14 15:57

绘制了这么多二次贝塞尔曲线啊,组合在一起而且改变cpx的值,就能变成动画呢。
看着像大蒜头{:4_173:}

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

cl.color = mkRgba(0.7);
这个不太懂呢。

马黑黑 发表于 2024-4-14 17:49

红影 发表于 2024-4-14 16:02
cl.color = mkRgba(0.7);
这个不太懂呢。

mkRgba(opacity) 是调用前面写的函数,函数是用来生成随机rgba颜色的,唯一参数是透明度参数,可选。

马黑黑 发表于 2024-4-14 17:50

小辣椒 发表于 2024-4-14 15:34
黑黑辛苦,教程讲的很仔细

{:4_190:}

马黑黑 发表于 2024-4-14 17:50

红影 发表于 2024-4-14 15:57
绘制了这么多二次贝塞尔曲线啊,组合在一起而且改变cpx的值,就能变成动画呢。
看着像大蒜头

{:4_196:}

马黑黑 发表于 2024-4-14 17:50

小辣椒 发表于 2024-4-14 15:34
预览了效果好像灯笼一样的在旋转。。。。

{:4_173:}

南无月 发表于 2024-4-14 18:18

以下代码,我们仅来回改变控制点(cpx,cpy)中的cpy值,作出的动态图案十分令人惊艳,它还有无限可能

画布能画出各种漂亮的图案。。真让人惊叹。。

马黑黑 发表于 2024-4-14 19:51

南无月 发表于 2024-4-14 18:18
以下代码,我们仅来回改变控制点(cpx,cpy)中的cpy值,作出的动态图案十分令人惊艳,它还有无限可能

画 ...

{:4_191:}

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

马黑黑 发表于 2024-4-14 17:49
mkRgba(opacity) 是调用前面写的函数,函数是用来生成随机rgba颜色的,唯一参数是透明度参数,可选。

原来是调用的,还以为咋一句就能控制颜色了呢{:4_173:}
谢谢黑黑的解答{:4_187:}

红影 发表于 2024-4-14 19:57

马黑黑 发表于 2024-4-14 17:50


不好意思,我这个比喻不太美{:4_173:}

马黑黑 发表于 2024-4-14 20:43

红影 发表于 2024-4-14 19:57
不好意思,我这个比喻不太美

很美很美

马黑黑 发表于 2024-4-14 20:43

红影 发表于 2024-4-14 19:56
原来是调用的,还以为咋一句就能控制颜色了呢
谢谢黑黑的解答

看代码要看前前后后

红影 发表于 2024-4-14 21:44

马黑黑 发表于 2024-4-14 20:43
很美很美

大蒜头也美啊{:4_170:}

红影 发表于 2024-4-14 21:45

马黑黑 发表于 2024-4-14 20:43
看代码要看前前后后

是的,通过这个记住教训了,不能只看一句呢{:4_173:}

马黑黑 发表于 2024-4-14 21:49

红影 发表于 2024-4-14 21:45
是的,通过这个记住教训了,不能只看一句呢

其实也简单:你看不懂,选择 mkRgba ,就按一下 Ctrl + F,你就会看到了

马黑黑 发表于 2024-4-14 21:50

红影 发表于 2024-4-14 21:44
大蒜头也美啊

你不说美,山东人跟你急

南无月 发表于 2024-4-14 21:55

马黑黑 发表于 2024-4-14 19:51


晚上喝啤的
页: [1] 2 3 4
查看完整版本: 在canvas画布中绘制二次贝塞尔曲线