Lowering CPU consumption of animated particles inside the canvas - javascript

I'm using this library zepto.min.js to add some particles over a canvas.
Here is the result on Codepen:
https://codepen.io/pixy-dixy/pen/JjGXQOo?editors=1111
I wonder if there is an option to:
control the number of particles inside the page
slow down the movements of the particles (velocity seems to not work properly here)
I want these especially for lowering CPU consumption.
Any recommendations will be greatly appreciated.
Here is the code:
(function ($, window) {
var $window = $(window);
function Constellation (canvas, options) {
var $canvas = $(canvas),
context = canvas.getContext('2d'),
defaults = {
star: {
color: 'rgba(255, 255, 255, .5)',
width: 1,
randomWidth: true
},
position: {
x: 0,
y: 0
},
width: window.innerWidth,
height: window.innerHeight,
velocity: 0.1,
length: 100,
distance: 120,
radius: 150,
stars: []
},
config = $.extend(true, {}, defaults, options);
function Star () {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (config.velocity - (Math.random() * 0.5));
this.vy = (config.velocity - (Math.random() * 0.5));
this.radius = config.star.randomWidth ? (Math.random() * config.star.width) : config.star.width;
}
Star.prototype = {
create: function(){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
context.fill();
},
animate: function(){
var i;
for (i = 0; i < config.length; i++) {
var star = config.stars[i];
if (star.y < 0 || star.y > canvas.height) {
star.vx = star.vx;
star.vy = - star.vy;
} else if (star.x < 0 || star.x > canvas.width) {
star.vx = - star.vx;
star.vy = star.vy;
}
star.x += star.vx;
star.y += star.vy;
}
},
};
this.createStars = function () {
var length = config.length,
star,
i;
context.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0; i < length; i++) {
config.stars.push(new Star());
star = config.stars[i];
star.create();
}
star.animate();
};
this.setCanvas = function () {
canvas.width = config.width;
canvas.height = config.height;
};
this.setContext = function () {
context.fillStyle = config.star.color;
context.strokeStyle = config.line.color;
context.lineWidth = config.line.width;
};
this.setInitialPosition = function () {
if (!options || !options.hasOwnProperty('position')) {
config.position = {
x: canvas.width * 0.5,
y: canvas.height * 0.5
};
}
};
this.loop = function (callback) {
callback();
this.rAF = window.requestAnimationFrame(function () {
this.loop(callback);
}.bind(this));
};
this.handlers = {
window: {
mousemove: function(e){
config.position.x = e.pageX - $canvas.offset().left;
config.position.y = e.pageY - $canvas.offset().top;
},
resize: function () {
window.cancelAnimationFrame(this.rAF);
}
}
};
this.bind = function () {
$window
.on('resize', this.handlers.window.resize.bind(this));
};
this.unbind = function () {
$window
.off('resize', this.handlers.window.resize);
}
this.init = function () {
this.setCanvas();
this.setContext();
this.setInitialPosition();
this.loop(this.createStars);
this.bind();
};
}
function instantiate(element, options) {
var c = new Constellation(element, options);
c.init();
}
$.fn.constellation = function (options) {
return this.each(function () {
$window.on('resize', () => {
instantiate(this, options);
});
instantiate(this, options);
});
};
})($, window);
// Init plugin
$('.constellation').constellation({
star: {
width: 1
},
line: {
color: 'rgba(255, 255, 255, .5)'
},
length: (window.innerWidth / 6),
radius: (window.innerWidth / 5)
});
body {
overflow: hidden;
background: #111;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.0/zepto.min.js"></script>
<canvas class="constellation" data-radius="10" width="1000" height="500"></canvas>

Related

Can't repeat js function in same page

Please be kind I'm a real beginner.
I'm trying to use this function twice on the same page (in different sections) but am unable to do this. I assume I somehow need to label it differently each time I use it, but can't figure out where.
Maybe the functions are not labeled correctly but I have tried trying this.
Here is the JS
(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);
};
})();
var Nodes = {
// Settings
density: 20,
reactionSensitivity: 10,
points: [],
lines: [[], [], [], []],
mouse: { x: 10, y: 10, down: false },
animation: null,
canvas: null,
context: null,
init: function () {
// Set up the visual canvas
this.canvas = document.getElementById("stripes2");
this.context = this.canvas.getContext("2d");
this.context.lineJoin = "bevel";
this.canvas.width = window.innerHeight*2;
this.canvas.height = window.innerHeight;
this.canvas.style.display = "block";
this.canvas.addEventListener("mousemove", this.mouseMove, false);
this.canvas.addEventListener("mousedown", this.mouseDown, false);
this.canvas.addEventListener("mouseup", this.mouseUp, false);
this.canvas.addEventListener("mouseout", this.mouseOut, false);
window.onresize = function (event) {
Nodes.canvas.width = (window.innerHeight*2);
Nodes.canvas.height = Math.max(window.innerHeight, 1000);
Nodes.onWindowResize();
};
this.preparePoints();
this.draw();
},
preparePoints: function () {
// Clear the current points
this.points = [];
this.lines = [[], [], [], [], []];
var width, height, i;
var center = window.innerHeight / 2;
for (i = -1000; i < this.canvas.width + 1000; i += this.density) {
var line1 = { y: center - 17, originalY: center - -100, color: "#249FA4" };
var line2 = { y: center - 17, originalY: center - -50, color: "#3DB8B5" };
var line3 = { y: center - 17, originalY: center - 0, color: "#F8DCAA" };
var line4 = { y: center - 17, originalY: center - 50, color: "#F1B30A" };
var line5 = { y: center - 17, originalY: center - 100, color: "#E77419" };
line1["x"] = i;
line1["originalX"] = i;
line2["x"] = i;
line2["originalX"] = i;
line3["x"] = i;
line3["originalX"] = i;
line4["x"] = i;
line4["originalX"] = i;
line5["x"] = i;
line5["originalX"] = i;
this.points.push(line1);
this.points.push(line2);
this.points.push(line3);
this.points.push(line4);
this.points.push(line5);
this.lines[0].push(line1);
this.lines[1].push(line2);
this.lines[2].push(line3);
this.lines[3].push(line4);
this.lines[4].push(line5);
}
},
updatePoints: function () {
var i, currentPoint, theta, distance;
for (i = 0; i < this.points.length; i++) {
currentPoint = this.points[i];
theta = Math.atan2(
currentPoint.y - this.mouse.y,
currentPoint.x - this.mouse.x
);
if (this.mouse.down) {
distance =
(this.reactionSensitivity * 300) /
Math.sqrt(
(this.mouse.x - currentPoint.x) * (this.mouse.x - currentPoint.x) +
(this.mouse.y - currentPoint.y) * (this.mouse.y - currentPoint.y)
);
} else {
distance =
(this.reactionSensitivity * 150) /
Math.sqrt(
(this.mouse.x - currentPoint.x) * (this.mouse.x - currentPoint.x) +
(this.mouse.y - currentPoint.y) * (this.mouse.y - currentPoint.y)
);
}
currentPoint.x +=
Math.cos(theta) * distance +
(currentPoint.originalX - currentPoint.x) * 0.07;
currentPoint.y +=
Math.sin(theta) * distance +
(currentPoint.originalY - currentPoint.y) * 0.07;
}
},
drawPoints: function () {
var i, currentPoint;
for (i = 0; i < 5; i++) {
var line = this.lines[i];
this.context.beginPath();
this.context.lineJoin = "round";
this.context.moveTo(line[0].x, line[0].y);
this.context.strokeStyle = line[0].color;
this.context.lineWidth = 50;
for (var j = 1; j < line.length - 2; j++) {
var point = line[j];
var xc = (point.x + line[j + 1].x) / 2;
var yc = (point.y + line[j + 1].y) / 2;
this.context.quadraticCurveTo(point.x, point.y, xc, yc);
}
this.context.stroke();
this.context.closePath();
}
},
draw: function () {
this.animation = requestAnimationFrame(function () {
Nodes.draw();
});
this.clear();
this.updatePoints();
this.drawPoints();
},
clear: function () {
this.canvas.width = this.canvas.width;
},
mouseDown: function (event) {
Nodes.mouse.down = true;
},
mouseUp: function (event) {
Nodes.mouse.down = false;
},
mouseMove: function (event) {
Nodes.mouse.x = event.pageY;
Nodes.mouse.y = event.pageY;
},
mouseOut: function (event) {
Nodes.mouse.x = -1000;
Nodes.mouse.y = -1000;
Nodes.mouse.down = false;
},
// Resize and redraw the canvas.
onWindowResize: function () {
cancelAnimationFrame(this.animation);
this.preparePoints();
this.draw();
}
};
// Start the app.
setTimeout(function () {
Nodes.init();
}, 10);
<canvas id="stripes2" class="container">
Many thanks

