zoukankan      html  css  js  c++  java
  • 使用Canvas基于手势可以使树秋千

    用Canvas制作能够依据手势摆动的树

    依据工作的须要。制作一个摆动的树做为页面的背景。为了添加页面的交互性,我又为背景中的树添加了鼠标(触控)事件,使他可以依据鼠标(触控)做出对应的动作,当手指做上下或者左右滑动的时候树会跟着摆动。先看看终于效果。
    终于效果

    Step1.完毕HTML页面。新建一个Tree类

    完毕HTML页面后新建一个Tree类用来记录树的各个属性。当中x,y为树根部的坐标值。branchLen,branchWidth各自是树枝的长度与宽度,depth为树枝的层数。canvas用来接页面中的canvas元素(默认是ID为canvas的元素)。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        this.canvas = canvas || document.getElementById('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.x = x||0;
        this.y = y||0;
        this.branchLen = branchLen||0;
        this.branchWidth = branchWidth||0;
        var depth = depth || 5;
    }


    点击查看历史代码

    Step2.加入drawRoot方法,用来绘制树干

    首先在drawRoot中画第一个枝干。

    drawRoot的參数意义同上。而且在Tree类的构造函数中执行drawRoot并把Tree接受到的參数传入。最后new一个Tree类,使树根位于屏幕的底部正中心。树枝长100px,树枝宽度为8px,树枝层数为8层(临时用不上)。

    var atree = new Tree(canvas.width/2-4,canvas.height,100,8,8,canvas);

    在drawRoot中我们须要用lineTo()画出树枝。树枝的起始的坐标值(x,y)已经给出,结束的坐标值(toX,toY)须要进行计算。第一个画的是树干,因为树干垂直于地面所以结束坐标toX等于初始坐标x,而结束坐标toY等于初始y减去树干长度branchLen(注意坐标的0,0点在canvas的左上角)。var toX = x;var toY = y-branchLen;


    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        this.canvas = canvas || document.getElementById('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.x = x||0;
        this.y = y||0;
        this.branchLen = branchLen||0;
        this.branchWidth = branchWidth||0;
        var depth = depth || 5;
        this.drawRoot(this.x,this.y,this.branchLen,this.branchWidth);
    }
    Tree.prototype.drawRoot = function(x,y,branchLen,branchWidth){
        var toX = x;
        var toY = y-branchLen;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
    }
    var atree = new Tree(canvas.width/2-4,canvas.height,100,8,8,canvas);

    执行代码:

    效果图
    点击查看历史代码

    Step3.加入drawBranch方法,用来绘制树枝

    drawBranch相同是依据初始与结束坐标画出一条直线代表树枝。

    与树干不同的是树枝不再是垂直与地面而是与树干保持一定的角度。并且树枝的初始值是树干的结束点(toX,toY)。所以在drawBranch中我们增加新參数angle用来表示树枝与树干的垂直夹角α,这样就能够依据α算出toX与toY。请看图。
    效果图
    这样我们在画完树干后再分别画两个不同角度的树枝。一个是30°一个-30°。并将传给树枝的宽度branchWidth减小一个像素。使其与树干粗细不同。

    Tree.prototype.drawRoot = function(x,y,branchLen,branchWidth){
        var toX = x;
        var toY = y-branchLen;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        this.drawBranch(toX,toY,branchLen,branchWidth-1,30);
        this.drawBranch(toX,toY,branchLen,branchWidth-1,-30);
    }
    Tree.prototype.drawBranch = function(x,y,branchLen,branchWidth,angle){
        var angle = angle || 0;
        var radian = (90-angle)*(Math.PI/180);
        var toX = x+Math.cos(radian)*branchLen;
        var toY = y-Math.sin(radian)*branchLen;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
    }

    执行代码:

    效果图
    点击查看历史代码

    Step4.改动drawBranch函数,反复画树枝

    在drawBranch函数的最后再次调用两次drawBranch

    this.drawBranch(toX,toY,branchLen,branchWidth-1,angle+30);
    this.drawBranch(toX,toY,branchLen,branchWidth-1,angle-30);

    使其调用自己完毕递归,注意这里传入的角度是在之前的角度的基础上在添加或者降低30度。

    为了使递归停下来我们须要一个停止条件,就是之前一直没实用到的depth參数。我们在每次画下一层之前使其减1表示已经完毕了一层树枝的绘制,直至depth减小到0表示绘制全然部的层数。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        this.canvas = canvas || document.getElementById('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.x = x||0;
        this.y = y||0;
        this.branchLen = branchLen||0;
        this.branchWidth = branchWidth||0;
        var depth = depth || 5;
        this.drawRoot(this.x,this.y,this.branchLen,this.branchWidth,depth);
    }
    Tree.prototype.drawRoot = function(x,y,branchLen,branchWidth,depth){
        var toX = x;
        var toY = y-branchLen;
        var depth = depth||5;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        depth--;
        if(depth>0){
          this.drawBranch(toX,toY,branchLen,branchWidth-1,30,depth);
          this.drawBranch(toX,toY,branchLen,branchWidth-1,-30,depth);
        }
    }
    Tree.prototype.drawBranch = function(x,y,branchLen,branchWidth,angle,depth){
        var angle = angle || 0;
        var radian = (90-angle)*(Math.PI/180);
        var toX = x+Math.cos(radian)*branchLen;
        var toY = y-Math.sin(radian)*branchLen;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        depth--;
        if(depth>0){
          this.drawBranch(toX,toY,branchLen,branchWidth-1,angle+30,depth);
          this.drawBranch(toX,toY,branchLen,branchWidth-1,angle-30,depth);
        }
    }

    执行代码:

    效果图
    因为树之间角度过大,并且全部树枝长度都相等,看起来并不像一棵树。所以我们须要在Tree的构造函数中增加几个參数用来调整树的姿态。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        ......
        this.branchLenFactor = 0.8;
        this.rootLenFactor = 1.2;
        this.branchAngle = 20;
        ......
    }

    branchLenFactor:画每一层树枝的时候乘在branchLen上面,用来控制树枝长度。

    rootLenFactor:画树根的时候乘在branchLen上面,用来控制树根长度。branchAngle: 用来控制树枝之间的角度。

    Tree.prototype.drawRoot = function(x,y,branchLen,branchWidth,depth){
        var toX = x;
        var toY = y-branchLen*this.rootLenFactor;
        var depth = depth||5;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        depth--;
        if(depth>0){
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,this.branchAngle,depth);
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,-this.branchAngle,depth);
        }
      }
      Tree.prototype.drawBranch = function(x,y,branchLen,branchWidth,angle,depth){
        var angle = angle || 0;
        var radian = (90-angle)*(Math.PI/180);
        var toX = x+Math.cos(radian)*branchLen;
        var toY = y-Math.sin(radian)*branchLen;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        depth--;
        if(depth>0){
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,angle+this.branchAngle,depth);
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,angle-this.branchAngle,depth);
        }
      }

    执行代码:

    效果图
    点击查看历史代码

    Step5.使树枝晃动起来

    为了使树枝有摇晃的效果。我们仅仅须要改变树枝之间的角度branchAngle就能够了。我须要在Tree的构造函数中添加三个新属性:oBranchAngle用来记录初始角度。branchAngleFactor用来控制角度随时间变化的变化量;swingAngle:随时间添加用来记录摇动的角度。
    同一时候改动下drawRoot函数使其不用接受參数。调用更加方便。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        ......
        this.branchAngle = 20;
        this.oBranchAngle = this.branchAngle;
        this.branchAngleFactor = 5;
        this.swingAngle = 0;
        ......
        this.drawRoot();
    }
    
    Tree.prototype.drawRoot = function(){
        var x = this.x,y=this.y,branchLen = this.branchLen,depth = this.depth,branchWidth = this.branchWidth;
        var toX = x;
        var toY = y-branchLen*this.rootLenFactor;
        var depth = depth||5;
        this.ctx.save();
        this.ctx.strokeStyle="rgba(37, 141, 194, 0.93)";
        this.ctx.beginPath();
        this.ctx.lineCap = "butt";
        this.ctx.lineJoin="round";
        this.ctx.lineWidth = this.branchWidth;
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(toX,toY);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
        depth--;
        if(depth>0){
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,this.branchAngle,depth);
          this.drawBranch(toX,toY,branchLen*this.branchLenFactor,branchWidth-1,-this.branchAngle,depth);
        }
      }

    添加循环函数,在循环函数中重绘整个树。而且每次重绘都要改动branchAngle值,使大树摇动起来。atree.swingAngle++;使摇动角度随时间变化。这里使用Math.sin(atree.swingAngle*(Math.PI/180))能够获得一个-1至1之间的连续变化值。atree.branchAngle = Math.sin(atree.swingAngle*(Math.PI/180))*atree.branchAngleFactor+atree.oBranchAngle;乘以系数并加在原角度上。

    function loop(time){
        ctx.clearRect(0,0,canvas.width,canvas.height);
        atree.branchAngle = Math.sin(atree.swingAngle*(Math.PI/180))*atree.branchAngleFactor+atree.oBranchAngle;
        atree.drawRoot()
        requestAnimFrame(loop);
      }
      loop(0);

    执行代码:

    效果图
    点击查看历史代码

    Step6.加入手势

    这里为了省事仅仅加入了touch事件,mouse事件与touch事件的处理方法大体一致。
    首先为Tree新加一个属性swingSwitch = true用来表示大树是否摆动。当手指触控到屏幕的时候摆动停止,离开屏幕的时候摆动继续。
    加入strengthX,strengthY两个属性;分别表示树在x轴与y轴因受到的力而移动的距离。


    加入strengthXFactor,strengthYFactor;分别用来表示再一次滑动中x轴与y轴移动的最大距离。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        ......
        this.swingSwitch = true;
        ......
        this.strengthX = 0;
        this.strengthY = 0;
        ......
      }
    //记录触控開始时的信息
    var touchStart = {x:0,y:0,strengthX:0,strengthY:0};
    document.addEventListener('touchstart',function(e){
        //让树停止摆动
        atree.swingSwitch = false;
        touchStart.x = e.touches[0].clientX;
        touchStart.y = e.touches[0].clientY;
        //记录触控開始时,原strength的值
        touchStart.strengthX = atree.strengthX;
        touchStart.strengthY = atree.strengthY;
    });
    document.addEventListener('touchmove',function(e){
        //阻止浏览器默认动作
        e.preventDefault();
        //(touchStart.x-e.touches[0].clientX)/canvas.width能够依据滑动距离获得一个0-1的值
        atree.strengthX = touchStart.strengthX-(touchStart.x-e.touches[0].clientX)/canvas.width*atree.strengthXFactor;
        atree.strengthY = touchStart.strengthY-(touchStart.y-e.touches[0].clientY)/canvas.height*atree.strengthYFactor;
    });
    document.addEventListener('touchend',function(e){
        //恢复摆动
        atree.swingSwitch = true;
    });

    改动drawBranch将strength的变化加入到角度与toX,toY的计算中,详情见凝视。

    Tree.prototype.drawBranch = function(x,y,branchLen,branchWidth,angle,depth){
    
        var angle = angle || 0;
        //用strengthX乘以(depth/this.depth)使得树枝末梢对角度的变化不敏感
        angle += this.strengthX*(depth/this.depth)/this.strengthXFactor*this.branchAngle;
        var radian = (90-angle)*(Math.PI/180);
        //用strengthX乘以(1-depth/this.depth)使得树枝末梢对角度的变化敏感
        var toX = x+Math.cos(radian)*branchLen+this.strengthX*(1-depth/this.depth);
        var toY = y-Math.sin(radian)*branchLen+this.strengthY*(1-depth/this.depth);
        ......
    }

    在动画循环中加入恢复代码,使strengthX,strengthY恢复为0。并添加swingSwitch的推断。

    function loop(time){
        ......
        //当swingSwitch开启时開始摆动
        if(atree.swingSwitch){
            //将strength恢复到0
          if(atree.strengthX >0){
            atree.strengthX -= 1;
          }
          if(atree.strengthX <0){
            atree.strengthX += 1;
          }
          if(atree.strengthY >0){
            atree.strengthY -= 1;
          }
          if(atree.strengthY <0){
            atree.strengthY += 1;
          }
          atree.swingAngle++;
          atree.branchAngle = Math.sin(atree.swingAngle*(Math.PI/180))*atree.branchAngleFactor+atree.oBranchAngle;
        }
        ......
    }
    loop(0);

    执行代码:

    效果图
    点击查看历史代码

    Step7.加入缓动效果

    Step6中的恢复strengthX,strengthY的代码过于简单,动画匀速恢复到0,显得过于突兀。比較真实的情况应该是由快变慢的恢复,所以我们要为恢复代码加上缓动。首先在Tree中加入recoverStartTime = 0用来记录恢复開始的时间。在手指离开屏幕的时候(touchend)将其赋为0,同一时候用oStrengthX,oStrengthY记录下来strengthX与strengthY的目标值。

    function Tree(x,y,branchLen,branchWidth,depth,canvas){
        ......
        this.recoverStartTime = 0;
        ......
    }
    function loop(time){
        ......
        if(atree.swingSwitch){
          if(atree.strengthX > 0){
            if(atree.recoverStartTime == 0){
              atree.recoverStartTime = time;
            }
            var t = time-atree.recoverStartTime;
            //五次方的缓动
            atree.strengthX =  Math.max(atree.oStrengthX-atree.oStrengthX*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(atree.strengthX < 0){
            if(atree.recoverStartTime == 0){
              atree.recoverStartTime = time;
            }
            var t = time-atree.recoverStartTime;
            //五次方的缓动
            atree.strengthX =  Math.min(atree.oStrengthX-atree.oStrengthX*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(atree.strengthY > 0){
            if(atree.recoverStartTime == 0){
              atree.recoverStartTime = time;
            }
            var t = time-atree.recoverStartTime;
            //五次方的缓动
            atree.strengthY =  Math.max(atree.oStrengthY-atree.oStrengthY*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(atree.strengthY < 0){
            if(atree.recoverStartTime == 0){
              atree.recoverStartTime = time;
            }
            var t = time-atree.recoverStartTime;
            //五次方的缓动
            atree.strengthY =  Math.min(atree.oStrengthY-atree.oStrengthY*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
        }
        ......
    }
    document.addEventListener('touchend',function(e){
      atree.recoverStartTime = 0;
      atree.oStrengthX = atree.strengthX;
      atree.oStrengthY = atree.strengthY;
      ......
    });

    执行代码:

    效果图点击查看历史代码

    Step7.使树干摇动并移至屏幕左边

    改动drawRoot使树干也能够晃动,并改动var atree = new Tree(10,canvas.height,100,8,8,canvas);使其移至左边。


    Tree.prototype.drawRoot = function(){
        ......
        //添加strength
        var angle = 0;
        angle += this.strengthX/this.strengthXFactor*this.branchAngle;
        var radian = (90-angle)*(Math.PI/180);
        var toX = x+Math.cos(radian)*branchLen*this.rootLenFactor;
        var toY = y-Math.sin(radian)*branchLen*this.rootLenFactor;
        ......
    }
    var atree = new Tree(10,canvas.height,100,8,8,canvas);

    执行代码:

    效果图

    点击查看历史代码

    Step8.

    将动画循环中处理角度的部分加入到Tree的swing()中。

    Tree.prototype.swing = function(time){
        this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
        if(this.swingSwitch){
          if(this.strengthX > 0){
            if(this.recoverStartTime == 0){
              this.recoverStartTime = time;
            }
            var t = time-this.recoverStartTime;
            this.strengthX =  Math.max(this.oStrengthX-this.oStrengthX*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(this.strengthX < 0){
            if(this.recoverStartTime == 0){
              this.recoverStartTime = time;
            }
            var t = time-this.recoverStartTime;
            this.strengthX =  Math.min(this.oStrengthX-this.oStrengthX*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(this.strengthY > 0){
            if(this.recoverStartTime == 0){
              this.recoverStartTime = time;
            }
            var t = time-this.recoverStartTime;
            this.strengthY =  Math.max(this.oStrengthY-this.oStrengthY*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          if(this.strengthY < 0){
            if(this.recoverStartTime == 0){
              this.recoverStartTime = time;
            }
            var t = time-this.recoverStartTime;
            this.strengthY =  Math.min(this.oStrengthY-this.oStrengthY*((t=t/2000-1)*t*t*t*t + 1)+0,0);
          }
          this.swingAngle++;
          this.branchAngle = Math.sin(this.swingAngle*(Math.PI/180))*this.branchAngleFactor+this.oBranchAngle;
        }
        this.drawRoot();
    }
    var atree = new Tree(10,canvas.height,100,8,8,canvas);
    function loop(time){
        atree.swing(time);
        requestAnimFrame(loop);
    }
    loop(0);

    执行代码:
    效果图



    查看全部代码请去Github


    如有问题或者建议请微博@UED天机

    我会及时回复

    也能够收藏天机的官网,http://ued.sexy/  经常更新了最新的教程。


    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    Mybatis Plus整合PageHelper分页的实现示例
    关于xlrd最新版本不支持.xlsx文件的解决办法
    mysql 错误解决大法 Specified key was too long; max key length is 767 bytes
    php连接redis
    flask通过request.path获取定义view函数的文件和行号
    使用bash内置命令complete来实现参数补全
    linux下对比两个文件夹下python文件的差异
    ssh登录后自动切换到原来的目录
    ssh &2>1 和重定向顺序问题
    wsl1(win10)中安装bochs
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4730466.html
Copyright © 2011-2022 走看看