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,y
It is the coordinate value of the root part,branchLen,branchWidth
They are the length and width of the branches,depth
Number of layers of branches,canvas
It 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.toX
Equal to initial coordinatesx
And end coordinatestoY
Equal to initialy
Minus 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.angle
It 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.depth
Parameters. 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:oBranchAngle
Used to record the initial angle;branchAngleFactor
Used 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 = true
Used 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
,strengthY
These two attributes indicate the distance between the tree moving on the X axis and the Y axis due to the force.
AddstrengthXFactor
,strengthYFactor
The 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 = 0
Used to record the start time of restoration. When the finger leaves the screen (touchend), it is assigned 0.oStrengthX
,oStrengthY
Record 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