Canvas particle animation mobile touch event with elements positioned on top

I have this canvas particle animation that follows the mouse event correctly but it does not follow a touch event on mobile. I realized that it's because I have this animation beneath the rest of the content which is positioned on top. When there is something on top of the canvas that it's hitting, it does not trigger the touch event. I'm hoping someone can help me figure out how to avoid this issue so that the animation will still follow the user underneath the page content.
JS
var PI2 = Math.PI * 2;
var HALF_PI = Math.PI / 2;
var isTouch = 'ontouchstart' in window;
var isSafari = !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/);
function Canvas(options) {
options = _.clone(options || {});
this.options = _.defaults(options, this.options);
this.el = this.options.el;
this.ctx = this.el.getContext('2d');
this.dpr = window.devicePixelRatio || 1;
this.updateDimensions();
window.addEventListener('resize', this.updateDimensions.bind(this), false);
this.resetTarget();
if(isTouch){
// touch
this.el.addEventListener('touchstart', this.touchMove.bind(this), false);
this.el.addEventListener('touchmove', this.touchMove.bind(this), false);
// this.el.addEventListener('touchend', this.resetTarget.bind(this), false);
} else {
// Mouse
window.addEventListener('mousemove', this.mouseMove.bind(this), false);
window.addEventListener('mouseout', this.resetTarget.bind(this), false);
}
this.setupParticles();
this.loop();
}
Canvas.prototype.updateDimensions = function() {
this.width = this.el.width = _.result(this.options, 'width') * this.dpr;
this.height = this.el.height = _.result(this.options, 'height') * this.dpr;
this.el.style.width = _.result(this.options, 'width') + 'px';
this.el.style.height = _.result(this.options, 'height') + 'px';
}
// Update the orb target
Canvas.prototype.mouseMove = function(event) {
this.target = new Vector(event.clientX * this.dpr, event.clientY* this.dpr);
}
// Reset to center when we mouse out
Canvas.prototype.resetTarget = function() {
this.target = new Vector(this.width / 2, this.height /2);
}
// Touch Eent
Canvas.prototype.touchMove = function(event) {
if(event.touches.length === 1) { event.preventDefault(); }
this.target = new Vector(event.touches[0].pageX * this.dpr, event.touches[0].pageY * this.dpr);
}
// Defaults
Canvas.prototype.options = {
count: 11,
speed: 0.001,
width: 400,
height: 400,
size: 5,
radius: 1,
background: '240, 240, 240, 0.6',
maxDistance: 100
}
Canvas.prototype.setupParticles = function() {
this.particles = [];
var index = -1;
var between = PI2 / this.options.count;
while(++index < this.options.count) {
var x;
var y;
var angle;
var max = Math.max(this.width, this.height);
angle = (index + 1) * between;
x = Math.cos(angle) * max;
x += this.width / 2;
y = Math.sin(angle) * max;
y += this.height / 2;
var particle = new Particle({
x: x,
y: y,
radius: this.options.radius,
size: this.options.size,
angle: angle,
color: this.options.color
});
this.particles.push(particle);
}
}
Canvas.prototype.findClosest = function() {
var index = -1;
var pointsLength = this.particles.length;
while(++index < pointsLength) {
var closestIndex = -1;
this.particles[index].closest = [];
while(++closestIndex < pointsLength) {
var closest = this.particles[closestIndex];
var distance = this.particles[index].position.distanceTo(closest.position);
if(distance < this.options.maxDistance) {
var vector = new Vector(closest.position.x, closest.position.y);
vector.opacity = 1 - (distance / this.options.maxDistance);
vector.distance = distance;
this.particles[index].closest.push(vector);
}
}
}
}
Canvas.prototype.loop = function() {
// this.clear();
if(isTouch || isSafari) {
this.ghost();
} else {
this.ghostGradient();
}
if(this.options.maxDistance > 0) {
this.findClosest();
}
this.draw();
window.requestAnimationFrame(_.bind(this.loop, this));
}
Canvas.prototype.clear = function() {
this.ctx.clearRect(0, 0 , this.width, this.height);
}
Canvas.prototype.ghost = function() {
this.ctx.globalCompositeOperation = "source-over";
this.ctx.rect(0, 0 , this.width, this.height);
if(typeof this.options.background === 'string') {
this.ctx.fillStyle = "rgba(" + this.options.background + ")";
} else {
this.ctx.fillStyle = "rgba(" + this.options.background[0] + ")";
}
this.ctx.fill();
}
Canvas.prototype.ghostGradient = function() {
var gradient;
if(typeof this.options.background === 'string') {
this.ctx.fillStyle = 'rgba(' + this.options.background + ')';
} else {
var gradient = this.ctx.createLinearGradient(0, 0, 0, this.height);
var length = this.options.background.length;
for(var i = 0; i < length; i++){
gradient.addColorStop((i+1) / length, 'rgba(' + this.options.background[i] + ')');
}
this.ctx.fillStyle = gradient;
}
this.ctx.globalOpacity = 0.1;
this.ctx.globalCompositeOperation = "darken";
this.ctx.fillRect(0, 0 , this.width, this.height);
}
// Draw
Canvas.prototype.draw = function() {
var index = -1;
var length = this.particles.length;
while(++index < length) {
var point = this.particles[index];
var color = point.color || this.options.color;
point.update(this.target, index);
this.ctx.globalAlpha = 0.3;
this.ctx.globalCompositeOperation = "lighten";
this.ctx.fillStyle = 'rgb(' + color + ')';
this.ctx.beginPath();
this.ctx.arc(point.position.x, point.position.y, point.size, 0, PI2, false);
this.ctx.closePath();
this.ctx.fill();
if(this.options.maxDistance > 0) {
this.drawLines(point, color);
}
}
}
// Draw connecting lines
Canvas.prototype.drawLines = function (point, color) {
color = color || this.options.color;
var index = -1;
var length = point.closest.length;
this.ctx.globalAlpha = 0.2;
this.ctx.globalCompositeOperation = "screen";
this.ctx.lineCap = 'round';
while(++index < length) {
this.ctx.lineWidth = (point.size * 2) * point.closest[index].opacity;
this.ctx.strokeStyle = 'rgba(250,250,250, ' + point.closest[index].opacity + ')';
this.ctx.beginPath();
this.ctx.moveTo(point.position.x, point.position.y);
this.ctx.lineTo(point.closest[index].x, point.closest[index].y);
this.ctx.stroke();
}
}
function Particle(options) {
options = _.clone(options || {});
this.options = _.defaults(options, this.options);
this.position = this.shift = new Vector(this.options.x, this.options.y);
this.speed = this.options.speed || 0.01 + Math.random() * 0.04;
this.angle = this.options.angle || 0;
if(this.options.color) {
var color = this.options.color.split(',');
var colorIndex = -1;
while(++colorIndex < 3) {
color[colorIndex] = Math.round(parseInt(color[colorIndex], 10) + (Math.random()*100)-50);
// Clamp
color[colorIndex] = Math.min(color[colorIndex], 255);
color[colorIndex] = Math.max(color[colorIndex], 0);
}
this.color = color.join(', ');
}
// Size
this.options.size = this.options.size || 7;
this.size = 1 + Math.random() * this.options.size;
this.targetSize = this.options.targetSize || this.options.size;
this.orbit = this.options.radius * 0.5 + (this.options.radius * 0.5 * Math.random());
}
Particle.prototype.update = function(target, index) {
this.angle += this.speed;
this.shift.x += (target.x - this.shift.x) * this.speed;
this.shift.y += (target.y - this.shift.y) * this.speed;
this.position.x = this.shift.x + Math.cos(index + this.angle) * this.orbit;
this.position.y = this.shift.y + Math.sin(index + this.angle) * this.orbit;
if(!isSafari) {
this.size += (this.targetSize - this.size) * 0.03;
if(Math.round(this.size) === Math.round(this.targetSize)) {
this.targetSize = 1 + Math.random() * this.options.size;
}
}
}
function Vector(x, y) {
this.x = x || 0;
this.y = y || 0;
}
Vector.prototype.distanceTo = function(vector, abs) {
var distance = Math.sqrt(Math.pow(this.x - vector.x, 2) + Math.pow(this.y - vector.y, 2));
return abs || false ? Math.abs(distance) : distance;
};
new Canvas({
el: document.getElementById('canvas'),
count: 25,
speed: 0.3,
radius: 6,
width: function() { return window.innerWidth; },
height: function() { return window.innerHeight; },
size: 15,
color: '30, 180, 1',
maxDistance: 100,
background: ['250,250,250,1', '215,216,215,0.8']
})
CSS
html, body {
min-height: 100%;
height: 100%;
}
#canvas {
width: 100%;
height: 100%;
position: fixed;
z-index: 100;
}
.page-content {
position: relative;
z-index: 900;
display: flex;
flex-direction: column;
}
HTML
<body>
<div id="container">
<canvas id="canvas"></canvas>
<div class="page-content">
</div>
</div>
</body>

