Use Canvas to create a tree that can swing with gestures

Source: Internet
Author: User
Tags linecap

Use Canvas to create a tree that can swing with gestures

Create a swinging tree as the background of the page as needed. In order to increase page interaction, I added a mouse (touch) event to the tree in the background so that he could make corresponding actions based on the mouse (touch, the tree will swing as your fingers slide up or down or left or right. First, let's look at the final effect.

Step 1. Complete the HTML page and create a new Tree class.

After the HTML page is complete, create a new Tree class to record the attributes of the Tree. Wherex,yIt is the coordinate value of the root part,branchLen,branchWidthThey are the length and width of the branches,depthNumber of layers of branches,canvasIt is used to connect the canvas Element in the page (the default is the element with the ID of 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;}


Click to view historical code

Step2. Add the drawRoot method to draw the trunk

First, draw the first branch in drawRoot. The parameter value of drawRoot is the same as that of drawRoot. Run drawRoot in the Tree constructor and pass in the parameters accepted by the Tree. In the last new Tree class, the root of the Tree is located in the center of the screen. The branches are PX long, the branches are 8 px wide, and the branches are 8 layers (not available for the time being ).var atree = new Tree(canvas.width/2-4,canvas.height,100,8,8,canvas);

In drawRoot, we need to uselineTo()Draw branches. Coordinate value of the start of a branch(x,y)The ending coordinate value is given.(toX,toY)Computing is required. The first picture is the trunk, and the end coordinate is as the trunk is perpendicular to the ground.toXEqual to initial coordinatesxAnd end coordinatestoYEqual to initialyMinus the trunk lengthbranchLen(Note that the 0, 0 Points of coordinates are in the upper left corner of the 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);

Run the Code:


Click to view historical code

Step3. Add the drawBranch method to draw branches.

DrawBranch draws a straight line based on the initial and final coordinates to represent the branches. Different from the trunk, the branches no longer have a vertical angle with the ground, but maintain a certain angle with the trunk, and the initial value of the branches is the end point of the trunk.(toX,toY). So we add a new parameter to drawBranch.angleIt is used to indicate the vertical angle α between the branches and the trunk, so that toX and toY can be calculated based on α. See the figure.

In this way, after the trunk is painted, we will draw two different branches from different angles. One is30°One-30°. The width branchWidth of the branches is reduced by one pixel so that it is different from the trunk width.

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();}

Run the Code:


Click to view historical code

Step 4. Modify the drawBranch function and repeat the branches.

Call drawBranch twice at the end of the drawBranch function.

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

Make the call to complete recursion. Note that the input angle is increased or decreased by 30 degrees based on the previous angle.

In order for recursion to stop, we need a stop condition, which is never used before.depthParameters. Before drawing the next layer, we subtract 1 to indicate that the branches have been drawn. Until the depth is reduced to 0, it indicates that all layers have been drawn.

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);    }}

Run the Code:


Because the angle between trees is too large and all branches are of the same length, they do not look like a tree. Therefore, we need to add several parameters to the Tree constructor to adjust the Tree's posture.

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

BranchLenFactor: used to control the length of a branchLen when drawing each layer of branches. RootLenFactor: it is used to control the length of the root when the root is drawn on branchLen. BranchAngle: used to control the angle between branches.

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);    }  }

Run the Code:


Click to view historical code

Step 5. Shake the branches

To shake the branches, we only need to change the angle between the branches. I need to add three new attributes to the Tree constructor:oBranchAngleUsed to record the initial angle;branchAngleFactorUsed to control the variation of angle over time;swingAngle: The angle used to record shaking increases with time.
Modify the drawRoot function so that it does not need to accept parameters. It is more convenient to call.

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);    }  }

Add a cyclic function to repaint the entire tree in the cyclic function, and modify the branchAngle value for each re-painting to shake the big tree. atree.swingAngle++;Change the shaking angle with time. Used here Math.sin(atree.swingAngle*(Math.PI/180))You can obtain a continuous change value between-1 and 1. atree.branchAngle = Math.sin(atree.swingAngle*(Math.PI/180))*atree.branchAngleFactor+atree.oBranchAngle;Multiply the coefficient and add it to the original angle.

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);

Run the Code:


Click to view historical code

Step 6. Add a gesture

