canvas绘制曲线,canvas画曲线图
背景概要
相信大家在学习canvas或者在项目开发中使用canvas的时候应该都遇到过这样的需求:实现一个可以写的画板小工具。
好吧,我相信这只是canvas 熟悉的童鞋几十行代码的问题。下面的演示是一个简单的例子:
!DOCTYPE html html head title sketchbad demo/title style type= text/CSS canvas { border:1px蓝色实心;}/style/head body canvas id= canvas width= 800 height= 500 /canvas script type= text/JavaScript let is down=false;设beginPoint=nullconst canvas=document . query selector( # canvas );const CTX=canvas . get context( 2d );//设置线条颜色ctx.strokeStyle= redCTX . line width=1;ctx.lineJoin= roundctx.lineCap= roundcanvas . addevent listener( mousedown ,down,false);canvas . addevent listener( mousemove ,move,false);canvas . addevent listener( mouseup ,up,false);canvas . addevent listener( mouseout ,up,false);函数关闭(evt){ is down=true;begin point=get pos(evt);}函数移动(evt) { if(!isDown)返回;const endPoint=get pos(evt);画线(起点,终点);beginPoint=端点;}函数up(evt) { if(!isDown)返回;const endPoint=get pos(evt);画线(起点,终点);beginPoint=nullisDown=false} function get pos(evt){ return { x:evt . clientx,y:evt . clienty } } function drawLine(begin point,endPoint){ CTX . begin path();ctx.moveTo(beginPoint.x,begin point . y);ctx.lineTo(endPoint.x,endpoint . y);CTX . stroke();CTX . close path();} /script/body/html其实现逻辑也很简单:
我们主要监视canvas上的三个事件:mousedown、mouseup和mousemove,我们还创建了一个isDown变量。当用户按下鼠标(即启动笔)时,它将isDown设置为true,而当用户放下鼠标(mouseup)时,它将它设置为false。这样做的好处是可以判断用户当前是否处于绘画状态。mousemove事件连续收集鼠标经过的坐标点,当且仅当isDown为真(即处于书写状态)时,canvas的lineTo方法将当前点与前一点连接并绘制;通过以上步骤,我们可以实现基本的画板功能。然而,事情并没有那么简单。细心的童鞋可能会发现一个严重的问题。3354这样画出来的线条参差不齐,不够流畅,而且越画越快,断线感越强。如下图所示:
为什么会这样呢?
问题分析
造成这种现象的主要原因是:
我们用canvas的lineTo方法来连接点,连接相邻两点的是直线,不是曲线,所以这样画出来的是折线;
受限于浏览器收集mousemove事件的频率,我们都知道在mousemove中,浏览器是以很短的间隔收集当前鼠标的坐标,所以鼠标移动越快,收集到的相邻两点之间的距离越远,所以“断线感越明显”;
如何才能画出平滑的曲线?
其实有办法画出光滑的曲线。lineTo不靠谱。然后我们可以用canvas的另一个画图API,——二次曲线否决,用来画二次贝塞尔曲线。
二次贝塞尔曲线
quadraticCurveTo(cp1x,cp1y,x,y)
调用quadraticCurveTo方法需要四个参数。cp1x和cp1y描述控制点,而X和Y是曲线的端点:
更多详情可在MDN中找到。
既然要用贝塞尔曲线,显然我们的数据不够。要完整描述一条二次贝塞尔曲线,我们需要:起点,控制点,终点。这些数据是怎么来的?
有一个聪明的算法可以帮助我们获得这些信息。
获取二次贝塞尔关键点的算法
这个算法不难理解。这里我直接给你举个例子:
假设我们在一幅画中收集了六个鼠标坐标,分别是A、B、C、D、E、F;取前面三点A、B、C,计算B、C的中点B1,用quadraticCurveTo绘制一条以A为起点,B为控制点,B1为终点的二次贝塞尔曲线段;
接下来计算C点和D点的中点C1,继续以B1为起点,C为控制点,C1为终点绘制曲线;
以此类推继续画,到最后一点F时,贝塞尔曲线将以D和E的中点D1为起点,E为控制点,F为终点结束。
好了,这是算法。然后我们将基于这个算法升级现有代码:
设isDown=false让点数=[];设beginPoint=nullconst canvas=document . query selector( # canvas );const CTX=canvas . get context( 2d );//设置线条颜色ctx.strokeStyle= redCTX . line width=1;ctx.lineJoin= roundctx.lineCap= roundcanvas . addevent listener( mousedown ,down,false);canvas . addevent listener( mousemove ,move,false);canvas . addevent listener( mouseup ,up,false);canvas . addevent listener( mouseout ,up,false);函数关闭(evt){ is down=true;const { x,y }=get pos(evt);points.push({x,y });beginPoint={x,y}}函数移动(evt) { if(!isDown)返回;const { x,y }=get pos(evt);points.push({x,y });if(points . length 3){ const lastwo points=points . slice(-2);const control point=lastwo points[0];const endPoint={ x:(lastwo points[0])。x lastTwoPoints[1]。x)/2,y: (lastTwoPoints[0]。y lastTwoPoints[1]。y)/2,} drawLine(起点,控制点,终点);beginPoint=端点;} }函数up(evt) { if(!isDown)返回;const { x,y }=get pos(evt);points.push({x,y });if(points . length 3){ const lastwo points=points . slice(-2);const control point=lastwo points[0];const endPoint=lastwo points[1];画线(起点、控制点、终点);} beginPoint=nullisDown=false点数=[];} function get pos(evt){ return { x:evt . clientx,y:evt . clienty } } function drawLine(begin point,controlPoint,endPoint){ CTX . begin path();ctx.moveTo(beginPoint.x,begin point . y);ctx.quadraticCurveTo(控制点. x,控制点. y,端点. x,端点. y);CTX . stroke();CTX . close path();}在原来的基础上,我们创建了一个变量points来保存鼠标在之前的mousemove事件中经过的点。根据这个算法,已知绘制二次贝塞尔曲线至少需要3个点,所以我们只在点大于3的时候才开始绘制。接下来的处理就跟算法一样,这里就不赘述了。
代码更新后,我们的曲线平滑多了,如下图所示:
本文到此结束。希望你在画布画板“画”得开心~下次见:)
感兴趣的童鞋可以戳这里关注我的博客,有什么新鲜有趣的博文都会第一时间分享到这里~
这就是本文的全部内容。希望对大家的学习和支持有帮助。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。