Why does my vector splits in two vectors?

I'll try to explain as best I can.
I'm using canvas, and a basic vectors library I wrote in JS. Now my enemy object has a few vectors in it, the math seems right all the way through, but I end up seeing the enemy's weapon split into two and fire in both directions when I get too close to it.
Not close
Close
The player is the white circle with a green dot in the middle, and I use mousemove for the coordinates.
After looking at the images, I noticed that it creates two weapons that fire at the correct fireRate, one after the other. It doesn't show in the images, but when you look at it in the browser, there are two small red dots firing those yellow projectiles.
I looked over the code, and I can't find anything repeating itself that could cause the dot to split into two, and I started to wonder if it has anything to do with Math.atan2() or maybe something else.
Here's the enemy function:
function Enemy(enemyConfig){
this.enemyConfig = enemyConfig;
this.position = new Vector(enemyConfig.x, enemyConfig.y);
if(this.enemyConfig.hasVelocity){
this.velocity = new Vector(enemyConfig.vx, enemyConfig.vy);
}
this.radius = enemyConfig.radius;
this.hasWeapon = false;
if(enemyConfig.hasWeapon){
this.weaponId = enemyConfig.weaponId;
this.projectiles = [];
this.weapon = this.position.Copy();
this.weaponDistanceFromPosition = this.radius * .9;
this.weaponAngleToPlayer = 0;
this.weaponReloaded = true;
this.fireRate = enemyConfig.fireRate || .25;
this.reloadTimer = this.fireRate;
this.hasWeapon = true;
}
this.target = cfg.player;
};
Enemy.prototype.Draw = function(){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(0, 0, 0, 1)";
cfg.ctx.strokeStyle = "rgba(255, 0, 0, 1)";
cfg.ctx.arc(this.position.x, this.position.y, this.radius, 0, PI*2, false);
cfg.ctx.fill();
cfg.ctx.stroke();
cfg.ctx.closePath();
cfg.ctx.restore();
if(this.hasWeapon){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(255, 0, 0, 1)";
// cfg.ctx.strokeStyle = "rgba(0, 0, 0, 1)";
cfg.ctx.arc(this.weapon.x, this.weapon.y, 2, 0, PI*2, false);
cfg.ctx.fill();
// cfg.ctx.stroke();
cfg.ctx.closePath();
cfg.ctx.restore();
if(this.projectiles.length > 0 && this.projectiles){
this.projectiles.forEach((projectile, index, projectiles) => {
if(projectile.exists) projectile.Draw();
})
}
}
};
Enemy.prototype.Update = function(){
if(this.hasWeapon){
this.weaponAngleToPlayer = this.weapon.AngleTo(this.target.position);
this.weapon.Length = this.weaponDistanceFromPosition;
this.weapon.Angle = this.weaponAngleToPlayer;
this.weapon.AddTo(this.position);
this.weaponReloaded = (this.reloadTimer === 0);
if(!this.weaponReloaded){
this.reloadTimer -= Math.min(cfg.deltaTime / 1000, this.reloadTimer);
}else{
this.FireWeapon();
}
if(this.projectiles.length > 0){
this.projectiles.forEach((projectile, index, projectiles) => {
projectile.Update();
if(!projectile.exists) projectiles.splice(index, 1);
});
}
}
// console.log(this.projectiles);
// this.target = cfg.player;
// console.log(this.target.position);
};
Enemy.prototype.FireWeapon = function(){
var projectile = new Projectile();
projectile.position = this.weapon.Copy();
projectile.velocity = this.weapon.Copy();
projectile.velocity.Angle = this.weaponAngleToPlayer;
projectile.velocity.Limit(15);
projectile.exists = true;
this.projectiles.push(projectile);
this.reloadTimer = this.fireRate;
};
var PI = Math.PI;
var cfg = cfg || {};
var player;
cfg.expectedFPS = 60;
cfg.framesPerMs = cfg.expectedFPS / 1000;
cfg.deltaTime = 0;
cfg.previousTimeStamp = undefined;
cfg.direction = {
up: 1.5 * PI,
down: .5 * PI,
left: PI,
right: 0
};
cfg.mouse = {
x: 0,
y: 0,
radius: 2
};
cfg.projectiles = [];
cfg.player = null;
cfg.enemies = [];
function random(min, max = null){
if(max != null){
return Math.random() * (Math.max(min, max) - Math.min(min, max)) + Math.min(min, max);
}
return Math.random() * min;
};
function Background(config = {}){
var config = config;
cfg.ctx.fillStyle = "rgba(0, 0, 0, .5)";
cfg.ctx.fillRect(0, 0, cfg.canvas.width, cfg.canvas.height);
};
function KeepInCanvas(object){
if(object.position.x + object.radius >= cfg.canvas.width)
object.position.x = cfg.canvas.width - object.radius;
if(object.position.x - object.radius <= 0)
object.position.x = object.radius;
if(object.position.y + object.radius >= cfg.canvas.height)
object.position.y = cfg.canvas.height - object.radius;
if(object.position.y - object.radius <= 0)
object.position.y = object.radius;
};
function IsOutOfCanvas(object){
return (object.position.x + object.radius < 0 || object.position.x - object.radius > cfg.canvas.width
|| object.position.y + object.radius < 0 || object.position.y - object.radius > cfg.canvas.height);
};
function Vector(x = 0, y = 0){
this.x = x;
this.y = y;
};
Vector.prototype = {
get Length(){
return Math.sqrt(this.x * this.x + this.y * this.y);
},
set Length(length){
var angle = this.Angle;
this.x = Math.cos(angle) * length;
this.y = Math.sin(angle) * length;
},
get Angle(){
return Math.atan2(this.y, this.x);
},
set Angle(angle){
var length = this.Length;
this.x = Math.cos(angle) * length;
this.y = Math.sin(angle) * length;
},
Add: function(vector){ return new Vector(this.x + vector.x, this.y + vector.y); },
AddTo: function(vector){
this.x += vector.x;
this.y += vector.y;
},
Subtract: function(vector){ return new Vector(this.x - vector.x, this.y - vector.y); },
SubtractFrom: function(vector){
this.x -= vector.x;
this.y -= vector.y;
},
Multiply: function(factor){ return new Vector(this.x * factor, this.y * factor); },
MultiplyBy: function(factor){
this.x *= factor;
this.y *= factor;
},
DivideBy: function(factor){
this.x /= factor;
this.y /= factor;
},
AngleTo: function(vector){
return Math.atan2(vector.y - this.y, vector.x - this.x);
},
Copy: function(){ return new Vector(this.x, this.y); },
Normalize: function(arg = false){
if(this.Length > 0){
if(arg) return this.Divide(this.Length);
this.DivideBy(this.Length);
}
},
Limit: function(factor){
if(this.Length > factor){
this.Normalize();
this.MultiplyBy(factor);
}
}
};
function Player(){
this.position = new Vector(0, 0);
this.radius = 20;
};
Player.prototype.Draw = function(){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(0, 180, 0, .35)";
cfg.ctx.strokeStyle = "rgba(255, 255, 255, 1)";
cfg.ctx.arc(this.position.x, this.position.y, this.radius, 0, PI*2, false);
cfg.ctx.fill();
cfg.ctx.stroke();
cfg.ctx.closePath();
cfg.ctx.restore();
};
Player.prototype.Update = function(){
this.position.x = cfg.mouse.x;
this.position.y = cfg.mouse.y;
KeepInCanvas(this);
};
function Projectile(){
this.position = new Vector(0, 0);
this.velocity;
this.radius = 5;
this.exists = false;
};
Projectile.prototype.SetInitialVelocity = function(angle, length){
var x = Math.cos(angle) * length,
y = Math.sin(angle) * length;
return new Vector(x, y);
};
Projectile.prototype.Draw = function(){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(255, 160, 0, 1)";
cfg.ctx.arc(this.position.x, this.position.y, this.radius, 0, PI*2, false);
cfg.ctx.fill();
cfg.ctx.closePath();
cfg.ctx.restore();
};
Projectile.prototype.Update = function(){
this.position.AddTo(this.velocity.Multiply(cfg.framesPerMs).Multiply(cfg.deltaTime));
if(IsOutOfCanvas(this)) this.exists = false;
};
function Enemy(enemyConfig){
this.position = new Vector(enemyConfig.x, enemyConfig.y);
this.radius = enemyConfig.radius;
this.hasWeapon = false;
if(enemyConfig.hasWeapon){
this.projectiles = [];
this.weapon = this.position.Copy();
this.weaponAngleToPlayer;// = 0;
this.hasWeapon = true;
}
this.target = cfg.player;
};
Enemy.prototype.Draw = function(){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(0, 0, 0, 1)";
cfg.ctx.strokeStyle = "rgba(255, 0, 0, 1)";
cfg.ctx.arc(this.position.x, this.position.y, this.radius, 0, PI*2, false);
cfg.ctx.fill();
cfg.ctx.stroke();
cfg.ctx.closePath();
cfg.ctx.restore();
if(this.hasWeapon){
cfg.ctx.save();
cfg.ctx.beginPath();
cfg.ctx.fillStyle = "rgba(255, 0, 0, 1)";
cfg.ctx.arc(this.weapon.x, this.weapon.y, 2, 0, PI*2, false);
cfg.ctx.fill();
cfg.ctx.closePath();
cfg.ctx.restore();
if(this.projectiles.length > 0 && this.projectiles){
this.projectiles.forEach(projectile => {
if(projectile.exists) projectile.Draw();
})
}
}
};
Enemy.prototype.Update = function(){
if(this.hasWeapon){
this.weaponAngleToPlayer = this.position.AngleTo(this.target.position);
// console.trace(this.weaponAngleToPlayer);
this.weapon.Length = this.radius * .9;
this.weapon.Angle = this.weaponAngleToPlayer;
this.weapon.AddTo(this.position);
this.FireWeapon();
if(this.projectiles.length > 0){
this.projectiles.forEach((projectile, index, projectiles) => {
projectile.Update();
if(!projectile.exists) projectiles.splice(index, 1);
});
}
}
};
Enemy.prototype.FireWeapon = function(){
var projectile = new Projectile();
projectile.position = this.weapon.Copy();
projectile.velocity = projectile.position.Copy();
projectile.velocity.Angle = this.weaponAngleToPlayer;
projectile.velocity.Limit(15);
projectile.exists = true;
// console.trace(projectile);
this.projectiles.push(projectile);
};
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Space Shooter</title>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
window.onload = function(){
cfg.canvas = document.getElementById("gameCanvas");
cfg.canvas.focus();
cfg.canvas.width = 760;
cfg.canvas.height = 1060;
cfg.canvas.style.cursor = "none";
cfg.ctx = cfg.canvas.getContext("2d");
cfg.canvasOffset = cfg.canvas.getBoundingClientRect();
cfg.player = player = new Player();
cfg.enemies[0] = new Enemy({
x: 200,
y: 500,
radius: 35,
hasWeapon: true
});
cfg.enemies[1] = new Enemy({
x: 500,
y: 200,
radius: 45,
hasWeapon: true
});
window.requestAnimationFrame(Game);
};
window.addEventListener("mousemove", function(e){
cfg.mouse.x = e.clientX - cfg.canvasOffset.x;
cfg.mouse.y = e.clientY - cfg.canvasOffset.y;
});
function Game(timestamp){
if(!cfg.previousTimeStamp) cfg.previousTimeStamp = timestamp;
cfg.deltaTime = timestamp - cfg.previousTimeStamp;
cfg.previousTimeStamp = timestamp;
Update();
Draw();
window.requestAnimationFrame(Game);
};
function Update(){
player.Update();
if(cfg.enemies.length > 0){
cfg.enemies.forEach(enemy => {
enemy.Update();
});
}
};
function Draw(){
Background();
if(cfg.enemies.length > 0){
cfg.enemies.forEach(enemy => {
enemy.Draw();
});
}
player.Draw();
};
</script>
</body>