Only touch events are added to save time. The mouse events and touch events are handled in the same way.
First, add an attribute to the Tree.swingSwitch = trueUsed to indicate whether the big tree is swinging. The swing stops when your fingers touch the screen and continues when you exit the screen.
AddstrengthX,strengthYThese two attributes indicate the distance between the tree moving on the X axis and the Y axis due to the force.
AddstrengthXFactor,strengthYFactorThe maximum distance between the X axis and the Y axis in the next slide.

function Tree(x,y,branchLen,branchWidth,depth,canvas){    ......    this.swingSwitch = true;    ......    this.strengthX = 0;    this.strengthY = 0;    ......  }
// Record the information at the touch start var touchStart = {x: 0, y: 0, strengthX: 0, strengthY: 0}; document. addEventListener ('touchstart', function (e) {// stop the tree from swinging atree. swingSwitch = false; touchStart. x = e. touches [0]. clientX; touchStart. y = e. touches [0]. clientY; // record the touchStart value of the original strength when the touch starts. strengthX = atree. strengthX; touchStart. strengthY = atree. strengthY;}); document. addEventListener ('touchmove ', function (e) {// block the default browser Action e. preventDefault (); // (touchStart. x-e.touches [0]. clientX)/canvas. width can be used to obtain a 0-1 value atree Based on the sliding distance. 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) {// restore the swing atree. swingSwitch = true ;});

Modify drawBranch to add the change of strength to the calculation of angle, toX, and toY. For details, see comments.

Tree. prototype. drawBranch = function (x, y, branchLen, branchWidth, angle, depth) {var angle = angle | 0; // use strengthX multiplied by (depth/this. depth) makes the branches not sensitive to angle changes + = this. strengthX * (depth/this. depth)/this. strengthXFactor * this. branchAngle; var radian = (90-angle) * (Math. PI/180); // use strengthX multiplied by (1-depth/this. depth) makes the branch end sensitive to angle changes. 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 );......}

Add the restoration code in the animation loop to restore strengthX and strengthY to 0, and add the swingSwitch judgment.

Function loop (time ){...... // if (atree. swingSwitch) {// restore strength to 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 );

Run the Code:


Click to view historical code

Step7. Add the easing Effect

In step 6, the code for restoring strengthX and strengthY is too simple, and the animation is restored to 0 at a constant speed, which is too abrupt. The actual situation should be caused by slow recovery, so we need to add a slow recovery code. First, addrecoverStartTime = 0Used to record the start time of restoration. When the finger leaves the screen (touchend), it is assigned 0.oStrengthX,oStrengthYRecord the target values of strengthX and 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; // gentle atree to the power of 5. strengthX = Math. max (atree. oStrengthX-atree.oStrengthX * (t = t/2000-1) * t + 1) +);} if (atree. strengthX <0) {if (atree. recoverStartTime = 0) {atree. recoverStartTime = time;} var t = time-atree.recoverStartTime; // gentle atree to the power of 5. strengthX = Math. min (atree. oStrengthX-atree.oStrengthX * (t = t/2000-1) * t + 1) +);} if (atree. strengthY> 0) {if (atree. recoverStartTime = 0) {atree. recoverStartTime = time;} var t = time-atree.recoverStartTime; // gentle atree to the power of 5. strengthY = Math. max (atree. oStrengthY-atree.oStrengthY * (t = t/2000-1) * t + 1) +);} if (atree. strengthY <0) {if (atree. recoverStartTime = 0) {atree. recoverStartTime = time;} var t = time-atree.recoverStartTime; // gentle atree to the power of 5. strengthY = Math. min (atree. oStrengthY-atree.oStrengthY * (t = t/2000-1) * t + 1) + );}}......} document. addEventListener ('touchend', function (e) {atree. recoverStartTime = 0; atree. oStrengthX = atree. strengthX; atree. oStrengthY = atree. strengthY ;......});

Run the Code:

Click to view historical code

Step 7. Shake the trunk and move it to the left of the screen

Modify drawRoot so that the trunk can shake and modifyvar atree = new Tree(10,canvas.height,100,8,8,canvas);Move it to the left.


Tree. prototype. drawRoot = function (){...... // Add 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 );

Run the Code:

Click to view historical code

Step8.

Add the processing angle in the animation loop to the swing () of the Tree.

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);

Run the Code:



Go to Github to view all the code.


If you have any questions, ask Weibo @ UED Tianji. I will reply in time


Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.