canvas图形编辑器

小说:如意娱乐注册ruyi988作者:陵丁乙更新时间:2018-10-23字数:48914

在一个月里面她根本没有消停,不断的进攻,她攻下了一座座的城池,直接将里面的满人杀得一干二净……

手机邀请98彩金金沙

道衍说完走出燕王行馆,朱能手持佩剑追出,外面漆黑一片,哪里还有道衍的影子,飞身跳回,“人不见了,王爷,要不要派人去追?”
“之前是怎么回事?”如果王小民能够听到这个声音,一定会觉得耳熟。因为对方就是刚才给燕儿打电话的人。

灵山大雄宝殿一改当初模样,被改建成了一座巍峨庄严又朴实无华的佛堂,匾额也从原来的“大雄宝殿”换做了“大悲殿”。

原文地址:http://jeffzhong.space/2017/11/02/drawboard/

使用canvas进行开发项目,我们离不开各种线段,曲线,图形,但每次都必须用代码一步一步去实现,显得非常麻烦。有没有一种类似于PS,CAD之类的可视化工具,绘制出基本的图形,然后输出代码。之后我们就可以在这个生成的图形场景的基础上去实现功能,那将是多么的美妙的事啊。话不多说,我们来实现一个图形编辑器吧????。

主要实现如下的功能:

  1. 直线(实线、虚线)
  2. 贝塞尔曲线(2次,3次)
  3. 多边形(三角形、矩形、任意边形)
  4. 多角星(3角星、4角星、5角星...)
  5. 圆形、椭圆

实际效果: drawboard(推荐在chrome或safari下运行)
drawboard

功能点包括:

  1. 所有的图形都可以拖拽位置,直线和曲线需要拖拽中点(黄色圆点),其他图形只需要把鼠标放于图形内部拖拽即可;
  2. 所有的图形只要把鼠标放于中心点或图形内部,然后按delete键即可删除;
  3. 线段可以实现拉伸减少长度,旋转角度;
  4. 贝塞尔曲线可以通过拖拽控制点实现任意形状的变化;
  5. 多边形可以拖拽控制点控制多边形的旋转角度和大小变化,所有顶点都可以拖拽;
  6. 多角星除了多边形的功能外,拖拽第二控制点可以实现图形的饱满程度;
  7. 是否填充图形,是否显示控制线,是否显示背景格;
  8. 生成代码。

使用方式:

  1. 选中工具栏中的图形选项,是否填充,颜色等,然后在画板拖动鼠标,同时选中的工具栏中的选项复位,此时为绘图模式;
  2. 完成绘制图形后,可以对图形进行拖拽位置,变换顶点,旋转等,此时为修改模式;
  3. 然后再选中工具栏选项,再次绘制,如此类推;
  4. 可以消除控制线和背景格,查看效果,然后可以点击生成代码,复制代码即可。

该项目用到的知识点包括:

  1. ES6面向对象
  2. html5标签,布局
  3. 基本的三角函数
  4. canvas部分有:坐标变换,渐变,混合模式,线条和图形的绘制。

工具栏

drawboard tools
首先我们实现如图所示的工具栏,也就是基本的html/css,使用了flex布局,同时使用了html5的color, range, number标签,其它都是普通的html和css代码。主要注意的地方就是如下用纯css实现选择效果

  .wrap [type=radio]{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 99;
    opacity: 0;
    cursor: pointer;
  }
  .wrap [type=radio]:checked~.label{/* 覆盖radio */
    background: hsl(200, 100%, 40%);
    color: hsl(0, 0%, 100%)
  }

其中多边形边数选择范围控制为:3-20,当然我们也可以扩大为无限大的边数,但实际应用到的情况比较少。多角星情况类型,范围控制为3~20。

然后对线条粗细,描边颜色,填充颜色显示信息,也就是onchang事件触发时获取value值,再显示出来。显示鼠标当前的位置功能也非常简单,在此也略过不表。

图形基类