Add image to canvas

I saw on the internet beatiful example of canvas: https://codepen.io/anon/pen/BdjzPb
And I became interested in canvas. So I began to study it. But I can not understand how to change the dots (stars) in this example to pictures? I tried different combinations of context.drawImage(...) but I have no results...
The question is, where I should insert context.drawImage(...) to replace white dots with my images?
Here is js code of this canvas:
/*
* requestAnimationFrame pollyfill
*/
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) {
return window.setTimeout(callback, 1000 / 60);
});
}
// Init Stats
var stats = new Stats();
stats.setMode(0);
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
/*!
* Mantis.js / jQuery / Zepto.js plugin for Constellation
* #version 1.2.2
* #author AcauĆ£ Montiel <contato#acauamontiel.com.br>
* #license http://acaua.mit-license.org/
*/
(function ($, window) {
/**
* Makes a nice constellation on canvas
* #constructor Constellation
*/
function Constellation (canvas, options) {
var $canvas = $(canvas),
context = canvas.getContext('2d'),
defaults = {
star: {
color: 'rgba(255, 255, 255, .5)',
width: 1,
randomWidth: true
},
line: {
color: 'rgba(255, 255, 255, .5)',
width: 0.2
},
position: {
x: 0, // This value will be overwritten at startup
y: 0 // This value will be overwritten at startup
},
width: window.innerWidth,
height: window.innerHeight,
velocity: 0.1,
length: 100,
distance: 120,
radius: 150,
stars: []
},
config = $.extend(true, {}, defaults, options);
function Star () {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (config.velocity - (Math.random() * 0.5));
this.vy = (config.velocity - (Math.random() * 0.5));
this.radius = config.star.randomWidth ? (Math.random() * config.star.width) : config.star.width;
}
Star.prototype = {
create: function(){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
context.fill();
},
animate: function(){
var i;
for (i = 0; i < config.length; i++) {
var star = config.stars[i];
if (star.y < 0 || star.y > canvas.height) {
star.vx = star.vx;
star.vy = - star.vy;
} else if (star.x < 0 || star.x > canvas.width) {
star.vx = - star.vx;
star.vy = star.vy;
}
star.x += star.vx;
star.y += star.vy;
}
},
line: function(){
var length = config.length,
iStar,
jStar,
i,
j;
for (i = 0; i < length; i++) {
for (j = 0; j < length; j++) {
iStar = config.stars[i];
jStar = config.stars[j];
if (
(iStar.x - jStar.x) < config.distance &&
(iStar.y - jStar.y) < config.distance &&
(iStar.x - jStar.x) > - config.distance &&
(iStar.y - jStar.y) > - config.distance
) {
if (
(iStar.x - config.position.x) < config.radius &&
(iStar.y - config.position.y) < config.radius &&
(iStar.x - config.position.x) > - config.radius &&
(iStar.y - config.position.y) > - config.radius
) {
context.beginPath();
context.moveTo(iStar.x, iStar.y);
context.lineTo(jStar.x, jStar.y);
context.stroke();
context.closePath();
}
}
}
}
}
};
this.createStars = function () {
var length = config.length,
star,
i;
context.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0; i < length; i++) {
config.stars.push(new Star());
star = config.stars[i];
star.create();
}
star.line();
star.animate();
};
this.setCanvas = function () {
canvas.width = config.width;
canvas.height = config.height;
};
this.setContext = function () {
context.fillStyle = config.star.color;
context.strokeStyle = config.line.color;
context.lineWidth = config.line.width;
};
this.setInitialPosition = function () {
if (!options || !options.hasOwnProperty('position')) {
config.position = {
x: canvas.width * 0.5,
y: canvas.height * 0.5
};
}
};
this.loop = function (callback) {
callback();
window.requestAnimationFrame(function () {
stats.begin(); // Only for Stats
this.loop(callback);
stats.end(); // Only for Stats
}.bind(this));
};
this.bind = function () {
$canvas.on('mousemove', function(e){
config.position.x = e.pageX - $canvas.offset().left;
config.position.y = e.pageY - $canvas.offset().top;
});
};
this.init = function () {
this.setCanvas();
this.setContext();
this.setInitialPosition();
this.loop(this.createStars);
this.bind();
};
}
$.fn.constellation = function (options) {
return this.each(function () {
var c = new Constellation(this, options);
c.init();
});
};
})($, window);
// Init plugin
$('canvas').constellation({
star: {
width: 3
},
line: {
color: 'rgba(255, 0, 0, .5)'
},
radius: 250
});
You need to modify the create() method of Star constructor, because this is exactly the one that's being called when the canvas redraws everything (as a part of calling createStars() method inside the loop).
There is, however, one thing about context.drawImage(...) that you should be aware of: it needs image to be present in the DOM in order to render it in canvas, so first you would have to do something like
var img = new Image;
img.src = 'https://sourceofyourimage/image.jpg';
and then in create() method you can call context.drawImage(...) passing image, coordinates and size (if you want to).
https://codepen.io/Alvvi/pen/RZroPW here is a working CodePen
Also, you may want to check, if the image is loaded before trying to paint it, I suggest using onload for that, however in my case it works fine without it, because the image is quite small.

