I would like to measure the performance between canvas and svg in HTML5.
I have done so far. I have created multiple circles in svg and canvas.
Both have a 500 x 500 Element width and height.
I found out I am measuring the scripting time. If I use the dev tools in Chrome, the scripting time is nearly equal to my measured time.
Now, how can I measure the rendering time? Would be a code with separate canvas and svg circle creation and devtools for rendering a good way to compare svg and canvas rendering performance?
<html>
<head>
<script type="text/javascript">
var svgNS = "http://www.w3.org/2000/svg";
function createCircle1() {
var t3 = performance.now();
for (var x = 1; x <= 1000; x++) {
for (var y = 1; y <= 100; y++) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.stroke();
}
}
var t4 = performance.now();
console.log("canvas time " + (t4 - t3) + " milliseconds.")
var t0 = performance.now();
for (var x = 1; x <= 1000; x++) {
for (var y = 1; y <= 100; y++) {
var myCircle = document.createElementNS(svgNS, "circle"); //to create a circle, for rectangle use rectangle
myCircle.setAttributeNS(null, "cx", x);
myCircle.setAttributeNS(null, "cy", y);
myCircle.setAttributeNS(null, "r", 5);
myCircle.setAttributeNS(null, "stroke", "none");
document.getElementById("mySVG").appendChild(myCircle);
}
}
var t1 = performance.now();
console.log("svg time " + (t1 - t0) + " milliseconds.")
}
</script>
</head>
<body onload="createCircle1();">
<svg id="mySVG" width="500" height="500" style="border:1px solid #d3d3d3;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>
</body>
</html>
Somehow the scripting time and my measured performance time is different.
Can someone tell me if this performance comparison is useful?
I did the test multiple times, the performance time is always different but canvas is faster than svg in rendering and also in scripting.
Why in rendering? Scripting should be because of the DOM reference of svg?
This test I did with seperate svg and canvas, I just rendered first only svg, and in the next test only canvas.
The problem with SVG.
Below is a performance test of drawing a big circle on a canvas and a SVG image.
Both get about the same performance 30ms per circle on my machine and chrome.
Run the test and see the result. If you watch the progress you may notice that it begins to slow down a little. When the first test is run click the button again and this time you will notice that there is even more of a slow down.
Do a 3rd test and slower still, but the performance per circle for both canvas and SVG has not changed, where is the slow down coming from.
The DOM is not javascript.
The code run to add a node to the SVG does not care how many nodes the SVG has, but when add nodes to the SVG image and your code exits you have informed the DOM that your SVG element is dirty and needs to be redrawn.
I grouped the test into groups of ~10 before exiting. This means that for every ten circles added the DOM will redraw all the SVG nodes from scratch and outside the javascript context and your ability to measure or control it.
When you click test the second time the SVG already has 10000 circles, so after adding the first ten the DOM happily re-renders 10000+10 circles.
It is next to impossible to get an accurate measure of SVG performance.
Using timeline
I ran the code snippet below with timeline recording. I ran the test two times.
The next two images show the same period. The top one is at the start of the test and the next one is at the end of the second test.
I have mark the GPU sections that are likely involved in rendering the SVG. Note how they change from trivial to excessive.
This image shows one cycle of the test 10 renders in the second test cycle. The code execution is barely visible at ~1ms but the GPU is flat out with a huge 175ms devoted to drawing all the SVG circle again.
When you use SVG you must remember that when you make a change to it the DOM will re-render all of it. It does not care if it's visible or not. If you change the size it's redrawn.
To use SVG you must bundle all your calls into one execution context to get the best performance.
Canvas V SVG
Both SVG and Canvas use the GPU with very similar shaders to do the work. Drawing a circle in SVG or Canvas takes the same time. The advantage that the canvas has over SVG is that you control the rendering, what when and where. For SVG you only control content and have little say over rendering.
var running = false;
var test = function(){
if(running){
return;
}
var mySVG = document.getElementById("mySVG");
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var myCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
running = true;
const testCount = 1000;
const groupCount = 10;
var times = [[],[]];
var names =["Canvas test.","SVG test."];
var indexs = [0,0];
var tests = [function(){
now = performance.now();
ctx.beginPath();
ctx.arc(250, 250, 250, 0, 2 * Math.PI);
ctx.fill();
return performance.now()-now;
},
function(){
now = performance.now();
var circle = myCircle.cloneNode();
circle.setAttributeNS(null, "cx", 250);
circle.setAttributeNS(null, "cy", 250);
circle.setAttributeNS(null, "r", 250);
circle.setAttributeNS(null, "stroke", "none");
mySVG.appendChild(circle);
return performance.now()-now;
}];
for(var i = 0; i < testCount; i ++){ // preallocate and zeor arrays
times[0][i] = 0;
times[1][i] = 0;
}
var testComplete = false;
function doTests(){
for(i = 0; i < groupCount; i ++){
var testIndex = Math.floor(Math.random()*2);
times[testIndex][indexs[testIndex]] = tests[testIndex]();
indexs[testIndex] += 1;
if(indexs[testIndex] >= testCount){
testComplete = true;
return;
}
}
}
function getResults(){
// get the mean
var meanA = 0;
var meanB = 0;
var varianceA = 0;
var varianceB = 0;
var totalA = 0;
var totalB = 0;
for(var i = 0; i < testCount; i ++){ // preallocate and zero arrays
totalA += i < indexs[0] ? times[0][i] : 0;
totalB += i < indexs[1] ? times[1][i] : 0;
}
meanA = Math.floor((totalA / indexs[0]) * 1000) / 1000;
meanB = Math.floor((totalB / indexs[1]) * 1000) / 1000;
for(var i = 0; i < testCount; i ++){ // preallocate and zero arrays
varianceA += i < indexs[0] ? Math.pow((times[0][i] - meanA),2) : 0;
varianceB += i < indexs[1] ? Math.pow((times[1][i] - meanB),2) : 0;
}
varianceA = Math.floor((varianceA / indexs[0]) * 1000) / 1000;
varianceB = Math.floor((varianceB / indexs[1]) * 1000) / 1000;
result1.textContent = `Test ${names[0]} Mean : ${meanA}ms Variance : ${varianceA}ms Total : ${totalA.toFixed(3)}ms over ${indexs[0]} tests.`;
result2.textContent = `Test ${names[1]}. Mean : ${meanB}ms Variance : ${varianceB}ms Total : ${totalB.toFixed(3)}ms over ${indexs[1]} tests.`;
}
function test(){
doTests();
var p = Math.floor((((indexs[0] + indexs[1]) /2)/ testCount) * 100);
if(testComplete){
getResults();
p = 100;
running = false;
}else{
setTimeout(test,10);
}
progress.textContent = p+"%";
}
test()
}
startBut.addEventListener("click",test);
#ui {
font-family : Arial;
}
<div id="ui">
<h3>Comparative performance test</h3>
Adding circles to canvas V adding circles to SVG.<br> The test adds 1000 * 10 circles to the canvas or SVG with as much optimisation as possible and to be fair.<br>
<input id="startBut" type="button" value = "Start test"/>
<div id="progress"></div>
<div id="result1"></div>
<div id="result2"></div>
<h3>SVG element</h3>
<svg id="mySVG" width="500" height="500" style="border:1px solid #d3d3d3;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
<h3>Canvas element</h3>
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>
</div>
This is what I am trying to achieve--GRASS Animation(Desired animation)
This is where the project is standing currently --My hair animation
This is a more structurised code of the above code --My hair animation(by markE)--markE`s code of hair animation
PROBLEM:--
I am able to give movements to hairs but animation should be more like wavy grass like freeflowing.Its not very smooth now.What can be done to make the hairs flow in more natural manner.
Please provide me with a small sample if possible!!!
<canvas id="myCanvas" width="500" height="500" style="background-color: antiquewhite" ></canvas>
JAVASCRIPT
//mouse position
var x2=0;
var y2=0;
window.addEventListener("mousemove",function(){moving(event);init()},false)
//these variables define the bend in our bezier curve
var bend9=0;
var bend8=0;
var bend7=0;
var bend6=0;
var bend5=0;
var bend4=0;
var bend3=0;
var bend2=0;
var bend1=0;
//function to get the mouse cordinates
function moving(event) {
bend_value();//this function is defined below
try
{
x2 = event.touches[0].pageX;
y2 = event.touches[0].pageY;
}
catch (error)
{
try
{
x2 = event.clientX;
y2 = event.clientY;
}
catch (e)
{
}
}
try
{
event.preventDefault();
}
catch (e)
{
}
if(between(y2,204,237) && between(x2,115,272))
{
console.log("Xmove="+x2,"Ymove="+y2)
}
}
//function for declaring range of bezier curve
function between(val, min, max)
{
return val >= min && val <= max;
}
(function() {
hair = function() {
return this;
};
hair.prototype={
draw_hair:function(a,b,c,d,e,f,g,h){
var sx =136+a;//start position of curve.used in moveTo(sx,sy)
var sy =235+b;
var cp1x=136+c;//control point 1
var cp1y=222+d;
var cp2x=136+e;//control point 2
var cp2y=222+f;
var endx=136+g;//end points
var endy=210+h;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
// context.clearRect(0, 0,500,500);
context.strokeStyle="grey";
context.lineWidth="8";
context.beginPath();
context.moveTo(sx,sy);
context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,endx,endy);
context.lineCap = 'round';
context.stroke();
// context.restore();
// context.save();
}
};
})();
//this function provides and calculate the bend on mousemove
function bend_value(){
var ref1=135;//this is ref point for hair or curve no 1
var ref2=150;//hair no 2 and so on
var ref3=165;
var ref4=180;
var ref5=195;
var ref6=210;
var ref7=225;
var ref8=240;
var ref9=255;
if(between(x2,115,270) && between(y2,205,236))
{
if(x2>=135 && x2<=145){bend1=(x2-ref1)*(2.2);}
if(x2<=135 && x2>=125){bend1=(x2-ref1)*(2.2);}
if(x2>=150 && x2<=160){bend2=(x2-ref2)*(2.2);}
if(x2<=150 && x2>=140){bend2=(x2-ref2)*(2.2);}
if(x2>=165 && x2<=175){bend3=(x2-ref3)*(2.2);}
if(x2<=165 && x2>=155){bend3=(x2-ref3)*(2.2);}
if(x2>=180 && x2<=190){bend4=(x2-ref4)*(2.2);}
if(x2<=180 && x2>=170){bend4=(x2-ref4)*(2.2);}
if(x2>=195 && x2<=205){bend5=(x2-ref5)*(2.2);}
if(x2<=195 && x2>=185){bend5=(x2-ref5)*(2.2);}
if(x2>=210 && x2<=220){bend6=(x2-ref6)*(2.2);}
if(x2<=210 && x2>=200){bend6=(x2-ref6)*(2.2);}
if(x2>=225 && x2<=235){bend7=(x2-ref7)*(2.2);}
if(x2<=225 && x2>=215){bend7=(x2-ref7)*(2.2);}
if(x2>=240 && x2<=250){bend8=(x2-ref8)*(2.2);}
if(x2<=240 && x2>=230){bend8=(x2-ref8)*(2.2);}
if(x2>=255 && x2<=265){bend9=(x2-ref9)*(2.2);}
if(x2<=255 && x2>=245){bend9=(x2-ref9)*(2.2);}
}
}
function init(){//this function draws each hair/curve
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var clear=context.clearRect(0, 0,500,500);
var save=context.save();
// /* console.log("bend2="+bend2)
// console.log("bend3="+bend3)
// console.log("bend4="+bend4)
// console.log("bend5="+bend5)
// console.log("bend6="+bend6)
// console.log("bend7="+bend7)
// console.log("bend8="+bend8)
// console.log("bend9="+bend9)*/
hd1 = new hair();//hd1 stands for hair draw 1.this is an instance created for drawing hair no 1
clear;
hd1.draw_hair(0,0,0,0,0,0,0+bend1/2,0);//these parameters passed to function drawhair and bend is beint retrieved from function bend_value()
save;
hd2 = new hair();
clear;
hd2.draw_hair(15,0,15,0,15,0,15+bend2/2,0);
save;
hd3 = new hair();
clear;
hd3.draw_hair(30,0,30,0,30,0,30+bend3/2,0);
save;
hd4 = new hair();
clear;
hd4.draw_hair(45,0,45,0,45,0,45+bend4/2,0);
save;
hd5 = new hair();
clear;
hd5.draw_hair(60,0,60,0,60,0,60+bend5/2,0);
save;
}
window.onload = function() {
init();
disableSelection(document.body)
}
function disableSelection(target){
if (typeof target.onselectstart!="undefined") //IE
target.onselectstart=function(){return false}
else if (typeof target.style.MozUserSelect!="undefined") //Firefox
target.style.MozUserSelect="none"
else //All other ie: Opera
target.onmousedown=function(){return false}
target.style.cursor = "default"
}
Update: I'm currently adjusting the code to produce the requested result and commenting it.
(function() { // The code is encapsulated in a self invoking function to isolate the scope
"use strict";
// The following lines creates shortcuts to the constructors of the Box2D types used
var B2Vec2 = Box2D.Common.Math.b2Vec2,
B2BodyDef = Box2D.Dynamics.b2BodyDef,
B2Body = Box2D.Dynamics.b2Body,
B2FixtureDef = Box2D.Dynamics.b2FixtureDef,
B2Fixture = Box2D.Dynamics.b2Fixture,
B2World = Box2D.Dynamics.b2World,
B2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
B2RevoluteJoint = Box2D.Dynamics.Joints.b2RevoluteJoint,
B2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef;
// This makes sure that there is a method to request a callback to update the graphics for next frame
window.requestAnimationFrame =
window.requestAnimationFrame || // According to the standard
window.mozRequestAnimationFrame || // For mozilla
window.webkitRequestAnimationFrame || // For webkit
window.msRequestAnimationFrame || // For ie
function (f) { window.setTimeout(function () { f(Date.now()); }, 1000/60); }; // If everthing else fails
var world = new B2World(new B2Vec2(0, -10), true), // Create a world with gravity
physicalObjects = [], // Maintain a list of the simulated objects
windInput = 0, // The input for the wind in the current frame
wind = 0, // The current wind (smoothing the input values + randomness)
STRAW_COUNT = 10, // Number of straws
GRASS_RESET_SPEED = 2, // How quick should the straw reset to its target angle
POWER_MOUSE_WIND = 120, // How much does the mouse affect the wind
POWER_RANDOM_WIND = 180; // How much does the randomness affect the wind
// GrassPart is a prototype for a piece of a straw. It has the following properties
// position: the position of the piece
// density: the density of the piece
// target: the target angle of the piece
// statik: a boolean stating if the piece is static (i.e. does not move)
function GrassPart(position, density, target, statik) {
this.width = 0.05;
this.height = 0.5;
this.target = target;
// To create a physical body in Box2D you have to setup a body definition
// and create at least one fixture.
var bdef = new B2BodyDef(), fdef = new B2FixtureDef();
// In this example we specify if the body is static or not (the grass roots
// has to be static to keep the straw in its position), and its original
// position.
bdef.type = statik? B2Body.b2_staticBody : B2Body.b2_dynamicBody;
bdef.position.SetV(position);
// The fixture of the piece is a box with a given density. The negative group index
// makes sure that the straws does not collide.
fdef.shape = new B2PolygonShape();
fdef.shape.SetAsBox(this.width/2, this.height/2);
fdef.density = density;
fdef.filter.groupIndex = -1;
// The body and fixture is created and added to the world
this.body = world.CreateBody(bdef);
this.body.CreateFixture(fdef);
}
// This method is called for every frame of animation. It strives to reset the original
// angle of the straw (the joint). The time parameter is unused here but contains the
// current time.
GrassPart.prototype.update = function (time) {
if (this.joint) {
this.joint.SetMotorSpeed(GRASS_RESET_SPEED*(this.target - this.joint.GetJointAngle()));
}
};
// The link method is used to link the pieces of the straw together using a joint
// other: the piece to link to
// torque: the strength of the joint (stiffness)
GrassPart.prototype.link = function(other, torque) {
// This is all Box2D specific. Look it up in the manual.
var jdef = new B2RevoluteJointDef();
var p = this.body.GetWorldPoint(new B2Vec2(0, 0.5)); // Get the world coordinates of where the joint
jdef.Initialize(this.body, other.body, p);
jdef.maxMotorTorque = torque;
jdef.motorSpeed = 0;
jdef.enableMotor = true;
// Add the joint to the world
this.joint = world.CreateJoint(jdef);
};
// A prototype for a straw of grass
// position: the position of the bottom of the root of the straw
function Grass(position) {
var pos = new B2Vec2(position.x, position.y);
var angle = 1.2*Math.random() - 0.6; // Randomize the target angle
// Create three pieces, the static root and to more, and place them in line.
// The second parameter is the stiffness of the joints. It controls how the straw bends.
// The third is the target angle and different angles are specified for the pieces.
this.g1 = new GrassPart(pos, 1, angle/4, true); // This is the static root
pos.Add(new B2Vec2(0, 1));
this.g2 = new GrassPart(pos, 0.75, angle);
pos.Add(new B2Vec2(0, 1));
this.g3 = new GrassPart(pos, 0.5);
// Link the pieces into a straw
this.g1.link(this.g2, 20);
this.g2.link(this.g3, 3);
// Add the pieces to the list of simulate objects
physicalObjects.push(this.g1);
physicalObjects.push(this.g2);
physicalObjects.push(this.g3);
}
Grass.prototype.draw = function (context) {
var p = new B2Vec2(0, 0.5);
var p1 = this.g1.body.GetWorldPoint(p);
var p2 = this.g2.body.GetWorldPoint(p);
var p3 = this.g3.body.GetWorldPoint(p);
context.strokeStyle = 'grey';
context.lineWidth = 0.4;
context.lineCap = 'round';
context.beginPath();
context.moveTo(p1.x, p1.y);
context.quadraticCurveTo(p2.x, p2.y, p3.x, p3.y);
context.stroke();
};
var lastX, grass = [], context = document.getElementById('canvas').getContext('2d');
function updateGraphics(time) {
window.requestAnimationFrame(updateGraphics);
wind = 0.95*wind + 0.05*(POWER_MOUSE_WIND*windInput + POWER_RANDOM_WIND*Math.random() - POWER_RANDOM_WIND/2);
windInput = 0;
world.SetGravity(new B2Vec2(wind, -10));
physicalObjects.forEach(function(obj) { if (obj.update) obj.update(time); });
world.Step(1/60, 8, 3);
world.ClearForces();
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.save();
context.translate(context.canvas.width/2, context.canvas.height/2);
context.scale(context.canvas.width/20, -context.canvas.width/20);
grass.forEach(function (o) { o.draw(context); });
context.restore();
}
document.getElementsByTagName('body')[0].addEventListener("mousemove", function (e) {
windInput = Math.abs(lastX - e.x) < 200? 0.2*(e.x - lastX) : 0;
lastX = e.x;
});
var W = 8;
for (var i = 0; i < STRAW_COUNT; i++) {
grass.push(new Grass(new B2Vec2(W*(i/(STRAW_COUNT-1))-W/2, -1)));
}
window.requestAnimationFrame(updateGraphics);
})();
Waving grass algorithm
UPDATE
I made a reduced update to better meet what I believe is your requirements. To use mouse you just calculate the angle between the mouse point and the strain root and use that for new angle in the update.
I have incorporated a simple mouse-move sensitive approach which makes the strains "point" towards the mouse, but you can add random angles to this as deltas and so forth. Everything you need is as said in the code - adjust as needed.
New fiddle (based on previous with a few modifications):
http://jsfiddle.net/AbdiasSoftware/yEwGc/
Image showing 150 strains being simulated.
Grass simulation demo:
http://jsfiddle.net/AbdiasSoftware/5z89V/
This will generate a nice realistic looking grass field. The demo has 70 grass rendered (works best in Chrome or just lower the number for Firefox).
The code is rather simple. It consists of a main object (grassObj) which contains its geometry as well as functions to calculate the angles, segments, movements and so forth. I'll show this in detail below.
First some inits that are accessed globally by the functions:
var numOfGrass = 70, /// number of grass strains
grass,
/// get canvas context
ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
/// we use an animated image for the background
/// The image also clears the canvas for each loop call
/// I rendered the clouds in a 3D software.
img = document.createElement('img'),
ix = 0, /// background image position
iw = -1; /// used for with and initial for flag
/// load background image, use it whenever it's ready
img.onload = function() {iw = this.width}
img.src = 'http://i.imgur.com/zzjtzG7.jpg';
The heart - grassObj
The main object as mentioned above is the grassObj:
function grassObj(x, y, seg1, seg2, maxAngle) {
/// exposed properties we need for rendering
this.x = x; /// bottom position of grass
this.y = y;
this.seg1 = seg1; /// segments of grass
this.seg2 = seg2;
this.gradient = getGradient(Math.random() * 50 + 50, 100 * Math.random() + 170);
this.currentAngle; ///current angle that will be rendered
/// internals used for calculating new angle, goal, difference and speed
var counter, /// counter between 0-1 for ease-in/out
delta, /// random steps in the direction goal rel. c.angle.
angle, /// current angle, does not change until goal is reached
diff, /// diff between goal and angle
goal = getAngle();
/// internal: returns an angel between 0 and maxAngle
function getAngle() {
return maxAngle * Math.random();
}
/// ease in-out function
function easeInOut(t) {
return t < 0.5 ? 4 * t * t * t : (t-1) * (2 * t - 2) * (2 * t - 2) + 1;
}
/// sets a new goal for grass to move to. Does the main calculations
function newGoal() {
angle = goal; /// set goal as new angle when reached
this.currentAngle = angle;
goal = getAngle(); /// get new goal
diff = goal - angle; /// calc diff
counter = 0; /// reset counter
delta = (4 * Math.random() + 1) / 100;
}
/// creates a gradient for this grass to increase realism
function getGradient(min, max) {
var g = ctx.createLinearGradient(0, 0, 0, h);
g.addColorStop(1, 'rgb(0,' + parseInt(min) + ', 0)');
g.addColorStop(0, 'rgb(0,' + parseInt(max) + ', 0)');
return g;
}
/// this is called from animation loop. Counts and keeps tracks of
/// current position and calls new goal when current goal is reached
this.update = function() {
/// count from 0 to 1 with random delta value
counter += delta;
/// if counter passes 1 then goal is reached -> get new goal
if (counter > 1) {
newGoal();
return;
}
/// ease in/out function
var t = easeInOut(counter);
/// update current angle for render
this.currentAngle = angle + t * diff;
}
/// init
newGoal();
return this;
}
Grass generator
We call makeGrass to generate grass at random positions, random heights and with random segments. The function is called with number of grass to render, width and height of canvas to fill and a variation variable in percent (0 - 1 float).
The single grass consist only of four points in total. The two middle points are spread about 1/3 and 2/3 of the total height with a little variation to break pattern. The points when rendered, are smoother using a cardinal spline with full tension to make the grass look smooth.
function makeGrass(numOfGrass, width, height, hVariation) {
/// setup variables
var x, y, seg1, seg2, angle,
hf = height * hVariation, /// get variation
i = 0,
grass = []; /// array to hold the grass
/// generate grass
for(; i < numOfGrass; i++) {
x = width * Math.random(); /// random x position
y = height - hf * Math.random(); /// random height
/// break grass into 3 segments with random variation
seg1 = y / 3 + y * hVariation * Math.random() * 0.1;
seg2 = (y / 3 * 2) + y * hVariation * Math.random() * 0.1;
grass.push(new grassObj(x, y, seg1, seg2, 15 * Math.random() + 50));
}
return grass;
}
Render
The render function just loops through the objects and updates the current geometry:
function renderGrass(ctx, grass) {
/// local vars for animation
var len = grass.length,
i = 0,
gr, pos, diff, pts, x, y;
/// renders background when loaded
if (iw > -1) {
ctx.drawImage(img, ix--, 0);
if (ix < -w) {
ctx.drawImage(img, ix + iw, 0);
}
if (ix <= -iw) ix = 0;
} else {
ctx.clearRect(0, 0, w, h);
}
/// loops through the grass object and renders current state
for(; gr = grass[i]; i++) {
x = gr.x;
y = gr.y;
ctx.beginPath();
/// calculates the end-point based on length and angle
/// Angle is limited [0, 60] which we add 225 deg. to get
/// it upwards. Alter 225 to make grass lean more to a side.
pos = lineToAngle(ctx, x, h, y, gr.currentAngle + 225);
/// diff between end point and root point
diff = (pos[0] - x)
pts = [];
/// starts at bottom, goes to top middle and then back
/// down with a slight offset to make the grass
pts.push(x); /// first couple at bottom
pts.push(h);
/// first segment 1/4 of the difference
pts.push(x + (diff / 4));
pts.push(h - gr.seg1);
/// second segment 2/3 of the difference
pts.push(x + (diff / 3 * 2));
pts.push(h - gr.seg2);
pts.push(pos[0]); /// top point
pts.push(pos[1]);
/// re-use previous data, but go backward down to root again
/// with a slight offset
pts.push(x + (diff / 3 * 2) + 10);
pts.push(h - gr.seg2);
pts.push(x + (diff / 4) + 12);
pts.push(h - gr.seg1 + 10);
pts.push(x + 15); /// end couple at bottom
pts.push(h);
/// smooth points (extended context function, see demo)
ctx.curve(pts, 0.8, 5);
ctx.closePath();
/// fill grass with its gradient
ctx.fillStyle = gr.gradient;
ctx.fill();
}
}
Animate
The main loop where we animate everything:
function animate() {
/// update each grass objects
for(var i = 0;i < grass.length; i++) grass[i].update();
/// render them
renderGrass(ctx, grass);
/// loop
requestAnimationFrame(animate);
}
And that's all there is to it for this version.
Darn! Late to the party...
But LOTS of neat answers here -- I'm upvoting all !
Anyway, here's my idea:
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/MJjHz/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.1/jquery-ui.js"></script>
<style>
body { font-family: arial; padding:15px; }
canvas { border: 1px solid red;}
input[type="text"]{width:35px;}
</style>
</head>
<body>
<p>Move mouse across hairs</p>
<canvas height="100" width="250" id="canvas"></canvas>
<script>
$(function() {
var canvas=document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var cHeight=canvas.height;
var showControls=false;
var lastMouseX=0;
// preset styling CONSTANTS
var SWAY=.55; // max endpoint sway from center
var C1Y=.40; // fixed Y of cp#1
var C2SWAY=.20 // max cp#2 sway from center
var C2Y=.75; // fixed Y of cp#2
var YY=20; // max height of ellipse at top of hair
var PIPERCENT=Math.PI/100;
var hairs=[];
// create hairs
var newHairX=40;
var hairCount=20;
for(var i=0;i<hairCount;i++){
var randomLength=50+parseInt(Math.random()*5);
addHair(newHairX+(i*8),randomLength);
}
function addHair(x,length){
hairs.push({
x:x,
length:length,
left:0,
right:0,
top:0,
s:{x:0,y:0},
c1:{x:0,y:0},
c2:{x:0,y:0},
e:{x:0,y:0},
isInMotion:false,
currentX:0
});
}
for(var i=0;i<hairs.length;i++){
var h=hairs[i];
setHairPointsFixed(h);
setHairPointsPct(h,50);
draw(h);
}
function setHairPointsFixed(h){
h.s.x = h.x;
h.s.y = cHeight;
h.c1.x = h.x;
h.c1.y = cHeight-h.length*C1Y;
h.c2.y = cHeight-h.length*C2Y;
h.top = cHeight-h.length;
h.left = h.x-h.length*SWAY;
h.right = h.x+h.length*SWAY;
}
function setHairPointsPct(h,pct){
// endpoint
var a=Math.PI+PIPERCENT*pct;
h.e.x = h.x - ((h.length*SWAY)*Math.cos(a));
h.e.y = h.top + (YY*Math.sin(a));
// controlpoint#2
h.c2.x = h.x + h.length*(C2SWAY*2*pct/100-C2SWAY);
}
//////////////////////////////
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// draw this frame based on mouse moves
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<hairs.length;i++){
hairMoves(hairs[i],mouseX,mouseY);
}
lastMouseX=mouseX;
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
function hairMoves(h,mouseX,mouseY){
// No hair movement if not touching hair
if(mouseY<cHeight-h.length-YY){
if(h.isInMotion){
h.isInMotion=false;
setHairPointsPct(h,50);
}
draw(h);
return;
}
// No hair movement if too deep in hair
if(mouseY>h.c1.y){
draw(h);
return;
}
//
var pct=50;
if(mouseX>=h.left && mouseX<=h.right){
if(h.isInMotion){
var pct=-(mouseX-h.right)/(h.right-h.left)*100;
setHairPointsPct(h,pct);
draw(h);
}else{
// if hair is at rest
// but mouse has just contacted hair
// set hair in motion
if( (lastMouseX<=h.x && mouseX>=h.x )
||(lastMouseX>=h.x && mouseX<=h.x )
){
h.isInMotion=true;
var pct=-(mouseX-h.right)/(h.right-h.left)*100;
}
setHairPointsPct(h,pct);
draw(h);
}
}else{
if(h.isInMotion){
h.isInMotion=false;
setHairPointsPct(h,50);
};
draw(h);
}
}
function dot(pt,color){
ctx.beginPath();
ctx.arc(pt.x,pt.y,5,0,Math.PI*2,false);
ctx.closePath();
ctx.fillStyle=color;
ctx.fill();
}
function draw(h){
ctx.beginPath();
ctx.moveTo(h.s.x,h.s.y);
ctx.bezierCurveTo(h.c1.x,h.c1.y,h.c2.x,h.c2.y,h.e.x,h.e.y);
ctx.strokeStyle="orange";
ctx.lineWidth=3;
ctx.stroke();
if(showControls){
dot(h.s,"green");
dot(h.c1,"red");
dot(h.c2,"blue");
dot(h.e,"purple");
ctx.beginPath();
ctx.rect(h.left,h.top-YY,(h.right-h.left),h.length*(1-C1Y)+YY)
ctx.lineWidth=1;
ctx.strokeStyle="lightgray";
ctx.stroke();
}
}
});
</script>
</body>
</html>
Here is a simple hair simulation that seems to be what you are looking for. The basic idea is to draw a bezier curve (in this case I use two curves to provide thickness for the hair). The curve will have a base, a bending point, and a tip. I set the bending point halfway up the hair. The tip of the hair will rotate about the axis of the base of the hair in response to mouse movement.
Place this code in a script tag below the canvas element declaration.
function Point(x, y) {
this.x = x;
this.y = y;
}
function Hair( ) {
this.height = 100; // hair height
this.baseWidth = 3; // hair base width.
this.thickness = 1.5; // hair thickness
this.points = {};
this.points.base1 = new Point(Math.random()*canvas.width, canvas.height);
// The point at which the hair will bend. I set it to the middle of the hair, but you can adjust this.
this.points.bendPoint1 = new Point(this.points.base1.x-this.thickness, this.points.base1.y - this.height / 2)
this.points.bendPoint2 = new Point(this.points.bendPoint1.x, this.points.bendPoint1.y-this.thickness); // complement of bendPoint1 - we use this because the hair has thickness
this.points.base2 = new Point(this.points.base1.x + this.baseWidth, this.points.base1.y) // complement of base1 - we use this because the hair has thickness
}
Hair.prototype.paint = function(mouseX, mouseY, direction) {
ctx.save();
// rotate the the tip of the hair
var tipRotationAngle = Math.atan(Math.abs(this.points.base1.y - mouseY)/Math.abs(this.points.base1.x - mouseX));
// if the mouse is on the other side of the hair, adjust the angle
if (mouseX < this.points.base1.x) {
tipRotationAngle = Math.PI - tipRotationAngle;
}
// if the mouse isn't close enough to the hair, it shouldn't affect the hair
if (mouseX < this.points.base1.x - this.height/2 || mouseX > this.points.base1.x + this.height/2 || mouseY < this.points.base1.y - this.height || mouseY > this.points.base1.y) {
tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
}
// Use the direction of the mouse to as a lazy way to simulate the direction the hair should bend.
// Note that in real life, the direction that the hair should bend has nothing to do with the direction of motion. It actually depends on which side of the hair the force is being applied.
// Figuring out which side of the hair the force is being applied is a little tricky, so I took this shortcut.
// If you run your finger along a comb quickly, this approximation will work. However if you are in the middle of the comb and slowly change direction, you will notice that the force is still applied in the opposite direction of motion as you slowly back off the set of tines.
if ((mouseX < this.points.base1.x && direction == 'right') || (mouseX > this.points.base1.x && direction == 'left')) {
tipRotationAngle = Math.PI/2; // 90 degrees, which means the hair is straight
}
var tipPoint = new Point(this.points.base1.x + this.baseWidth + this.height*Math.cos(tipRotationAngle), this.points.base1.y - this.height*Math.sin(tipRotationAngle));
ctx.beginPath();
ctx.moveTo(this.points.base1.x, this.points.base1.y); // start at the base
ctx.bezierCurveTo(this.points.base1.x, this.points.base1.y, this.points.bendPoint1.x, this.points.bendPoint1.y, tipPoint.x, tipPoint.y); // draw a curve to the tip of the hair
ctx.bezierCurveTo(tipPoint.x, tipPoint.y, this.points.bendPoint2.x, this.points.bendPoint2.y, this.points.base2.x, this.points.base2.y); // draw a curve back down to the base using the complement points since the hair has thickness.
ctx.closePath(); // complete the path so we have a shape that we can fill with color
ctx.fillStyle='rgb(0,0,0)';
ctx.fill();
ctx.restore();
}
// I used global variables to keep the example simple, but it is generally best to avoid using global variables
window.canvas = document.getElementById('myCanvas');
window.ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200,255,255)'; // background color
window.hair = [];
window.prevClientX = 0;
for (var i = 0; i < 100; i++) {
hair.push(new Hair());
}
// initial draw
ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas
for (var i = 0; i < hair.length; i++) {
hair[i].paint(0, 0, 'right');
}
window.onmousemove = function(e) {
ctx.fillRect(0,0,canvas.width,canvas.height); // clear canvas
for (var i = 0; i < hair.length; i++) {
hair[i].paint(e.clientX, e.clientY, e.clientX > window.prevClientX ? 'right' : 'left');
}
window.prevClientX = e.clientX;
}
Made this some time ago, might be useful to some people. Just adjust the variables at the beginning of the code with the values that fits your wishes:
...
Mheight = 1;
height = 33;
width = 17;
distance = 10;
randomness = 14;
angle = Math.PI / 2;
...
Also on http://lucasm0ta.github.io/JsGrass/
I have a small program that I am supposed to write that makes a bouncy ball in a canvas. I can get a wireframe of a ball bouncing, but can't seem to get the setTimeout to fire at all. I have read, read and read about the function, but can't figure this out (new).
<!DOCTYPE HTML>
<html>
<head>
<title>basic Canvas</title>
<style>
#canvas1{
border:1px solid #9C9898;
}
body{
margin:0px;
padding:0px;
}
</style>
<script>
function drawMe(){
//Set x,y,radius
var x = 60;
var y = 60;
var radius = 70;
drawLoop(x,y,radius);
}
function drawLoop(x,y,radius){
var canvas2=document.getElementById("canvas1");
var ctx=canvas2.getContext("2d");
for(i=1;i<100;i++){
if(y + radius >= canvas2.height){
d = 1;
}
if(y - radius <= 0){
d = 0;
}
if (d==0){
x = x + 10;
y = y + 10;
}
else if (d==1){
x = x + 10;
y = y - 10;
}
draw(x,y,radius);
window.setTimeout(function() {draw(x,y,radius)},3000);
}
}
function draw(x,y,radius){
var canvas2=document.getElementById("canvas1");
var ctx=canvas2.getContext("2d");
ctx.beginPath();
ctx.arc(x,y,radius,0,2*Math.PI,false);
var gradient = ctx.createRadialGradient(x, y, 1, x, y, radius);
gradient.addColorStop(0,"blue");
gradient.addColorStop(1,"white");
ctx.fillStyle=gradient;
ctx.lineWidth=1;
ctx.strokeStyle="blue";
ctx.fill();
ctx.stroke();
}
</script>
</head>
<body onload="drawMe()">
<canvas id="canvas1" width=1000" height="400">
</canvas>
</body>
</html>
A little function called 'drawMe()' which sets x, y, and radius, then calls a little drawing loop that fires 100 times that draws the bouncy ball ('drawLoop'). at the bottom of the function drawLoop, I call draw, which actually drawls the circles. From what I've read, the line 'setTimeout(function(){draw(x,y,radius)};,3000); should call the draw function every three seconds. But it doesn't. What the heck am I doing wrong?
setTimeouts are counted from the time they are created. The loop runs almost instantly and creates the setTimeouts at almost the same time. They are then all ran 3 seconds later.
One way to get around this is in the solution below. This does not increment the loop until the current timeout has been completed.
http://jsfiddle.net/x8PWg/14/
This is only one of the many potential solutions to this.
I tried both of these in canvas and nothing showed, also I doubt it is even efficient :/. I am trying to make rain that comes down the screen.. Wondering what is the most efficient way of doing this. I am a beginner at animation and would really appreciate help.
I suspect that creating a rain object would be best, each with the quality of coming down the screen then coming to the top and then an array with them...maybe with random x values withing the canvas width and y values of 0 but I don't know how to implement that. Please help!
xofRain = 20;
startY = 0;
ctx.beginPath();
ctx.moveTo(xofRain, startY);
ctx.lineTo(xofRain, startY + 20);
ctx.closePath();
ctx.fillStyle = "black";
ctx.fill();
function rain(xofRain){
startY = canvas.height();
ctx.moveTo(xofRain, startY);
ctx.beginPath();
ctx.lineTo(xofRain, startY + 3);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
}
Here comes your answer, this snow rain is created using pure HTML5 Canvas, the technique used to achieve this animation is called "Double Buffer Animation". First it is good to know what is Double Buffer animation technique.
Double Buffer Technique: This is an advanced technique to make animation clear and with less flickers in it. In this technique 2 Canvas is used, one is displayed on webpage to show the result and second one is used to create animation screens in backed process.
How this will help full, suppose we have to create a animation with very high number of move, as in our Snow Fall example, there are number of Flakes are moving with there own speed, so keep them moving, we have to change position of each flake and update it on the canvas, this is quite heavy process to deal with.
So Now instead of updating each Flake directly on our page canvas, we will create a buffer Canvas, where all these changes take place and we just capture a Picture from Buffer canvas after 30ms and display it on our real canvas.
This way our animation will be clear and without flickers. So here is a live example of it.
http://aspspider.info/erishaan8/html5rain/
Here is the code of it:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>HTML5 Rain</title>
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<style>
article, aside, figure, footer, header, hgroup,
menu, nav, section { display: block; }
</style>
<script type="text/javascript">
var canvas = null;
var context = null;
var bufferCanvas = null;
var bufferCanvasCtx = null;
var flakeArray = [];
var flakeTimer = null;
var maxFlakes = 200; // Here you may set max flackes to be created
function init() {
//Canvas on Page
canvas = document.getElementById('canvasRain');
context = canvas.getContext("2d");
//Buffer Canvas
bufferCanvas = document.createElement("canvas");
bufferCanvasCtx = bufferCanvas.getContext("2d");
bufferCanvasCtx.canvas.width = context.canvas.width;
bufferCanvasCtx.canvas.height = context.canvas.height;
flakeTimer = setInterval(addFlake, 200);
Draw();
setInterval(animate, 30);
}
function animate() {
Update();
Draw();
}
function addFlake() {
flakeArray[flakeArray.length] = new Flake();
if (flakeArray.length == maxFlakes)
clearInterval(flakeTimer);
}
function blank() {
bufferCanvasCtx.fillStyle = "rgba(0,0,0,0.8)";
bufferCanvasCtx.fillRect(0, 0, bufferCanvasCtx.canvas.width, bufferCanvasCtx.canvas.height);
}
function Update() {
for (var i = 0; i < flakeArray.length; i++) {
if (flakeArray[i].y < context.canvas.height) {
flakeArray[i].y += flakeArray[i].speed;
if (flakeArray[i].y > context.canvas.height)
flakeArray[i].y = -5;
flakeArray[i].x += flakeArray[i].drift;
if (flakeArray[i].x > context.canvas.width)
flakeArray[i].x = 0;
}
}
}
function Flake() {
this.x = Math.round(Math.random() * context.canvas.width);
this.y = -10;
this.drift = Math.random();
this.speed = Math.round(Math.random() * 5) + 1;
this.width = (Math.random() * 3) + 2;
this.height = this.width;
}
function Draw() {
context.save();
blank();
for (var i = 0; i < flakeArray.length; i++) {
bufferCanvasCtx.fillStyle = "white";
bufferCanvasCtx.fillRect(flakeArray[i].x, flakeArray[i].y, flakeArray[i].width, flakeArray[i].height);
}
context.drawImage(bufferCanvas, 0, 0, bufferCanvas.width, bufferCanvas.height);
context.restore();
}
</script>
</head>
<body onload="init()">
<canvas id="canvasRain" width="800px" height="800px">Canvas Not Supported</canvas>
</body>
</html>
Also if you find this help full, accept as Answer and make it up. o_O
Cheers!!!
I'm not sure what "most efficient" is. If it was me I'd do it in WebGL but whether or not that's efficient is not clear to me.
In either case I'd try to use a stateless formula. Creating and updating state for every raindrop is arguably slow.
const ctx = document.querySelector("canvas").getContext("2d");
const numRain = 200;
function render(time) {
time *= 0.001; // convert to seconds
resizeCanvasToDisplaySize(ctx.canvas);
const width = ctx.canvas.width;
const height = ctx.canvas.height;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
resetPseudoRandom();
const speed = time * 500;
ctx.fillStyle = "#68F";
for (let i = 0; i < numRain; ++i) {
const x = pseudoRandomInt(width);
const y = (pseudoRandomInt(height) + speed) % height;
ctx.fillRect(x, y, 3, 8);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
let randomSeed_ = 0;
const RANDOM_RANGE_ = Math.pow(2, 32);
function pseudoRandom() {
return (randomSeed_ =
(134775813 * randomSeed_ + 1) %
RANDOM_RANGE_) / RANDOM_RANGE_;
};
function resetPseudoRandom() {
randomSeed_ = 0;
};
function pseudoRandomInt(n) {
return pseudoRandom() * n | 0;
}
function resizeCanvasToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
Note that I could have used ctx.moveTo(x, y); ctx.lineTo(x, y + 8); for each line and then at the end of the loop called ctx.stroke(). I didn't do that because I'm assuming it would be less efficient than using ctx.fillRect. In order for the canvas to draw lines it actually has to allocate a dynamic path (you call ctx.beginPath). It then has to record all the lines you add. Then it has to expand those lines into vertices of various kinds to rasterize the lines. You can basically see the various algorithms it uses here. Conversely none of that has to happen with ctx.fillRect. No allocations have to happen (not saying they don't happen, just saying they don't have to). The canvas can just use a single pre-allocated quad and draw it on the GPU by passing the correct matrix to draw whatever rectangle you ask of it. Of course they're might be more overhead calling ctx.fillRect 200 times rather than ctx.moveTo, ctx.lineTo 200s + ctx.stroke once but really that's up to the browser.
The rain above may or may not be a good enough rain effect. That wasn't my point in posting really. The point is efficiency. Pretty much all games that have some kind of rain effect do some kind of stateless formula for their rain. A different formula would generate different or less repetitive rain. The point is it being stateless.
I'm writing a little object oriented style javasscript demo -- just to draw a bunch of balls moving around the screen. nothing fancy, no collision detection or anything at this point. Consider it safe to assume my Ball.js class is good.
My question amounts to this: Where should I call ball.draw(context) ? The only way to get balls drawn to the screen the way I set it up seems to be by placing the call in generateBalls(). But that means each ball is just drawn once.
So I'd really appreaciate it if someone could point out the error of my ways here. This isn't homework - just trying to get a better handle on javascript and canvas.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<script src="ball.js"></script>
<script src="utils.js"></script>
...
<canvas id="canvas" width="600" height="480"></canvas>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasSupport() {
return true;
}
function canvasApp() {
if(!canvasSupport()) {
return;
}
}
console.log("app entered");
var numBalls = 45;
//var numBalls = demo.numberofballs.value;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = new Array();
var tempBall;
var tempX;
var tempY;
var tempSpeed;
var tempAngle;
var tempRadius;
var tempRadians;
var tempXunits;
var tempYunits;
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
generateBalls();
setInterval(drawScreen, 33);
function generateBalls() {
console.log("Make some balls");
for(var index = 0; index < numBalls; index++) {
var tempRadius = Math.floor(Math.random()*maxSize)+minSize;
var ball = new Ball(tempRadius, "#000000");
ball.x = tempRadius * 2 + (Math.floor(Math.random()*canvas.width) - tempRadius * 2);
ball.y = tempRadius * 2 + (Math.floor(Math.random()*canvas.height) - tempRadius * 2);
ball.speed = maxSpeed - tempRadius;
ball.angle = Math.floor(Math.random()*360);
ball.dx = Math.cos(tempRadians) * tempSpeed;
ball.dy = Math.sin(tempRadians) * tempSpeed;
// here outputted balls but a stupid place to put it LOL
balls.push(ball);
}
}
function drawScreen() {
console.log("draw screen");
// loop through all balls and adjust their position
// a BallManager could do this more cleanly
for(var index = 0; index < balls.length; index++) {
context.fillStyle="#EE00EE";
context.fillRect(0,0,canvas.width, canvas.height);
// Box
context.strokeStyle = "#ff0043";
context.strokeRect(1,1,canvas.width-2, canvas.height-2);
// place balls
context.fillStyle = "#ff8783";
console.log("ball mover loop in drawscreen");
// no var ball now
ball = balls[index];
ball.x += ball.dx;
ball.y += ball.dy;
ball.draw(context);
//checkBoundaries(balls[index]);
if(ball.x > canvas.width || ball.x < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if(ball.y > canvas.height || ball.y < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
//ball.draw(context);
}
}
}
//function checkBoundaries(ball) {
//console.log("Check Bounds: " + " " + "ball.x: " + ball.x + " " + //"ball.y: " + ball.y);
//}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.dx = Math.cos(ball.radians) * ball.speed;
ball.dy = Math.sin(ball.radians) * ball.speed;
//ball.draw(context);
}
</script>
</body>
</html>
Thank you for your advice,
Marc
Your example contains more than one error, please check your modified code. It works, but you must extend and correct it.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<script type="text/javascript">
// next lines is a Ball() implementation code
Ball = function(radius,color) {
this.radius=radius;
this.color=color;
};
Ball.prototype.x=0;
Ball.prototype.y=0;
Ball.prototype.speed=0;
Ball.prototype.angle=0;
Ball.prototype.dx=0;
Ball.prototype.dy=0;
Ball.prototype.radius=10;
Ball.prototype.color="#000";
Ball.prototype.draw=function() {
context.beginPath();
context.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
context.lineWidth = 5;
context.strokeStyle = this.color; // line color
context.stroke();
context.closePath();
};
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
//console.log("app entered");
window.canvas = document.getElementById("canvas");
window.context = canvas.getContext("2d");
generateBalls();
// if you want to use setInterval() instead replace next line
setTimeout(drawScreen, 33);
}
function canvasSupport() {
return true;
}
function canvasApp() {
if(!canvasSupport()) {
return;
}
}
var numBalls = 45;
//var numBalls = demo.numberofballs.value;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = new Array();
var tempBall;
var tempX;
var tempY;
var tempSpeed;
var tempAngle;
var tempRadius;
var tempRadians;
var tempXunits;
var tempYunits;
function generateBalls() {
//console.log("Make some balls");
for(var index = 0; index < numBalls; index++) {
var tempRadius = Math.floor(Math.random()*maxSize)+minSize;
var tempRadians = Math.random()*Math.PI;
var tempSpeed = 10;
var ball = new Ball(tempRadius, "#000000");
ball.x = tempRadius * 2 + (Math.floor(Math.random()*canvas.width) - tempRadius * 2);
ball.y = tempRadius * 2 + (Math.floor(Math.random()*canvas.height) - tempRadius * 2);
ball.speed = maxSpeed - tempRadius;
ball.angle = Math.floor(Math.random()*360);
ball.dx = Math.cos(tempRadians) * tempSpeed;
ball.dy = Math.sin(tempRadians) * tempSpeed;
// here outputted balls but a stupid place to put it LOL
balls.push(ball);
}
}
function drawScreen() {
console.log("draw screen");
context.fillStyle="#EE00EE";
context.fillRect(0,0,canvas.width, canvas.height);
// Box
context.strokeStyle = "#ff0043";
context.strokeRect(1,1,canvas.width-2, canvas.height-2);
// loop through all balls and adjust their position
// a BallManager could do this more cleanly
for(var index = 0; index < balls.length; index++) {
// place balls
context.fillStyle = "#008700";
//console.log("ball mover loop in drawscreen");
// no var ball now
ball = balls[index];
ball.x += ball.dx;
ball.y += ball.dy;
ball.draw(context);
//checkBoundaries(balls[index]);
if(ball.x > canvas.width || ball.x < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if(ball.y > canvas.height || ball.y < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
//ball.draw(context);
}
}
// if you want to use setInterval() instead remove next line
setTimeout(drawScreen, 33);
}
//function checkBoundaries(ball) {
//console.log("Check Bounds: " + " " + "ball.x: " + ball.x + " " + //"ball.y: " + ball.y);
//}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.dx = Math.cos(ball.radians) * ball.speed;
ball.dy = Math.sin(ball.radians) * ball.speed;
//ball.draw(context);
}
</script>
</head>
<body>
<canvas id="canvas" width="600" height="480" style="background:red;"></canvas>
</body>
</html>
http://jsfiddle.net/QVgZx/2/
Just necroing the thread to add a bit of an update for anyone new to canvas who arrives here:
Stan recommended creating a function to draw each of the balls at one spot in code; this is good for a number of reasons, it makes the animation smoother, and the code far easier to debug/maintain.
I would disagree with Stan, however, when it comes to using setInterval to trigger this... although, that might very well have been common practice when Stan wrote it.
Now, however, we use requestAnimationFrame.
This will sync your painting with the rest of the browser.
This allows your code to run far faster since every time the browser draws something, it has to go through the entire page again to determine where everything will go, just in case something moved.
Using setInterval leaves no guarantee as to when it will fire, and certainly doesn't time it to coincide with the browser's screen refresh.
This means it is drawing a little, reconfiguring the page, drawing a little more, reconfiguring again, drawing some more... and again and again. This is very bad, and very slow.
Using requestAnimationFrame, however, allows the browser to call your function whenever it is about to redraw the screen anyway... which means a single redraw, and a single refresh.
Far faster, far cleaner.
It works slightly differently, but is actually very simple.
requestAnimationFrame(redraw);
This registers your function 'redraw' with requestAnimationFrame so that the next time the browser wants to redraw the window, your function will be called.
The one gotcha with this is that it will only call your function ONCE... in that way, it is like a setTimeout rather than a setInterval.
This way, you don't need to pass around the timer variable to stop the animation; it will stop because it stopped being called.
However, to ensure that your animation continues to run, just put that very same call at the bottom of your redraw function:
function redraw()
{
var blah, blahblah;
...
requestAnimationFrame(redraw);
}
You can also set this conditionally, to run your animation until it is finished, then stop:
function redraw()
{
...
if (!finished) requestAnimationFrame(redraw);
}
Mozilla reference HERE, Paul Irish HERE, and Chris Coyer HERE.