开始实现画板的功能,第一步,实现图形基类,这个是最重要的部分。因为不管是线条,多边形都会继承该类。

注意:isPointInPath非常有用,就是这个api实现鼠标是否选中的功能了,它的原理就是调用上下文context绘制路径,然后向isPointInPath传递位置(x,y)信息,该api会返回这个点是否在绘制路径上,相当于绘制的是隐形的路径进行判断点是否在该路径或图形内部,这也是我要把绘制路径和渲染的功能分离开的原因。

具体的功能还是直接看代码吧

  class Graph{
    //初始化图形需要用到的属性,位置,顶点列表,边的宽度,描边颜色,填充颜色,是否填充;
    constructor(pos){
      this.x=pos.x;
      this.y=pos.y;
      this.points=[];
      this.sides=5;
      this.stars=5;
      this.lineWidth=1;
      this.strokeStyle="#f00";
      this.fillStyle="#f00";
      this.isFill=false;
    }
    //实现绘制时的拖拽
    initUpdate(start,end){
      this.points[1]=end;
      this.x=(start.x+end.x)/2;
      this.y=(start.y+end.y)/2;
    }
    //实现修改模式下的拖拽顶点和控制点
    update(i,pos){
      if(i==9999){
        var that=this,
          x1=pos.x-this.x,
          y1=pos.y-this.y;
        this.points.forEach((p,i)=>{
          that.points[i]={x:p.x+x1, y:p.y+y1 };
        });
        this.x=Math.round(pos.x);
        this.y=Math.round(pos.y);
      } else {
        this.points[i]=pos;
        var x=0,y=0;
        this.points.forEach(p=>{
          x+=p.x;
          y+=p.y;
        });
        this.x=Math.round(x/this.points.length);
        this.y=Math.round(y/this.points.length);
      }
    }
    //绘制路径
    createPath(ctx){
      ctx.beginPath();
      this.points.forEach((p,i)=>{
        ctx[i==0?"moveTo":"lineTo"](p.x,p.y);
      });
      ctx.closePath();
    }
    //判断鼠标是否选中对应的图形,选中哪个顶点,选中哪个控制点,中心点;
    isInPath(ctx,pos){
      for(var i=0,point,len=this.points.length;i<len;i++){
        point=this.points[i];
        ctx.beginPath();
        ctx.arc(point.x,point.y,5,0,Math.PI*2,false);
        if(ctx.isPointInPath(pos.x,pos.y)){
          return i;
        }
      }
      this.createPath(ctx);
      if(ctx.isPointInPath(pos.x,pos.y)){
        return 9999;
      }
      return -1
    }
    //绘制控制点
    drawController(ctx){
      this.drawPoints(ctx);
      this.drawCenter(ctx);
    }
    //绘制顶点
    drawPoints(){
      ctx.save();
      ctx.lineWidth=2;
      ctx.strokeStyle="#999";
      this.points.forEach(p=>{
        ctx.beginPath();
        ctx.arc(p.x,p.y,5,0,Math.PI*2,false);
        ctx.stroke();
      });
      ctx.restore();
    }
    //绘制中心点
    drawCenter(ctx){
      ctx.save();
      ctx.lineWidth=1;
      ctx.strokeStyle="hsla(60,100%,45%,1)";
      ctx.fillStyle="hsla(60,100%,50%,1)";
      ctx.beginPath();
      ctx.arc(this.x,this.y,5,0,Math.PI*2,false);
      ctx.stroke();
      ctx.fill();
      ctx.restore();
    }
    //绘制整个图形
    draw(ctx){
      ctx.save();
      ctx.lineWidth=this.lineWidth;
      ctx.strokeStyle=this.strokeStyle;
      ctx.fillStyle=this.fillStyle;
      this.createPath(ctx);
      ctx.stroke();
      if(this.isFill){ ctx.fill(); }
      ctx.restore();
    }
    //生成代码
    createCode(){
      var codes=["// "+this.name];
      codes.push("ctx.save();");
      codes.push("ctx.lineWidth="+this.lineWidth);
      codes.push("ctx.strokeStyle=""+this.strokeStyle+"";");
      if(this.isFill){
        codes.push("ctx.fillStyle=""+this.fillStyle+"";");
      }
      codes.push("ctx.beginPath();");
      codes.push("ctx.translate("+this.x+","+this.y+");")//translate到中心点,方便使用
      this.points.forEach((p,i)=>{
        if(i==0){
          codes.push("ctx.moveTo("+(p.x-this.x)+","+(p.y-this.y)+");");
          // codes.push("ctx.moveTo("+(p.x)+","+(p.y)+");");
        } else {
          codes.push("ctx.lineTo("+(p.x-this.x)+","+(p.y-this.y)+");");
          // codes.push("ctx.lineTo("+(p.x)+","+(p.y)+");");
        }
      });
      codes.push("ctx.closePath();");
      codes.push("ctx.stroke();");
      if(this.isFill){
        codes.push("ctx.fill();");
      }
      codes.push("ctx.restore();");
      return codes.join("
");
    }
  }

直线

line
实现直线功能相当简单,继承基类,只需要重写draw和createCode方法,拖拽和变换等功能都已经在基类实现了。

  class Line extends Graph{
    constructor(pos){
      super(pos);
      this.points=[pos,pos];
      this.name="直线"
    }
    createPath(ctx){
      ctx.beginPath();
      ctx.arc(this.x,this.y,5,0,Math.PI*2,false);
    }
    draw(ctx){
      ctx.save();
      ctx.lineWidth=this.lineWidth;
      ctx.strokeStyle=this.strokeStyle;
      ctx.beginPath();
      this.points.forEach((p,i)=>{
        if(i==0){
          ctx.moveTo(p.x,p.y);
        } else {
          ctx.lineTo(p.x,p.y);
        }
      });
      ctx.closePath();
      ctx.stroke();
      ctx.restore();
    }
    createCode(){
      var codes=["// "+this.name];
      codes.push("ctx.lineWidth="+this.lineWidth);
      codes.push("ctx.strokeStyle=""+this.strokeStyle+"";");
      codes.push("ctx.beginPath();");
      this.points.forEach((p,i)=>{
        if(i==0){
          codes.push("ctx.moveTo("+p.x+","+p.y+");");
        } else {
          codes.push("ctx.lineTo("+p.x+","+p.y+");");
        }
      });
      codes.push("ctx.closePath();");
      codes.push("ctx.stroke();");
      return codes.join("
");
    }
  }

还有就是虚线功能了,其实就是先绘制一段直线,然后空出一段空间,接着再绘制一段直线,如此类推。小伙伴可以思考一下怎么实现,这个和直线所涉及的知识点相同,代码就略过了。

贝塞尔曲线

curve
接着就是贝塞尔曲线的绘制了,首先继承直线类,曲线比直线不同的是除了起始点和结束点,它还多出了控制点,2次贝塞尔曲线有一个控制点,3次贝塞尔曲线有则有两个控制点。所以对应初始化拖拽,顶点绘制的方法必须重写,以下是3次贝塞尔曲线的代码。

  class Bezier extends Line {
    constructor(pos){
      super(pos);
      this.points=[pos,pos,pos,pos];
      this.name="三次贝塞尔曲线"
    }
    initUpdate(start,end){
      var a=Math.round(Math.sqrt(Math.pow(end.x-start.x,2)+Math.pow(end.y-start.y,2)))/2,
        x1=start.x+(end.x-start.x)/2,
        y1=start.y-a,
        y2=end.y+a;

      this.points[1]={x:end.x,y:end.y};
      this.points[2]={x:x1,y:y1<0?0:y1};
      this.points[3]={x:start.x,y:end.y};
      this.points[3]={x:x1,y:y2>H?H:y2};
      this.x=(start.x+end.x)/2;
      this.y=(start.y+end.y)/2;
    }
    drawPoints(ctx){
      ctx.lineWidth=0.5;
      ctx.strokeStyle="#00f";

      //画控制点的连线
      ctx.beginPath();
      ctx.moveTo(this.points[0].x, this.points[0].y);
      ctx.lineTo(this.points[2].x, this.points[2].y);
      ctx.moveTo(this.points[1].x, this.points[1].y);
      ctx.lineTo(this.points[3].x, this.points[3].y);
      ctx.stroke();

      //画连接点和控制点
      this.points.forEach(function(point,i){
        ctx.beginPath();
        ctx.arc(point.x,point.y,5,0,Math.PI*2,false);
        ctx.stroke();
      });
    }
    draw(){
      ctx.save();
      ctx.lineWidth=this.lineWidth;
      ctx.strokeStyle=this.strokeStyle;
      ctx.beginPath();
      ctx.moveTo(this.points[0].x, this.points[0].y);
      ctx.bezierCurveTo(this.points[2].x,this.points[2].y,this.points[3].x,this.points[3].y,this.points[1].x,this.points[1].y);
      ctx.stroke();
      ctx.restore();
    }
    createCode(){
      var codes=["// "+this.name];
      codes.push("ctx.lineWidth="+this.lineWidth);
      codes.push("ctx.strokeStyle=""+this.strokeStyle+"";");
      codes.push("ctx.beginPath();");
      codes.push(`ctx.moveTo(${this.points[0].x},${this.points[0].y});`);
      codes.push(`ctx.bezierCurveTo(${this.points[2].x},${this.points[2].y},${this.points[3].x},${this.points[3].y},${this.points[1].x},${this.points[1].y});`);
      codes.push("ctx.stroke();");
      return codes.join("
");
    }
  }

至于贝塞尔2次曲线功能类似,同时也更加简单,代码也略过。

多边形

polygon
实现任意条边的多边形,大家思考一下都会知道如何实现,平均角度=360度/边数,不是吗?

在知道中点和第一个顶点的情况下,第n个顶点与中点的角度 = n*平均角度;然后记录下每个顶点的位置,然后依次绘制每个顶点的连线即可。这里用到了二维旋转的公式,也就是绕图形的中点,旋转一定的角度。

既然我们已经记录了每个顶点的位置,当拖动对应的顶点后修改该顶点位置,重新绘制,就可以伸缩成任意的图案。

难点是拖拽控制线,实现旋转多边形角度,和扩大缩小多边形。等比例扩大缩小每个顶点与中点的距离即可实现等比例缩放多边形,记录第一个顶点与中点的角度变化即可实现旋转功能,这里用到反正切Math.atan2(y,x)求角度;具体实现看如下代码。

  /**
   * 多边形
   */
  class Polygon extends Graph{
    constructor(pos){
      super(pos);
      this.cPoints=[];
    }
    get name(){
      return this.sides+"边形";
    }
    //生成顶点
    createPoints(start,end){
      var x1 = end.x - start.x,
        y1 = end.y - start.y,
        angle=0;
      this.points=[];
      for(var i=0;i<this.sides;i++){
        angle=2*Math.PI/this.sides*i;
        var sin=Math.sin(angle),
          cos=Math.cos(angle),
          newX = x1*cos - y1*sin,
          newY = y1*cos + x1*sin;
        this.points.push({
          x:Math.round(start.x + newX),
          y:Math.round(start.y + newY)
        });
      }
    }
    //生成控制点
    createControlPoint(start,end,len){
      var x1 = end.x - start.x,
        y1 = end.y - start.y,
        angle=Math.atan2(y1,x1),
        c=Math.round(Math.sqrt(x1*x1+y1*y1)),
        l=c+(!len?0:c/len),
        x2 =l * Math.cos(angle) + start.x, 
            y2 =l * Math.sin(angle) + start.y;
          return {x:x2,y:y2};
    }
    initUpdate(start,end){
      this.createPoints(start,end);
            this.cPoints[0]=this.createControlPoint(start,end,3);
    }
    //拖拽功能
    update(i,pos){
      if(i==10000){//拖拽控制点
        var point=this.createControlPoint({x:this.x,y:this.y},pos,-4);
        this.cPoints[0]=pos;
        this.createPoints({x:this.x,y:this.y},point);
      } else if(i==9999){ //移动位置
        var that=this,
          x1=pos.x-this.x,
          y1=pos.y-this.y;
        this.points.forEach((p,i)=>{
          that.points[i]={x:p.x+x1, y:p.y+y1 };
        });
        this.cPoints.forEach((p,i)=>{
          that.cPoints[i]={x:p.x+x1,y:p.y+y1};
        });
        this.x=Math.round(pos.x);
        this.y=Math.round(pos.y);
      } else {//拖拽顶点
        this.points[i]=pos;
        var x=0,y=0;
        this.points.forEach(p=>{
          x+=p.x;
          y+=p.y;
        });
        this.x=Math.round(x/this.points.length);
        this.y=Math.round(y/this.points.length);
      }
    }
    createCPath(ctx){
      this.cPoints.forEach(p=>{
        ctx.beginPath();
        ctx.arc(p.x,p.y,6,0,Math.PI*2,false);
      });
    }
    isInPath(ctx,pos){
      var index=super.isInPath(ctx,pos);
      if(index>-1) return index;
      this.createCPath(ctx);
      for(var i=0,len=this.cPoints.length;i<len;i++){
        var p=this.cPoints[i];
        ctx.beginPath();
        ctx.arc(p.x,p.y,6,0,Math.PI*2,false);
        if(ctx.isPointInPath(pos.x,pos.y)){
          return 10000+i;break;
        }
      }
      return -1
    }
    drawCPoints(ctx){
      ctx.save();
      ctx.lineWidth=1;
      ctx.strokeStyle="hsla(0,0%,50%,1)";
      ctx.fillStyle="hsla(0,100%,60%,1)";
      this.cPoints.forEach(p=>{
        ctx.beginPath();
        ctx.moveTo(this.x,this.y);
        ctx.lineTo(p.x,p.y);
        ctx.stroke();
        ctx.beginPath();
        ctx.arc(p.x,p.y,6,0,Math.PI*2,false);
        ctx.stroke();
        ctx.fill();
      });
      ctx.restore();
    }
    drawController(ctx){
      this.drawPoints(ctx);
      this.drawCPoints(ctx);
      this.drawCenter(ctx);
    }
  }

多角星

star
仔细思考一下,多角星其实就是2*n边形,不过它是凹多边形而已,于是我们在之前凸多边形基础上去实现。相比于多边形,我们还要在此基础上增加第二控制点,实现凹点与凸点的比值变化,通俗点就是多角星的胖瘦度。

  class Star extends Polygon{
    //增加凹顶点与凸顶点的比例属性size
    constructor(pos){
      super(pos);
      this.cPoints=[];
      this.size=0.5;
    }
    get name() {
      return this.stars+"角星"
    }
    // 增加凹顶点
    createPoints(start,end){
      var x1 = end.x - start.x,
        y1 = end.y - start.y,
        x2 =x1*this.size,
        y2 =y1*this.size,
        angle=0,
        angle2=0;
      this.points=[];
      for(var i=0;i<this.stars;i++){
        angle=2*Math.PI/this.stars*i;
        angle2=angle+Math.PI/this.stars;
        var sin=Math.sin(angle),
          cos=Math.cos(angle),
          newX = x1*cos - y1*sin,
          newY = y1*cos + x1*sin,
          sin2=Math.sin(angle2)

编辑:扁杜

发布:2018-10-23 05:48:45

当前文章:http://leetaemin.cn/array/dkytotgg4y.html

贝斯特老虎机注册送76元 龙8娱乐注册送83元体验金 老虎机注册送15元可提现 博彩注册送23元 注册送888元现金 永利娱乐开户送57元现金 免费注册送96元现金 永利娱乐注册送35元现金

87565 32221 91667 74200 56491 8212990938 73051 47920

我要说两句: (0人参与)

发布