How to resize a canvas on resizing?

I'm using this plugin: https://codepen.io/acauamontiel/pen/mJdnw
/*
* requestAnimationFrame pollyfill
*/
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function (callback) {
return window.setTimeout(callback, 1000 / 60);
});
}
/*!
* Mantis.js / jQuery / Zepto.js plugin for Constellation
* #version 1.2.2
* #author AcauĆ£ Montiel <contato#acauamontiel.com.br>
* #license http://acaua.mit-license.org/
*/
(function ($, window) {
/**
* Makes a nice constellation on canvas
* #constructor Constellation
*/
function Constellation (canvas, options) {
var $canvas = $(canvas),
context = canvas.getContext('2d'),
defaults = {
star: {
color: 'rgba(255, 255, 255, .5)',
width: 1,
randomWidth: true
},
line: {
color: 'rgba(255, 255, 255, .5)',
width: 0.2
},
position: {
x: 0, // This value will be overwritten at startup
y: 0 // This value will be overwritten at startup
},
width: window.innerWidth,
height: window.innerHeight,
velocity: 0.1,
length: 100,
distance: 120,
radius: 150,
stars: []
},
config = $.extend(true, {}, defaults, options);
function Star () {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (config.velocity - (Math.random() * 0.5));
this.vy = (config.velocity - (Math.random() * 0.5));
this.radius = config.star.randomWidth ? (Math.random() * config.star.width) : config.star.width;
}
Star.prototype = {
create: function(){
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
context.fill();
},
animate: function(){
var i;
for (i = 0; i < config.length; i++) {
var star = config.stars[i];
if (star.y < 0 || star.y > canvas.height) {
star.vx = star.vx;
star.vy = - star.vy;
} else if (star.x < 0 || star.x > canvas.width) {
star.vx = - star.vx;
star.vy = star.vy;
}
star.x += star.vx;
star.y += star.vy;
}
},
line: function(){
var length = config.length,
iStar,
jStar,
i,
j;
for (i = 0; i < length; i++) {
for (j = 0; j < length; j++) {
iStar = config.stars[i];
jStar = config.stars[j];
if (
(iStar.x - jStar.x) < config.distance &&
(iStar.y - jStar.y) < config.distance &&
(iStar.x - jStar.x) > - config.distance &&
(iStar.y - jStar.y) > - config.distance
) {
if (
(iStar.x - config.position.x) < config.radius &&
(iStar.y - config.position.y) < config.radius &&
(iStar.x - config.position.x) > - config.radius &&
(iStar.y - config.position.y) > - config.radius
) {
context.beginPath();
context.moveTo(iStar.x, iStar.y);
context.lineTo(jStar.x, jStar.y);
context.stroke();
context.closePath();
}
}
}
}
}
};
this.createStars = function () {
var length = config.length,
star,
i;
context.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0; i < length; i++) {
config.stars.push(new Star());
star = config.stars[i];
star.create();
}
star.line();
star.animate();
};
this.setCanvas = function () {
canvas.width = config.width;
canvas.height = config.height;
};
this.setContext = function () {
context.fillStyle = config.star.color;
context.strokeStyle = config.line.color;
context.lineWidth = config.line.width;
};
this.setInitialPosition = function () {
if (!options || !options.hasOwnProperty('position')) {
config.position = {
x: canvas.width * 0.5,
y: canvas.height * 0.5
};
}
};
this.loop = function (callback) {
callback();
window.requestAnimationFrame(function () {
stats.begin(); // Only for Stats
this.loop(callback);
stats.end(); // Only for Stats
}.bind(this));
};
this.bind = function () {
$canvas.on('mousemove', function(e){
config.position.x = e.pageX - $canvas.offset().left;
config.position.y = e.pageY - $canvas.offset().top;
});
};
this.init = function () {
this.setCanvas();
this.setContext();
this.setInitialPosition();
this.loop(this.createStars);
this.bind();
};
}
$.fn.constellation = function (options) {
return this.each(function () {
var c = new Constellation(this, options);
c.init();
});
};
})($, window);
// Init plugin
$('canvas').constellation({
star: {
width: 3
},
line: {
color: 'rgba(255, 0, 0, .5)'
},
radius: 250
});
body {
overflow: hidden;
background: #111;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<canvas></canvas>
As you can see, when you resize your screen, the canvas keeps the original width and height, I would like to give the capacity to resize the window and do like a restart on the plugin to keep it fullscreen.
First
wrap your plugin initializing code with a function, like so ...
function init_plugin() {
$('canvas').constellation({
star: {
width: 3
},
line: {
color: 'rgba(255, 0, 0, .5)'
},
radius: 250
});
}
init_plugin(); //init plugin
Second
add a window resize event handler and within that do all the necessary stuff ...
window.onresize = function() {
cancelAnimationFrame(rAF); //cancel current animation frame
$('canvas')[0].width = window.innerWidth; //reset canvas width
$('canvas')[0].height = window.innerHeight; //reset canvas height
init_plugin(); //init plugin
}
also, you would need to assign requestAnimationFrame() to a globally accessible variable (here, it's rAF) , so you can cancel it later.
Here is the working code on CodePen
apology for not giving that much explanation

Categories

Resources