Related
I've been trying to recreate this Project in canvas and Javascript. I wasn't able to decipher the original code so I did it from scratch. The difference is that my projects starts to lag at about 2500 particles while the project above works with 30 000.
I'll paste my entire code below but these are the relevant parts:
var particleContainer = []
var distance = 10
for(let i = 0; i< square.height/distance; i++){
for(let j = 0; j< square.height/distance; j++){
particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}
}
if( c < 90 ){
i.xVelocity = a/c * -20
i.yVelocity = b/c * -20
}else if(90 < c && c < 95){
i.xVelocity = a/c * -1
i.yVelocity = b/c * -1
}else if(c2 !== 0){
i.xVelocity =( a2/c2 )
i.yVelocity = (b2/c2 )
}
(c -> distance between mouse and particle)
I'm creating a new Particle every 'distance' pixels of my square and pushing all of them into an array. when My mouse is to close to one of them the particle will start moving away from the mouse until it is 90-95px away from the Mouse.
30 000 pixels seems to work in a similar fashion judging from this line
for ( i = 0; i < NUM_PARTICLES; i++ ) {
p = Object.create( particle );
p.x = p.ox = MARGIN + SPACING * ( i % COLS );
p.y = p.oy = MARGIN + SPACING * Math.floor( i / COLS );
list[i] = p;
}
but that project doesn't run into the same case of performance Issues as I.
my full code for reference, (html is just a canvas):
var canvas = document.querySelector("canvas")
var c = canvas.getContext('2d')
function getMousePos(canvas, evt) {
// var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX,
y: evt.clientY
};
}
document.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
mouse.x= mousePos.x;
mouse.y= mousePos.y;
}, false);
var mouse = {
x:0,
y:0
}
function Particle(x,y){
this.x = x;
this.y = y;
this.xFixed = x;
this.yFixed = y;
this.radius = 1
this.xVelocity = 0
this.yVelocity = 0
this.color = 'white'
}
Particle.prototype.draw = function(){
c.save()
c.beginPath()
c.arc(this.x, this.y, this.radius,0,Math.PI*2,false)
c.fillStyle = this.color
c.fill()
}
Particle.prototype.update = function(){
this.draw()
this.x += this.xVelocity
this.y += this.yVelocity
}
var square = {
x: 500,
y: 150,
height: 500,
width: 500,
color: 'white'
}
var particleContainer = []
var distance = 10
for(let i = 0; i< square.height/distance; i++){
for(let j = 0; j< square.height/distance; j++){
particleContainer.push( new Particle(square.x + i*distance,square.y + j*distance) )
}
}
function animate(){
requestAnimationFrame(animate);
c.clearRect(0,0,window.innerWidth,window.innerHeight)
canvas.width = window.innerWidth
canvas.height = window.innerHeight
for(i of particleContainer){
let a = mouse.x - i.x
let b = mouse.y - i.y
let c = Math.sqrt(Math.pow(b,2) + Math.pow(a,2))
let a2 = i.xFixed - i.x
let b2 = i.yFixed - i.y
let c2 = Math.sqrt(Math.pow(b2,2) + Math.pow(a2,2))
if( c < 90 ){
i.xVelocity = a/c * -20
i.yVelocity = b/c * -20
}else if(90 < c && c < 95){
i.xVelocity = a/c * -1
i.yVelocity = b/c * -1
}else if(c2 !== 0){
i.xVelocity =( a2/c2 )
i.yVelocity = (b2/c2 )
}
}
for(i of particleContainer){
i.update()
}
}
animate()
To get better rendering you need to add render objects to the same path. Once the path is created then you can draw them in one call to ctx.fill
Try to limit accessing innerWidth and innerHeight as they are very slow DOM objects that may cause reflows just by accessing them.
Further improvements can be made by using object pools and pre-allocation but that is beyond the scope of a single answer.
Make the following changes to your animate function.
var W = 1, H = 1;
function animate() {
requestAnimationFrame(animate);
c.clearRect(0 ,0, W, H)
if (H !== innerHeight || W !== innerWidth) {
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
}
c.beginPath(); // start a new path
c.fillStyle = "white";
for (i of particleContainer) { // update and draw all particles in one pass
const a = mouse.x - i.x, b = mouse.y - i.y
const dist = (b * b + a * a) ** 0.5;
const a2 = i.xFixed - i.x, b2 = i.yFixed - i.y
const dist2 = (b2 * b2 + a2 * a2) ** 0.5;
if (dist < 90 ){
i.x += a / dist * -20
i.y += b / dist * -20
} else if (90 < dist && dist < 95){
i.x += a / dist * -1
i.y += b / dist * -1
} else if (dist2 !== 0){
i.x += (a2 / dist2 )
i.y += (b2 / dist2 )
}
c.rect(i.x, i.y, 1, 1);
}
c.fill(); // path complete render it.
//for(i of particleContainer){ // no longer needed
// i.update()
//}
}
Learn to use the Performance tab in dev tools and you can see which functions are taking the most time. In this case I think you’ll see that it’s ctx.fill. The example you posted is writing pixels into an ImageData buffer which will be much faster than drawing and filling arcs. There are a lot of other small optimisations in the example but that’s going to be the most important one, drawing is usually much slower than updating.
Have a very small snippet of an asteroids-like game I'm working on using only the DOM without Canvas. I have the "ship" moving pretty smoothly when arrow keys are pressed but how would I go about making the ship accelerate ( in speed and rotation ) when an arrow key is held down for a longer length of time?
window.onkeyup = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = false;
else if ( kc === 38 ) Keys.up = false;
else if ( kc === 39 ) Keys.right = false;
else if ( kc === 40 ) Keys.down = false;
};
function update() {
if ( Keys.up ) {
document.querySelector( 'div' ).style.transform += 'translateY( -1px )';
}
else if ( Keys.down ) {
document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
#import url( "https://fonts.googleapis.com/css?family=Nunito" );
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
b {
display: block;
transform: rotate( 180deg );
}
<div>
<b>v</b>
</div>
<script>
var Keys = {
up: false,
down: false,
left: false,
right: false
}
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = true;
else if ( kc === 38 ) Keys.up = true;
else if ( kc === 39 ) Keys.right = true;
else if ( kc === 40 ) Keys.down = true;
};
</script>
Use the arrow keys to control snippet.
Updated. As another similar question had a answer based on this previous version answer I have changed the answer to the better answer.
Transformations, Acceleration, Drag, and Rocket Ships.
There are so many ways to apply movement to a asteroids type game. This answer shows the most basic method and then gives an example showing variations on the basic methods to produce different feels. This answer also give a brief overview of how to set the CSS transformation using matrix (2D)
The basics
At the most basic you have a number that represents some component of the position or rotation. To move you add a constant x += 1; move in x one unit at a time when you let go of the control you don`t add and you stop.
But things don't move like that, they accelerate. So you create a second value that holds the speed (formerly the one from x += 1) and call it dx (delta X). When you get input you increase the speed by some small amount dx += 0.01 so that in time the speed increases gradually.
But the problem is the longer you hold the controls the faster you go, and when you let go of the controls the ship keeps going (which is all normal for space but a pain in a game) so you need to limit the speed and bring it gradually back down to zero. You can do that by applying a scale to the delta X value each frame. dx *= 0.99;
Thus you have the basic acceleration, drag, and speed limited value
x += dx;
dx *= 0.99;
if(input){ dx += 0.01);
Do that for both the x, y and angle. Where input is directional you need to use vectors for x, y as follows.
x += dx;
y += dy;
angle += dAngle;
dx *= 0.99;
dy *= 0.99;
dAngle *= 0.99;
if(turnLeft){
dAngle += 0.01;
}
if(turnRight){
dAngle -= 0.01;
}
if(inputMove){
dx += Math.cos(angle) * 0.01;
dy += Math.sin(angle) * 0.01;
}
That is the most basic space game movement.
Setting the CSS transform.
Setting the CSS transform is easiest apply via the matrix command. eg setting default transform element.style.transform = "matrix(1,0,0,1,0,0)";
The six values often named a,b,c,d,e 'matrix(a,b,c,d,e,f)' or m11, m12, m21, m22, m31, m32 or
horizontal scaling, horizontal skewing, vertical skewing, vertical scaling, horizontal moving, vertical moving and represent a shortened version of the 3 by 3 2D matrix.
I find that the majority of confusion about how this matrix works and why it is not often used is in part due to the naming of the variables. I prefer the description as x axis x, x axis y, y axis x, y axis y, origin x, origin y and simply describe the direction and scale of the x and y axis and the position of the origin all in CSS pixel coordinates.
The following image illustrates the matrix. The red box is a element that has been rotated 45 deg (Math.PI / 4 radians) and its origin moved to CSS pixel coordinated 16,16.
Image Grid shows CSS pixels. The right grid shows a zoomed view of the matrix showing the X Axis vector (a,b) = (cos(45), sin(45)), Y Axis vector (c,d) = (cos(45 + 90), sin(45 + 90)) and the Origin (e,f) = (16, 16)
Thus is I have the values for an element in terms of angle, position (x,y), scale (x,y). We then create the matrix as follows
var scale = { x : 1, y : 1 };
var pos = {x : 16, y : 16 };
var angle = Math.PI / 4; // 45 deg
var a,b,c,d,e,f; // the matrix arguments
// the x axis and x scale
a = Math.cos(angle) * scale.x;
b = Math.sin(angle) * scale.x;
// the y axis which is at 90 degree clockwise of the x axis
// and the y scale
c = -Math.sin(angle) * scale.y;
d = Math.cos(angle) * scale.y;
// and the origin
e = pos.x;
f = pos.y;
element.style.transform = "matrix("+[a,b,c,d,e,f].join(",")+")";
As most of the time we dont skew the transform and use a uniform scale we can shorten the code. I prefer to use a predefined array to help keep GC hits low.
const preDefinedMatrix = [1,0,0,1,0,0]; // define at start
// element is the element to set the CSS transform on.
// x,y the position of the elements local origin
// scale the scale of the element
// angle the angle in radians
function setElementTransform (element, x, y, scale, angle) {
var m = preDefinedMatrix;
m[3] = m[0] = Math.cos(angle) * scale;
m[2] = -(m[1] = Math.sin(angle) * scale);
m[4] = x;
m[5] = y;
element.style.transform = "matrix("+m.join(",")+")";
}
I use a slightly different function in the demo. ship.updatePos and uses the ship.pos and ship.displayAngle to set the transformation relative to the containing elements origin (top,left)
Note that the 3D matrix though a little more complex (includes the projection) is very similar to the 2D matrix, it describes the x,y, and z axis as 3 vectors each with 3 scalars (x,y,z) with the y axis at 90 deg to the x axis and the z axis at 90 deg to both the x and y axis and can be found with the cross product of the x dot y axis. The length of each axis is the scale and the origin is a point coordinate (x,y,z).
Demo:
The demo shows 4 5 variants. Use the keyboard 1,2,3,4,5 to select a ship (it will turn red) and use the arrow keys to fly. Basic up arrow you go, left right you turn.
The maths for each ship is in the object ship.controls
requestAnimationFrame(mainLoop);
const keys = {
ArrowUp : false,
ArrowLeft : false,
ArrowRight : false,
Digit1 : false,
Digit2 : false,
Digit3 : false,
Digit4 : false,
Digit5 : false,
event(e){
if(keys[e.code] !== undefined){
keys[e.code] = event.type === "keydown" ;
e.preventDefault();
}
},
}
addEventListener("keyup",keys.event);
addEventListener("keydown",keys.event);
focus();
const ships = {
items : [],
controling : 0,
add(ship){ this.items.push(ship) },
update(){
var i;
for(i = 0; i < this.items.length; i++){
if(keys["Digit" + (i+1)]){
if(this.controling !== -1){
this.items[this.controling].element.style.color = "green";
this.items[this.controling].hasControl = false;
}
this.controling = i;
this.items[i].element.style.color = "red";
this.items[i].hasControl = true;
}
this.items[i].updateUserIO();
this.items[i].updatePos();
}
}
}
const ship = {
element : null,
hasControl : false,
speed : 0,
speedC : 0, // chase value for speed limit mode
speedR : 0, // real value (real as in actual speed)
angle : 0,
angleC : 0, // as above
angleR : 0,
engSpeed : 0,
engSpeedC : 0,
engSpeedR : 0,
displayAngle : 0, // the display angle
deltaAngle : 0,
matrix : null, // matrix to create when instantiated
pos : null, // position of ship to create when instantiated
delta : null, // movement of ship to create when instantiated
checkInView(){
var bounds = this.element.getBoundingClientRect();
if(Math.max(bounds.right,bounds.left) < 0 && this.delta.x < 0){
this.pos.x = innerWidth;
}else if(Math.min(bounds.right,bounds.left) > innerWidth && this.delta.x > 0){
this.pos.x = 0;
}
if(Math.max(bounds.top,bounds.bottom) < 0 && this.delta.y < 0){
this.pos.y = innerHeight;
}else if( Math.min(bounds.top,bounds.bottom) > innerHeight && this.delta.y > 0){
this.pos.y = 0;
}
},
controls : {
oldSchool(){
if(this.hasControl){
if(keys.ArrowUp){
this.delta.x += Math.cos(this.angle) * 0.1;
this.delta.y += Math.sin(this.angle) * 0.1;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.001;
}
if(keys.ArrowRight){
this.deltaAngle += 0.001;
}
}
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.angle += this.deltaAngle;
this.displayAngle = this.angle;
this.delta.x *= 0.995;
this.delta.y *= 0.995;
this.deltaAngle *= 0.995;
},
oldSchoolDrag(){
if(this.hasControl){
if(keys.ArrowUp){
this.delta.x += Math.cos(this.angle) * 0.5;
this.delta.y += Math.sin(this.angle) * 0.5;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.01;
}
if(keys.ArrowRight){
this.deltaAngle += 0.01;
}
}
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.angle += this.deltaAngle;
this.delta.x *= 0.95;
this.delta.y *= 0.95;
this.deltaAngle *= 0.9;
this.displayAngle = this.angle;
},
speedster(){
if(this.hasControl){
if(keys.ArrowUp){
this.speed += 0.02;
}
if(keys.ArrowLeft){
this.deltaAngle -= 0.01;
}
if(keys.ArrowRight){
this.deltaAngle += 0.01;
}
}
this.speed *= 0.99;
this.deltaAngle *= 0.9;
this.angle += this.deltaAngle;
this.delta.x += Math.cos(this.angle) * this.speed;
this.delta.y += Math.sin(this.angle) * this.speed;
this.delta.x *= 0.95;
this.delta.y *= 0.95;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angle;
},
engineRev(){ // this one has a 3 control. Engine speed then affects acceleration.
if(this.hasControl){
if(keys.ArrowUp){
this.engSpeed = 3
}else{
this.engSpeed *= 0.9;
}
if(keys.ArrowLeft){
this.angle -= 0.1;
}
if(keys.ArrowRight){
this.angle += 0.1;
}
}else{
this.engSpeed *= 0.9;
}
this.engSpeedC += (this.engSpeed- this.engSpeedR) * 0.05;
this.engSpeedC *= 0.1;
this.engSpeedR += this.engSpeedC;
this.speedC += (this.engSpeedR - this.speedR) * 0.1;
this.speedC *= 0.4;
this.speedR += this.speedC;
this.angleC += (this.angle - this.angleR) * 0.1;
this.angleC *= 0.4;
this.angleR += this.angleC;
this.delta.x += Math.cos(this.angleR) * this.speedR * 0.1; // 0.1 reducing this as easier to manage speeds when values near pixel size and not 0.00umpteen0001
this.delta.y += Math.sin(this.angleR) * this.speedR * 0.1;
this.delta.x *= 0.99;
this.delta.y *= 0.99;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angleR;
},
speedLimiter(){
if(this.hasControl){
if(keys.ArrowUp){
this.speed = 15;
}else{
this.speed = 0;
}
if(keys.ArrowLeft){
this.angle -= 0.1;
}
if(keys.ArrowRight){
this.angle += 0.1;
}
}else{
this.speed = 0;
}
this.speedC += (this.speed - this.speedR) * 0.1;
this.speedC *= 0.4;
this.speedR += this.speedC;
this.angleC += (this.angle - this.angleR) * 0.1;
this.angleC *= 0.4;
this.angleR += this.angleC;
this.delta.x = Math.cos(this.angleR) * this.speedR;
this.delta.y = Math.sin(this.angleR) * this.speedR;
this.pos.x += this.delta.x;
this.pos.y += this.delta.y;
this.displayAngle = this.angleR;
}
},
updateUserIO(){
},
updatePos(){
this.checkInView();
var m = this.matrix;
m[3] = m[0] = Math.cos(this.displayAngle);
m[2] = -(m[1] = Math.sin(this.displayAngle));
m[4] = this.pos.x;
m[5] = this.pos.y;
this.element.style.transform = `matrix(${m.join(",")})`;
},
create(shape,container,xOff,yourRide){ // shape is a string
this.element = document.createElement("div")
this.element.style.position = "absolute";
this.element.style.top = this.element.style.left = "0px";
this.element.style.fontSize = "24px";
this.element.textContent = shape;
this.element.style.color = "green";
this.element.style.zIndex = 100;
container.appendChild(this.element);
this.matrix = [1,0,0,1,0,0];
this.pos = { x : innerWidth / 2 + innerWidth * xOff, y : innerHeight / 2 };
this.delta = { x : 0, y : 0};
this.updateUserIO = this.controls[yourRide];
return this;
}
}
var contain = document.createElement("div");
contain.style.position = "absolute";
contain.style.top = contain.style.left = "0px";
contain.style.width = contain.style.height = "100%";
contain.style.overflow = "hidden";
document.body.appendChild(contain);
window.focus();
ships.add(Object.assign({},ship).create("=Scl>",contain,-0.4,"oldSchool"));
ships.add(Object.assign({},ship).create("=Drg>",contain,-0.25,"oldSchoolDrag"));
ships.add(Object.assign({},ship).create("=Fast>",contain,-0.1,"speedster"));
ships.add(Object.assign({},ship).create("=Nimble>",contain,0.05,"speedLimiter"));
ships.add(Object.assign({},ship).create("=Rev>",contain,0.2,"engineRev"));
function mainLoop(){
ships.update();
requestAnimationFrame(mainLoop);
}
body {
font-family : verdana;
background : black;
color : #0F0;
}
Click to focus then keys 1, 2, 3, 4, 5 selects a ship. Arrow keys to fly. Best full page.
A zillion variants
There are many other variants and ways. I like the using a second derivative (first derivative dx/dt (dt is time) from x += dx, second de/dt for engine power) that simulates an engine ramping up power and winding down which can give a very nice feel. Basicly its
x += dx;
dx += de;
dx *= 0.999;
de *= 0.99;
if(input){ de += 0.01 }
What is suited for your game is up to you, you don't have to follow the rules so try out different values and methods till you are happy.
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left++;
else if ( kc === 38 ) Keys.up++;
else if ( kc === 39 ) Keys.right++;
else if ( kc === 40 ) Keys.down++;
};
window.onkeyup = function(e)
{
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) {Keys.left = 0;}
else if ( kc === 38 ) Keys.up = 0;
else if ( kc === 39 ) Keys.right = 0;
else if ( kc === 40 ) Keys.down = 0;
}
function update() {
if ( Keys.up ) {
document.querySelector( 'div' ).style.transform += 'translateY( -'+Keys.up+'px )';
}
else if ( Keys.down ) {
document.querySelector( 'div' ).style.transform += 'translateY( '+Keys.down+'px )';
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -'+Keys.left+'deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( '+Keys.right+'deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
Very basic idea how to implement acceleration:
Create speed variable and multiply them as you want.
Example of acceleration only for "UP" key
var speed = 1.0;
window.onkeyup = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = false;
else if ( kc === 38 ) Keys.up = false;
else if ( kc === 39 ) Keys.right = false;
else if ( kc === 40 ) Keys.down = false;
};
function update() {
if ( Keys.up ) {
speed = speed *1.01;
document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
}
else if ( Keys.down ) {
if (speed>1)
{ speed = speed *0.9;
document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
}
else {
speed = 1;
document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
}
}
if ( Keys.left ) {
document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
}
else if ( Keys.right ) {
document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
}
requestAnimationFrame( update );
}
requestAnimationFrame( update );
#import url( "https://fonts.googleapis.com/css?family=Nunito" );
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
b {
display: block;
transform: rotate( 180deg );
}
<div>
<b>v</b>
</div>
<script>
var Keys = {
up: false,
down: false,
left: false,
right: false
}
window.onkeydown = function( e ) {
var kc = e.keyCode;
e.preventDefault();
if ( kc === 37 ) Keys.left = true;
else if ( kc === 38 ) Keys.up = true;
else if ( kc === 39 ) Keys.right = true;
else if ( kc === 40 ) Keys.down = true;
};
</script>
I'm trying to get my OVERLAY tag to appear on top of my canvas javascript. I've gone through all the questions on here but nothing has worked!
Please help! Code:
page.html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id='container'>
<canvas id='canvas'></canvas>
<script src="test-script.js"></script>
<div id='overlay'>OVERLAY
<br></br>
OVERLAY
<br></br>
OVERLAY
</div>
</div>
</body>
</html>
style.css
#canvas {position: fixed; z-index: -1;}
#overlay {margin-top: -50px; z-index:0; position: relative;}
test-script.js
var ns = ns || {};
(function draw() {
var c;
var ctx;
var trails = [];
document.body.onload = function() {
c = document.getElementById( 'canvas' );
c.width = 2000;
c.height = 2000;
document.body.appendChild( c );
ctx = c.getContext( "2d" );
trails.push( new ns.trailer( [990000, 990000, 990000, 600000, 600000 ]));
// trails.push( new ns.trailer( [ 600000,600000,600000,600000,600000,600000,600000 ] ));
trails.push( new ns.trailer( [ 8000000, 8000000, 8000000, 990000, 990000 ] ));
document.onmousedown = reset;
reset();
setInterval( compute, 0 );
}
function reset() {
ctx.fillStyle = "white";
ctx.fillRect( 0,0,c.width,c.height );
for( var i =0; i < trails.length; i++ ) {
trails[ i ].reset();
}
}
function compute() {
for( var i =0; i < trails.length; i++ ) {
trails[ i ].compute( ctx );
}
}
})();
ns.trailer = function( colors ) {
this.points = [];
this.stroke = new ns.stroke( null, 100, 10, colors[ 0 ] );
this.colorIterator = 10;
this.colors = colors;
}
ns.trailer.prototype = {
reset : function() {
this.points = [];
this.width = document.body.offsetWidth;
this.height = document.body.offsetHeight;
this.radius = Math.max( this.width, this.height );
this.center = new ns.point( this.width / 2, this.height / 2 );
this.a0 = Math.random() * Math.PI * 2;
this.a1 = Math.random() * Math.PI * 2;
this.a2 = Math.random() * Math.PI * 2;
var mul = 1 + Math.random() * 2;
if( Math.random() > .5 ) mul *= 5;
else mul /= 2;
this.s0 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s1 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s2 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
},
compute : function( ctx ) {
with( this ) {
a0 += s0;
a1 += s1;
a2 += s2;
var c = Math.cos( a0 ) * Math.cos( a1 ) * Math.cos( a2 );
var s = Math.sin( a0 ) * Math.sin( a1 ) * Math.sin( a2 );
points.push( new ns.point( center.x + c * radius,
center.y + s * radius ) );
if( points.length > 10 ) points.shift();
stroke.anchors = points;
stroke.draw( ctx );
var t = .5 + (Math.sin( new Date().getTime() * .001 ) * .5 );
stroke.color = colors[ Math.floor( t * colors.length ) ];
stroke.width = 25 + ( 1 - t ) * 50;
//stroke.strokeCount = 5 + t * 5;
stroke.strokeCount = 5;
}
}
}
ns.point = function( x,y ) {
this.x = x;
this.y = y;
}
ns.point.prototype = {
add : function( p ) {
return new ns.point( this.x + p.x, this.y + p.y );
}.
sub : function( p ) {
return new ns.point( this.x - p.x, this.y - p.y );
},
negate : function() {
this.x *= -1;
this.y *= -1;
return this;
},
clone : function() {
return new ns.point( this.x, this.y );
},
length : function() {
return Math.sqrt( this.x * this.x + this.y * this.y );
},
normalize : function ( scale ) {
scale = scale || 1;
var l = this.length();
this.x /= l;
this.x *= scale;
this.y /= l;
this.y *= scale;
return this;
}
}
ns.stroke = function( anchors, width, strokeCount, color ) {
this.anchors = anchors;
this.width = width;
this.strokeCount = strokeCount;
this.color = color;
}
ns.stroke.prototype = {
normal : function( p0, p1 ){
return new ns.point( -( p1.y - p0.y ), ( p1.x - p0.x ) );
},
draw : function( ctx ) {
if( this.anchors == undefined ) return;
var half = this.height * .5;
var p, c, n, pnorm, pln, prn, cnorm, cln, crn;
with( this ) {
for( var j = 0; j < strokeCount; j++ ) {
half = width * .5 * Math.random();
var col = ns.variation( color, 35 );
ctx.lineWidth = .1 + Math.random() * 2;
for( var i = 0; i < anchors.length - 2; i++ ) {
p = anchors[ i ];
c = anchors[ i+1 ];
n = anchors[ i+2 ];
pnorm = normal( p, c );
cnorm = normal( c, n );
half += ( Math.random() - .5 );
pnorm.normalize( half );
pln = p.add( pnorm );
pnorm.normalize( -half );
prn = p.add( pnorm );
half += ( Math.random() - .5 );
cnorm.normalize( half );
cln = c.add( cnorm );
cnorm.normalize( -half );
crn = c.add( cnorm );
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( prn.x, prn.y );
ctx.lineTo( crn.x, crn.y );
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( pln.x, pln.y );
ctx.lineTo( cln.x, cln.y );
ctx.stroke();
ctx.closePath();
}
}
}
}
}
ns.variation = function( color, amount ) {
amount = amount || 25;
var r = color >> 16 & 0xFF;
var g = color >> 8 & 0xFF;
var b = color & 0xFF;
r += Math.floor( ( Math.random() - .5 ) * amount );
g += Math.floor( ( Math.random() - .5 ) * amount );
b += Math.floor( ( Math.random() - .5 ) * amount );
r = r > 0xFF ? 0xFF : r < 0 ? 0 : r;
g = g > 0xFF ? 0xFF : g < 0 ? 0 : g;
b = b > 0xFF ? 0xFF : b < 0 ? 0 : b;
return "rgba("+r+','+g+','+b+','+Math.random()+');';
}
**I've added my Javascript code
You need to use absolute position. Also mention width and height to 100%. z-index should be higher to place element over other elements.
#canvas {
position: fixed;
}
#overlay {
z-index: 9;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
}
<div id='container'>
<canvas id='canvas'></canvas>
<div id='overlay'>OVERLAY
<br>OVERLAY
<br>OVERLAY
</div>
</div>
CSS
#container {
position: relative;
}
#overlay {
position:absolute;
top:50px;
left:150px;
z-index:10;
}
Adjust the "top" and "left" amounts to get OVERLAY positioned on top of the canvas.
It was the JavaScript.
Run my code snippet.
var ns = ns || {};
(function draw() {
var c;
var ctx;
var trails = [];
document.body.onload = function() {
c = document.getElementById( 'canvas' );
c.width = 2000;
c.height = 2000;
document.body.appendChild( c );
ctx = c.getContext( "2d" );
trails.push( new ns.trailer( [990000, 990000, 990000, 600000, 600000 ]));
// trails.push( new ns.trailer( [ 600000,600000,600000,600000,600000,600000,600000 ] ));
trails.push( new ns.trailer( [ 8000000, 8000000, 8000000, 990000, 990000 ] ));
document.onmousedown = reset;
reset();
setInterval( compute, 0 );
};
function reset() {
ctx.fillStyle = "white";
ctx.fillRect( 0,0,c.width,c.height );
for( var i =0; i < trails.length; i++ ) {
trails[ i ].reset();
}
}
function compute() {
for( var i =0; i < trails.length; i++ ) {
trails[ i ].compute( ctx );
}
}
})();
ns.trailer = function( colors ) {
this.points = [];
this.stroke = new ns.stroke( null, 100, 10, colors[ 0 ] );
this.colorIterator = 10;
this.colors = colors;
};
ns.trailer.prototype = {
reset : function() {
this.points = [];
this.width = document.body.offsetWidth;
this.height = document.body.offsetHeight;
this.radius = Math.max( this.width, this.height );
this.center = new ns.point( this.width / 2, this.height / 2 );
this.a0 = Math.random() * Math.PI * 2;
this.a1 = Math.random() * Math.PI * 2;
this.a2 = Math.random() * Math.PI * 2;
var mul = 1 + Math.random() * 2;
if( Math.random() > .5 ) mul *= 5;
else mul /= 2;
this.s0 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s1 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s2 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
},
compute : function( ctx ) {
with( this ) {
a0 += s0;
a1 += s1;
a2 += s2;
var c = Math.cos( a0 ) * Math.cos( a1 ) * Math.cos( a2 );
var s = Math.sin( a0 ) * Math.sin( a1 ) * Math.sin( a2 );
points.push( new ns.point( center.x + c * radius,
center.y + s * radius ) );
if( points.length > 10 ) points.shift();
stroke.anchors = points;
stroke.draw( ctx );
var t = .5 + (Math.sin( new Date().getTime() * .001 ) * .5 );
stroke.color = colors[ Math.floor( t * colors.length ) ];
stroke.width = 25 + ( 1 - t ) * 50;
//stroke.strokeCount = 5 + t * 5;
stroke.strokeCount = 5;
}
}
};
ns.point = function( x,y ) {
this.x = x;
this.y = y;
};
ns.point.prototype = {
add : function( p ) {
return new ns.point( this.x + p.x, this.y + p.y );
},
sub : function( p ) {
return new ns.point( this.x - p.x, this.y - p.y );
},
negate : function() {
this.x *= -1;
this.y *= -1;
return this;
},
clone : function() {
return new ns.point( this.x, this.y );
},
length : function() {
return Math.sqrt( this.x * this.x + this.y * this.y );
},
normalize : function ( scale ) {
scale = scale || 1;
var l = this.length();
this.x /= l;
this.x *= scale;
this.y /= l;
this.y *= scale;
return this;
}
};
ns.stroke = function( anchors, width, strokeCount, color ) {
this.anchors = anchors;
this.width = width;
this.strokeCount = strokeCount;
this.color = color;
};
ns.stroke.prototype = {
normal : function( p0, p1 ){
return new ns.point( -( p1.y - p0.y ), ( p1.x - p0.x ) );
},
draw : function( ctx ) {
if( this.anchors === undefined ) return;
var half = this.height * .5;
var p, c, n, pnorm, pln, prn, cnorm, cln, crn;
with( this ) {
for( var j = 0; j < strokeCount; j++ ) {
half = width * .5 * Math.random();
var col = ns.variation( color, 35 );
ctx.lineWidth = .1 + Math.random() * 2;
for( var i = 0; i < anchors.length - 2; i++ ) {
p = anchors[ i ];
c = anchors[ i+1 ];
n = anchors[ i+2 ];
pnorm = normal( p, c );
cnorm = normal( c, n );
half += ( Math.random() - .5 );
pnorm.normalize( half );
pln = p.add( pnorm );
pnorm.normalize( -half );
prn = p.add( pnorm );
half += ( Math.random() - .5 );
cnorm.normalize( half );
cln = c.add( cnorm );
cnorm.normalize( -half );
crn = c.add( cnorm );
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( prn.x, prn.y );
ctx.lineTo( crn.x, crn.y );
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( pln.x, pln.y );
ctx.lineTo( cln.x, cln.y );
ctx.stroke();
ctx.closePath();
}
}
}
}
};
ns.variation = function( color, amount ) {
amount = amount || 25;
var r = color && 16 && 0xFF;
var g = color && 8 && 0xFF;
var b = color && 0xFF;
r += Math.floor( ( Math.random() - .5 ) * amount );
g += Math.floor( ( Math.random() - .5 ) * amount );
b += Math.floor( ( Math.random() - .5 ) * amount );
r = r > 0xFF ? 0xFF : r < 0 ? 0 : r;
g = g > 0xFF ? 0xFF : g < 0 ? 0 : g;
b = b > 0xFF ? 0xFF : b < 0 ? 0 : b;
return "rgba("+r+','+g+','+b+','+Math.random()+');';
};
#container {
position: relative;
}
#overlay {
position:absolute;
top:50px;
left:150px;
z-index:10;
}
<body>
<div id='container'>
<div id='overlay'>
<h1>
OVERLAY
</h1>
</div>
<canvas id='canvas'>
</canvas>
</div>
<!-- scripts -->
<script type="text/javascript" src="test-script.js"></script>
</body>
I'm searching the web for quite sometime for the name of this effect... I've seen it on many sites but can't find a guide or a name to look for it and learn how to do it.
The effect I'm talking about is in this website header:
http://diogodantas.com/demo/elegant-index#0
check this. i just ctrl + c,v.
(function() {
var width, height, largeHeader, canvas, ctx, points, target, animateHeader = true;
// Main
initHeader();
initAnimation();
addListeners();
function initHeader() {
width = window.innerWidth;
height = window.innerHeight;
target = {x: width/2, y: height/2};
largeHeader = document.getElementById('large-header');
largeHeader.style.height = height+'px';
canvas = document.getElementById('demo-canvas');
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
// create points
points = [];
for(var x = 0; x < width; x = x + width/20) {
for(var y = 0; y < height; y = y + height/20) {
var px = x + Math.random()*width/20;
var py = y + Math.random()*height/20;
var p = {x: px, originX: px, y: py, originY: py };
points.push(p);
}
}
// for each point find the 5 closest points
for(var i = 0; i < points.length; i++) {
var closest = [];
var p1 = points[i];
for(var j = 0; j < points.length; j++) {
var p2 = points[j]
if(!(p1 == p2)) {
var placed = false;
for(var k = 0; k < 5; k++) {
if(!placed) {
if(closest[k] == undefined) {
closest[k] = p2;
placed = true;
}
}
}
for(var k = 0; k < 5; k++) {
if(!placed) {
if(getDistance(p1, p2) < getDistance(p1, closest[k])) {
closest[k] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for(var i in points) {
var c = new Circle(points[i], 2+Math.random()*2, 'rgba(255,255,255,0.3)');
points[i].circle = c;
}
}
// Event handling
function addListeners() {
if(!('ontouchstart' in window)) {
window.addEventListener('mousemove', mouseMove);
}
window.addEventListener('scroll', scrollCheck);
window.addEventListener('resize', resize);
}
function mouseMove(e) {
var posx = posy = 0;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if(document.body.scrollTop > height) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
largeHeader.style.height = height+'px';
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for(var i in points) {
shiftPoint(points[i]);
}
}
function animate() {
if(animateHeader) {
ctx.clearRect(0,0,width,height);
for(var i in points) {
// detect points in range
if(Math.abs(getDistance(target, points[i])) < 4000) {
points[i].active = 0.3;
points[i].circle.active = 0.6;
} else if(Math.abs(getDistance(target, points[i])) < 20000) {
points[i].active = 0.1;
points[i].circle.active = 0.3;
} else if(Math.abs(getDistance(target, points[i])) < 40000) {
points[i].active = 0.02;
points[i].circle.active = 0.1;
} else {
points[i].active = 0;
points[i].circle.active = 0;
}
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
}
function shiftPoint(p) {
TweenLite.to(p, 1+1*Math.random(), {x:p.originX-50+Math.random()*100,
y: p.originY-50+Math.random()*100, ease:Circ.easeInOut,
onComplete: function() {
shiftPoint(p);
}});
}
// Canvas manipulation
function drawLines(p) {
if(!p.active) return;
for(var i in p.closest) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.closest[i].x, p.closest[i].y);
ctx.strokeStyle = 'rgba(156,217,249,'+ p.active+')';
ctx.stroke();
}
}
function Circle(pos,rad,color) {
var _this = this;
// constructor
(function() {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
})();
this.draw = function() {
if(!_this.active) return;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(156,217,249,'+ _this.active+')';
ctx.fill();
};
}
// Util
function getDistance(p1, p2) {
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
}
})();
I still cannot find a name but here is a link
https://codepen.io/thetwistedtaste/pen/GgrWLp
/*
*
* TERMS OF USE -
*
* Open source under the BSD License.
*
* Copyright © 2001 Robert Penner
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
$( function() {
var width, height, canvas, ctx, points, target, animateHeader = true;
// Main
initHeader();
initAnimation();
addListeners();
function initHeader() {
width = window.innerWidth;
height = window.innerHeight;
target = {
x: width / 2,
y: height / 3
};
canvas = document.getElementById( 'spiders' );
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext( '2d' );
// create points
points = [];
for ( var x = 0; x < width; x = x + width / 20 ) {
for ( var y = 0; y < height; y = y + height / 20 ) {
var px = x + Math.random() * width / 20;
var py = y + Math.random() * height / 20;
var p = {
x: px,
originX: px,
y: py,
originY: py
};
points.push( p );
}
}
// for each point find the 5 closest points
for ( var i = 0; i < points.length; i++ ) {
var closest = [];
var p1 = points[ i ];
for ( var j = 0; j < points.length; j++ ) {
var p2 = points[ j ]
if ( !( p1 == p2 ) ) {
var placed = false;
for ( var k = 0; k < 5; k++ ) {
if ( !placed ) {
if ( closest[ k ] == undefined ) {
closest[ k ] = p2;
placed = true;
}
}
}
for ( var k = 0; k < 5; k++ ) {
if ( !placed ) {
if ( getDistance( p1, p2 ) < getDistance( p1, closest[ k ] ) ) {
closest[ k ] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for ( var i in points ) {
var c = new Circle( points[ i ], 2 + Math.random() * 2, 'rgba(255,255,255,0.3)' );
points[ i ].circle = c;
}
}
// Event handling
function addListeners() {
if ( !( 'ontouchstart' in window ) ) {
window.addEventListener( 'mousemove', mouseMove );
}
window.addEventListener( 'scroll', scrollCheck );
window.addEventListener( 'resize', resize );
}
function mouseMove( e ) {
var posx = posy = 0;
if ( e.pageX || e.pageY ) {
posx = e.pageX;
posy = e.pageY;
} else if ( e.clientX || e.clientY ) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if ( document.body.scrollTop > height ) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for ( var i in points ) {
shiftPoint( points[ i ] );
}
}
function animate() {
if ( animateHeader ) {
ctx.clearRect( 0, 0, width, height );
for ( var i in points ) {
// detect points in range
if ( Math.abs( getDistance( target, points[ i ] ) ) < 2000 ) {
points[ i ].active = 0.2;
points[ i ].circle.active = 0.5;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 20000 ) {
points[ i ].active = 0.1;
points[ i ].circle.active = 0.3;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 70000 ) {
points[ i ].active = 0.02;
points[ i ].circle.active = 0.09;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 140000 ) {
points[ i ].active = 0;
points[ i ].circle.active = 0.02;
} else {
points[ i ].active = 0;
points[ i ].circle.active = 0;
}
drawLines( points[ i ] );
points[ i ].circle.draw();
}
}
requestAnimationFrame( animate );
}
function shiftPoint( p ) {
TweenLite.to( p, 1 + 1 * Math.random(), {
x: p.originX - 50 + Math.random() * 100,
y: p.originY - 50 + Math.random() * 100,
onComplete: function() {
shiftPoint( p );
}
} );
}
// Canvas manipulation
function drawLines( p ) {
if ( !p.active ) return;
for ( var i in p.closest ) {
ctx.beginPath();
ctx.moveTo( p.x, p.y );
ctx.lineTo( p.closest[ i ].x, p.closest[ i ].y );
ctx.strokeStyle = 'rgba(255,255,255,' + p.active + ')';
ctx.stroke();
}
}
function Circle( pos, rad, color ) {
var _this = this;
// constructor
( function() {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
} )();
this.draw = function() {
if ( !_this.active ) return;
ctx.beginPath();
ctx.arc( _this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false );
ctx.fillStyle = 'rgba(255,255,255,' + _this.active + ')';
ctx.fill();
};
}
// Util
function getDistance( p1, p2 ) {
return Math.pow( p1.x - p2.x, 2 ) + Math.pow( p1.y - p2.y, 2 );
}
} );
body {}
i {
position: absolute;
top:0; bottom:0;left:0;right:0;
display:block;
background:black;
z-index:-1;
}
canvas#spiders {
height:100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenLite.min.js"></script><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<i></i>
<canvas id="spiders" class="hidden-xs" width="1280" height="451"></canvas>
I just started playing around with three.js and I'm having some trouble modifying the color of individual sprites within an array.
I'm working with the example found here, from threejs.org.
I'm attempting to modify each sprite's color based on its scale value. In particular, I added the following line to the last function, from the source file.
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
var i = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
particle = particles[ i++ ];
particle.position.y = ( Math.sin( ( ix + count ) * 0.3 ) * 50 ) +
( Math.sin( ( iy + count ) * 0.5 ) * 50 );
particle.scale.x = particle.scale.y = ( Math.sin( ( ix + count ) * 0.3 ) + 1 ) * 4 +
( Math.sin( ( iy + count ) * 0.5 ) + 1 ) * 4;
// Added this line in an attempt to change color based on scale //
particle.material.color.setHSL(particle.scale.x * .1, .2, .2);
}
}
renderer.render( scene, camera );
count += 0.1;
}
However, the added line changes every particle to the same color values. I'd assumed that each element of the array would be accessed and modified within the loop, but it doesn't seem to be the case.
The material is being shared with all the particles,
var material = new THREE.SpriteCanvasMaterial( {
color: 0xffffff,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
Move this above the loop where the sprites particles are created so they get a separate material each.
The answer about each sprite having its own material sent me in the right direction. I set material to an array and added an association between each material and sprite element.
var i = 0;
var j = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
material[ j ] = new THREE.SpriteCanvasMaterial( {
vertexColors: THREE.VertexColors,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
particle = particles[ i ++ ] = new THREE.Sprite( material[ j ++ ] );
particle.position.x = ix * SEPARATION - ( ( AMOUNTX * SEPARATION ) / 2 );
particle.position.z = iy * SEPARATION - ( ( AMOUNTY * SEPARATION ) / 2 );
scene.add( particle );
}
}