Why bluring all circle isn`t only stroke of circle - javascript

context.filter = "blur(2px)";
Why this ^ element blur all circle space not only edges
I want to make circles blur on edges to look like blured street lights
i fetch stroke in
if (stroke) { context.lineWidth = 1; context.strokeStyle = stroke; context.stroke();}
and set the value in function invoke
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
EDIT: I want to blur only the edges of cirlce not all space(fill) to look like street light
EDIT::: ATTACH FULL CODE
let Drawing = false;
const fireworks = document.querySelector(".fireworks");
const context = fireworks.getContext("2d");
let x;
let y;
let numberCircles = 5;
const colors = ["#780000", "#c1121f", "#fdf0d5", "#003049", "#669bbc"];
//function seting window Size
function windowSize() {
fireworks.width = window.innerWidth * 2;
fireworks.height = window.innerHeight * 2;
fireworks.style.height = window.innerHeight + "px";
fireworks.style.width = window.innerWidth + "px";
context.scale(2, 2);
// console.log("windowSize");
}
//create circle
function createCircle(x, y, stroke) {
context.beginPath();
radius = Math.random() * 5 + 3;
let fill = colors[Math.round(Math.random() * colors.length - 1)];
context.arc(x, y, radius, 0, 2 * Math.PI, false);
if (fill) {
context.fillStyle = fill;
context.fill();
}
if (stroke) {
context.lineWidth = 1;
context.strokeStyle = stroke;
context.stroke();
}
}
//make circle spray DEPEND on angle
function sprayCircle() {
for (i = 0; i < numberCircles; i++) {
let los = Math.round(Math.random() * 3);
switch (los) {
case 0:
createCircle(x + Math.random() * 30 + 10, y + Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 1:
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 2:
createCircle(x - Math.random() * 5, y - Math.random() * 5);
break;
}
}
}
//mouse follow
fireworks.addEventListener("mousedown", function (e) {
Drawing = true;
});
fireworks.addEventListener("mousemove", function (e) {
if (Drawing === true) {
sprayCircle();
x = e.offsetX;
y = e.offsetY;
}
});
window.addEventListener("mouseup", function (e) {
if (Drawing === true) {
Drawing = false;
}
});
//end
windowSize();
window.addEventListener("resize", windowSize, false);
<canvas class="fireworks" width="560" height="868"></canvas>

Related

drawStar() with mouse inside canvas mouse up mouse down

What am I missing? The drawCircle and DrawPolygon (it is located in codepen https://codepen.io/mancod/pen/oNYdrVL work fine. I am still very new to all this still, and beat myself up as nothing in life should be this messy. A star is a circle as is a polygon. I get that the star has an inner and outer radius, but I cannot get this star. Thank you in advance for eyes that can fill in the part I am missing or have in the wrong order for function drawStar(). I have commented out the drawline and drawcircle. If you want to know it that even work you can view it on https://jsfiddle.net/mancod/mhbrqxk8/45/ where I have commented out the drawStar.
`enter code here`var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
`enter code here`function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {x: x, y: y};
}
`enter code here`function takeSnapshot (){
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
`enter code here`function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
`enter code here`function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
`enter code here`// this is for making circles
//d(P, Q) = p(x2 − x1)2 + (y2 − y1)2 {Distance formula}
//https://orion.math.iastate.edu/dept/links/formulas/form2.pdf
// comment out function to go back to drawing just straight lines.
function drawCircle (position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
context.beginPath();
context.arc(position.x, position.y, radius, 0, 2 * Math.PI, false);
context.fill();
}
**function drawStar (position, points, outerRadius, innnerRadius) {
var coordinates=[],
radius = index%2 == 0? outerRadius : innerRadius,
index=0;
for (index = 0; index < points; index++) {
coordinates.push({x: dragStartLocation.x + radius * Math.cos(angle), y: dragStartLocation.y - radius * Math.sin(angle)});
angle += Math.PI / points;
context.beginPath();
context.drawStar(position, points, innerRadius, outerRadius);
context.moveTo(coordinates[0].x, coordinates[0].y+outerRadius);
//for (index = 1; index < points; index++) //{
//context.lineTo(coordinates[index].x + radius *Math.cos(angle), coordinates[index].y + radius * Math.sin(angle));
//}
}
context.closePath();
}**
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
//drawCircle(position);
//drawLine(position);
drawStar(position, 6, 2, 15);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
//drawCircle(position);
//drawLine(position);
drawStar(postion,6, 2,15);
}
canvas = document.getElementById("cv0");
context = canvas.getContext('2d');
context.strokeStyle = 'orange';
context.fillStyle = 'hsl(' + 360*Math.random() +', 100%, 45%)';
context.lineWidth = 5;
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
Let's have a look at the parameter definition for the drawStar() function:
drawStar (position, points, outerRadius, innnerRadius)
and remind ourselves what a typical stylized star looks like
Alright so far. There are two places the drawStar function gets called: inside draw and dragStop. In both cases you're calling it like
drawStar(position, 6, 2, 15);
This means we pass 6 as the number of points for the star shape - if we look above we can see that star is made up of 10 points.
The second mistake here is the hardcoded values 2 and 15 for the radius of the star. I think you want to dynamically size it according to the movement of the mouse, so we need to recalculate the radii on mouse move. Well as we don't have a use for the two parameters we can get rid of it altogether and just call it like:
drawStar(position, 10);
Inside the drawStar function we need to calculate the points for the star shape like:
for (index = 0; index < points; index++) {
if (index % 2 == 0) {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
} else {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)) * 0.5;
}
coordinates.push({
x: dragStartLocation.x + radius * Math.cos(angle),
y: dragStartLocation.y - radius * Math.sin(angle)
});
angle += Math.PI / points * 2;
}
As you can see where dynamically calculating the radius for the inner and outer points, pushing the points into the coordinates array and ultimately adding 36° to the angle variable (360°/10 points=36°)
Finally let's iterate over the coordinates array and draw the lines to screen:
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < points; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.fill();
Here's a working example based on your fiddle:
var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {
x: x,
y: y
};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
// this is for making circles
//d(P, Q) = p(x2 − x1)2 + (y2 − y1)2 {Distance formula}
//https://orion.math.iastate.edu/dept/links/formulas/form2.pdf
// comment out function to go back to drawing just straight lines.
function drawCircle(position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
context.beginPath();
context.arc(position.x, position.y, radius, 0, 2 * Math.PI, false);
context.fill();
}
function drawStar(position, points) {
var coordinates = [];
var index;
var radius;
var angle = Math.PI / 2;
for (index = 0; index < points; index++) {
if (index % 2 == 0) {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
} else {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)) * 0.5;
}
coordinates.push({
x: dragStartLocation.x + radius * Math.cos(angle),
y: dragStartLocation.y - radius * Math.sin(angle)
});
angle += Math.PI / points * 2;
}
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < points; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.fill();
}
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
// drawCircle(position);
//drawLine(position);
drawStar(position, 10);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
// drawCircle(position);
//drawLine(position);
drawStar(position, 10);
}
canvas = document.getElementById("cv0");
context = canvas.getContext('2d');
context.strokeStyle = 'orange';
context.fillStyle = 'hsl(' + 360 * Math.random() + ', 100%, 45%)';
context.lineWidth = 5;
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
#cv0 {
border: solid gray;
}
<canvas id='cv0' width=400 height=300></canvas>

How can we stop this HTML5 Canvas wheel at exact points after spin?

In the Below code link HTML5 canvas spin wheel game. I want to stop this canvas at a user-defined position as if the user wants to stop always at 200 texts or 100 texts like that.
Currently, it is stopping at random points I want to control where to stop as in if I want to stop circle at 100 or 200 or 0 whenever I want.
How can we achieve that??? Can anyone Help!!!!!
Attached Codepen link also.
Html file
<div>
<canvas class="spin-wheel" id="canvas" width="300" height="300"></canvas>
</div>
JS file
var color = ['#ca7','#7ac','#77c','#aac','#a7c','#ac7', "#caa"];
var label = ['10', '200','50','100','5','500',"0"];
var slices = color.length;
var sliceDeg = 360/slices;
var deg = 270;
var speed = 5;
var slowDownRand = 0;
var ctx = canvas.getContext('2d');
var width = canvas.width; // size
var center = width/2; // center
var isStopped = false;
var lock = false;
function rand(min, max) {
return Math.random() * (max - min) + min;
}
function deg2rad(deg){ return deg * Math.PI/180; }
function drawSlice(deg, color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.moveTo(center, center);
ctx.arc(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg));
console.log(center, center, width/2, deg2rad(deg), deg2rad(deg+sliceDeg))
ctx.lineTo(center, center);
ctx.fill();
}
function drawText(deg, text) {
ctx.save();
ctx.translate(center, center);
ctx.rotate(deg2rad(deg));
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = 'bold 30px sans-serif';
ctx.fillText(text, 130, 10);
ctx.restore();
}
function drawImg() {
ctx.clearRect(0, 0, width, width);
for(var i=0; i<slices; i++){
drawSlice(deg, color[i]);
drawText(deg+sliceDeg/2, label[i]);
deg += sliceDeg;
}
}
// ctx.rotate(360);
function anim() {
isStopped = true;
deg += speed;
deg %= 360;
// Increment speed
if(!isStopped && speed<3){
speed = speed+1 * 0.1;
}
// Decrement Speed
if(isStopped){
if(!lock){
lock = true;
slowDownRand = rand(0.994, 0.998);
}
speed = speed>0.2 ? speed*=slowDownRand : 0;
}
// Stopped!
if(lock && !speed){
var ai = Math.floor(((360 - deg - 90) % 360) / sliceDeg); // deg 2 Array Index
console.log(slices)
ai = (slices+ai)%slices; // Fix negative index
return alert("You got:\n"+ label[ai] ); // Get Array Item from end Degree
// ctx.arc(150,150,150,8.302780584487312,9.200378485512967);
// ctx.fill();
}
drawImg();
window.requestAnimationFrame(anim);
}
function start() {
anim()
}
drawImg();
Spin wheel codepen
Ease curves
If you where to plot the wheel position over time as it slows to a stop you would see a curve, a curve that looks like half a parabola.
You can get the very same curve if you plot the value of x squared in the range 0 to 1 as in the next snippet, the red line shows the plot of f(x) => x * x where 0 <= x <= 1
Unfortunately the plot is the wrong way round and needs to be mirrored in x and y. That is simple by changing the function to f(x) => 1 - (1 - x) ** 2 (Click the canvas to get the yellow line)
const size = 200;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size / 2}).getContext("2d");
document.body.appendChild(ctx.canvas);
ctx.canvas.style.border = "2px solid black";
plot(getData());
plot(unitCurve(x => x * x), "#F00");
ctx.canvas.addEventListener("click",()=>plot(unitCurve(x => 1 - (1 - x) ** 2), "#FF0"), {once: true});
function getData(chart = []) {
var pos = 0, speed = 9, deceleration = 0.1;
while(speed > 0) {
chart.push(pos);
pos += speed;
speed -= deceleration;
}
return chart;
}
function unitCurve(f,chart = []) {
const step = 1 / 100;
var x = 0;
while(x <= 1) {
chart.push(f(x));
x += step
}
return chart;
}
function plot(chart, col = "#000") {
const xScale = size / chart.length, yScale = size / 2 / Math.max(...chart);
ctx.setTransform(xScale, 0, 0, yScale, 0, 0);
ctx.strokeStyle = col;
ctx.beginPath();
chart.forEach((y,x) => ctx.lineTo(x,y));
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
In animation this curve is an ease in.
We can create function that uses the ease function, takes the time and returns the position of the wheel. We can provide some additional values that controls how long the wheel will take to stop, the starting position and the all important stop position.
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
// first scale the current time to a value from 0 to 1
const x = (currentTime - startTime) / (endTime - startTime);
// rather than the square, we will use the square root (this flips the curve)
const xx = x ** (1 / 2);
// convert the value to a wheel position
return xx * (endPos - startPos) + startPos;
}
Demo
The demo puts it in action. Rather than using the square root the function in the demo defines the root as the constant slowDownRate = 2.6. The smaller this value the greater start speed and the slower the end speed. A value of 1 means it will move at a constant speed and then stop. The value must be > 0 and < 1
requestAnimationFrame(mainLoop);
Math.TAU = Math.PI * 2;
const size = 160;
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
document.body.appendChild(ctx.canvas);
const stopAt = document.createElement("div")
document.body.appendChild(stopAt);
ctx.canvas.style.border = "2px solid black";
var gTime; // global time
const colors = ["#F00","#F80","#FF0","#0C0","#08F","#00F","#F0F"];
const wheelSteps = 12;
const minSpins = 3 * Math.TAU; // min number of spins before stopping
const spinTime = 6000; // in ms
const slowDownRate = 1 / 1.8; // smaller this value the greater the ease in.
// Must be > 0
var startSpin = false;
var readyTime = 0;
ctx.canvas.addEventListener("click",() => { startSpin = !wheel.spinning });
stopAt.textContent = "Click wheel to spin";
const wheel = { // hold wheel related variables
img: createWheel(wheelSteps),
endTime: performance.now() - 2000,
startPos: 0,
endPos: 0,
speed: 0,
pos: 0,
spinning: false,
set currentPos(val) {
this.speed = (val - this.pos) / 2; // for the wobble at stop
this.pos = val;
},
set endAt(pos) {
this.endPos = (Math.TAU - (pos / wheelSteps) * Math.TAU) + minSpins;
this.endTime = gTime + spinTime;
this.startTime = gTime;
stopAt.textContent = "Spin to: "+(pos + 1);
}
};
function wheelPos(currentTime, startTime, endTime, startPos, endPos) {
const x = ((currentTime - startTime) / (endTime - startTime)) ** slowDownRate;
return x * (endPos - startPos) + startPos;
}
function mainLoop(time) {
gTime = time;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, size, size);
if (startSpin && !wheel.spinning) {
startSpin = false;
wheel.spinning = true;
wheel.startPos = (wheel.pos % Math.TAU + Math.TAU) % Math.TAU;
wheel.endAt = Math.random() * wheelSteps | 0;
} else if (gTime <= wheel.endTime) { // wheel is spinning get pos
wheel.currentPos = wheelPos(gTime, wheel.startTime, wheel.endTime, wheel.startPos, wheel.endPos);
readyTime = gTime + 1500;
} else { // wobble at stop
wheel.speed += (wheel.endPos - wheel.pos) * 0.0125;
wheel.speed *= 0.95;
wheel.pos += wheel.speed;
if (wheel.spinning && gTime > readyTime) {
wheel.spinning = false;
stopAt.textContent = "Click wheel to spin";
}
}
// draw wheel
ctx.setTransform(1,0,0,1,size / 2, size / 2);
ctx.rotate(wheel.pos);
ctx.drawImage(wheel.img, -size / 2 , - size / 2);
// draw marker shadow
ctx.setTransform(1,0,0,1,1,4);
ctx.fillStyle = "#0004";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
// draw marker
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#F00";
ctx.beginPath();
ctx.lineTo(size - 13, size / 2);
ctx.lineTo(size, size / 2 - 7);
ctx.lineTo(size, size / 2 + 7);
ctx.fill();
requestAnimationFrame(mainLoop);
}
function createWheel(steps) {
const ctx = Object.assign(document.createElement("canvas"),{width: size, height: size}).getContext("2d");
const s = size, s2 = s / 2, r = s2 - 4;
var colIdx = 0;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.fillStyle = colors[colIdx++ % colors.length];
ctx.beginPath();
ctx.moveTo(s2, s2);
ctx.arc(s2, s2, r, aa, aa + Math.TAU / steps);
ctx.fill();
}
ctx.fillStyle = "#FFF";
ctx.beginPath();
ctx.arc(s2, s2, 12, 0, Math.TAU);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(s2, s2, r, 0, Math.TAU);
ctx.moveTo(s2 + 12, s2);
ctx.arc(s2, s2, 12, 0, Math.TAU);
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const aa = a - Math.PI / steps;
ctx.moveTo(Math.cos(aa) * 12 + s2, Math.sin(aa) * 12 + s2);
ctx.lineTo(Math.cos(aa) * r + s2, Math.sin(aa) * r + s2);
}
//ctx.fill("evenodd");
ctx.stroke();
ctx.fillStyle = "#000";
ctx.font = "13px arial black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const tr = r - 8;
var idx = 1;
for (let a = 0; a < Math.TAU; a += Math.TAU / steps) {
const dx = Math.cos(a);
const dy = Math.sin(a);
ctx.setTransform(dy, -dx, dx, dy, dx * (tr - 4) + s2, dy * (tr - 4) + s2);
ctx.fillText(""+ (idx ++), 0, 0);
}
return ctx.canvas;
}
body { font-family: arial }

javascript code limited by text position in the html page

i'm trying to use this Fireworks javascript. But when i write some text in the middle of the HTML page, the fireworks will not go over it and is limited by the text position...how can i override this and keep the fireworks go up to the top of the page ?
i tryed to fix the SCREEN_WIDTH and SCREEN_HEIGHT position but it doesn't work...
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
document.body.appendChild(canvas);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(mousePos.x);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.05)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
gradient.addColorStop(1, "rgba(0, 0, 0, " + this.alpha + ")");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
body {
background-color: #000000;
margin: 0px;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Fireworks!</title>
</head>
<body/>
</html>
The problem is the canvas used by the script is positioned relative by default. To make it always visible completely on screen we have to make it fixed and set the top and left CSS values to 0.
Now because its fixed the canvas renders on top of everything. To get it in the background set z-index to -1.
All additions together:
canvas.style.position="fixed";
canvas.style.top="0";
canvas.style.left="0";
canvas.style.zIndex="-1";
Complete Source:
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
document.body.appendChild(canvas);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
canvas.style.position = "fixed";
canvas.style.top = "0";
canvas.style.left = "0";
canvas.style.zIndex = "-1";
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(mousePos.x);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.05)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT
}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
gradient.addColorStop(1, "rgba(0, 0, 0, " + this.alpha + ")");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
body {
background-color: #000000;
margin: 0px;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Fireworks!</title>
</head>
<body>
<p>
test
</p>
<br />
<p>
test2
</p>
</body>
</html>

How to add random shapes on click using canvas animation

For this animation On click I need a different shapes to be made I need 5 different shapes in total. Also Ive had a hard time doing these things,
add a random motion vector to every circle
add an interval timer that redraws the background and each circle in its new position every 30 milliseconds
Check if any circle is outside the canvas width and height, and if so reverse its direction back onto the screen
also maybe If I can have some random text to fade in and fade out every couple of seconds too
The code...
var canvas;
var context;
var circles = [];
var timer;
function Circle(x, y, color) {
this.x = x;
this.y = y;
this.dx = Math.random() * 4 - 2;
this.dy = Math.random() * 4 - 2;
this.color = color;
}
function init() {
canvas = document.getElementById('canvas');
context = canvas.getContext("2d");
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
resizeCanvas();
canvas.onclick = function (event) {
handleClick(event.clientX, event.clientY);
};
timer = setInterval(resizeCanvas, 20);
}
function handleClick(x, y) {
var found = false;
for (var i = 0; i < circles.length; i++) {
d = Math.sqrt((circles[i].x - x) * (circles[i].x - x) + (circles[i].y - y) * (circles[i].y - y));
if (d <= 30) {
circles.splice(i, 1);
found = true;
}
}
fillBackgroundColor();
if (!found) {
var colors = ["red", "green", "blue", "orange", "purple", "yellow"];
var color = colors[Math.floor(Math.random() * colors.length)];
circles.push(new Circle(x, y, color));
}
for (var i = 0; i < circles.length; i++) {
drawCircle(circles[i]);
}
}
function drawCircle(circle) {
context.beginPath();
context.arc(circle.x, circle.y, 30, 0, degreesToRadians(360), true);
context.fillStyle = circle.color;
context.fill();
if (circle.x + circle.dx > canvas.width || circle.x + circle.dx < 0)
circle.dx = -circle.dx;
if (circle.y + circle.dy > canvas.height || circle.y + circle.dy < 0)
circle.dy = -circle.dy;
circle.x += circle.dx;
circle.y += circle.dy;
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
fillBackgroundColor();
for (var i = 0; i < circles.length; i++) {
drawCircle(circles[i]);
}
}
function fillBackgroundColor() {
//var colors = ["white", "yellow", "blue", "red"];
//var bgColor = colors[Math.floor(Math.random() * colors.length)];
context.fillStyle = 'black';
context.fillRect(0, 0, canvas.width, canvas.height);
}
function degreesToRadians(degrees) {
//converts from degrees to radians and returns
return (degrees * Math.PI) / 180;
}
window.onload = init;
<canvas id='canvas' width=500 height=500></canvas>

Javascript Canvas apply Radial Gradient to Segment?

I am trying to create a shadow system for my 2D Game in a HTML5 Canvas. Right now, I am rendering my shadows like so:
function drawShadows(x, y, width) {
if (shadowSprite == null) {
shadowSprite = document.createElement('canvas');
var tmpCtx = shadowSprite.getContext('2d');
var shadowBlur = 20;
shadowSprite.width = shadowResolution;
shadowSprite.height = shadowResolution;
var grd = tmpCtx.createLinearGradient(-(shadowResolution / 4), 0,
shadowResolution, 0);
grd.addColorStop(0, "rgba(0, 0, 0, 0.1)");
grd.addColorStop(1, "rgba(0, 0, 0, 0)");
tmpCtx.fillStyle = grd;
tmpCtx.shadowBlur = shadowBlur;
tmpCtx.shadowColor = "#000";
tmpCtx.fillRect(0, 0, shadowResolution, shadowResolution);
}
graph.save();
graph.rotate(sun.getDir(x, y));
graph.drawImage(shadowSprite, 0, -(width / 2), sun.getDist(x, y), width);
graph.restore();
}
This renders a cube with a linear gradient that fades from black to alpha 0.
This however does not produce a realistic result, since it will always be a rectangle. Here is an illustration to describe the problem:
Sorry i'm not very artistic. It would not be an issue to draw the trapezoid shape. (Seen in blue). The issue is that I still there to be a gradient. Is it possible to draw a shape like that with a gradient?
The canvas is very flexible. Almost anything is possible. This example draws the light being cast. But it can just as easily be the reverse. Draw the shadows as a gradient.
If you are after realism then instead of rendering a gradient for the lighting (or shadows) use the shape created to set a clipping area and then render a accurate lighting and shadow solution.
With lineTo and gradients you can create any shape and gradient you my wish. Also to get the best results use globalCompositeOperation as they have a large variety of filters.
The demo just shows how to mix a gradient and a shadow map. (Very basic no recursion implemented, and shadows are just approximations.)
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouse = {
x:0,
y:0,
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
}
// add mouse controls
canvas.addEventListener('mousemove',mouseMove);
var boundSize = 10000; // a number....
var createImage = function(w,h){ // create an image
var image;
image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
var directionC = function(x,y,xx,yy){ // this should be inLine but the angles were messing with my head
var a; // so moved it out here
a = Math.atan2(yy - y, xx - x); // for clarity and the health of my sanity
return (a + Math.PI * 2) % (Math.PI * 2); // Dont like negative angles.
}
// Create background image
var back = createImage(20, 20);
back.ctx.fillStyle = "#333";
back.ctx.fillRect(0, 0, 20, 20);
// Create background image
var backLight = createImage(20, 20);
backLight .ctx.fillStyle = "#ACD";
backLight .ctx.fillRect(0, 0, 20, 20);
// create circle image
var circle = createImage(64, 64);
circle.ctx.fillStyle = "red";
circle.ctx.beginPath();
circle.ctx.arc(32, 32, 30, 0, Math.PI * 2);
circle.ctx.fill();
// create some circles semi random
var circles = [];
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
// shadows on for each circle;
var shadows = [{},{},{},{}];
var update = function(){
var c, dir, dist, x, y, x1, y1, x2, y2, dir1, dir2, aAdd, i, j, s, s1 ,nextDir, rev, revId;
rev = false; // if inside a circle reverse the rendering.
// set up the gradient at the mouse pos
var g = ctx.createRadialGradient(mouse.x, mouse.y, canvas.width * 1.6, mouse.x, mouse.y, 2);
// do each circle and work out the two shadow lines coming from it.
for(var i = 0; i < circles.length; i++){
c = circles[i];
dir = directionC(mouse.x, mouse.y, c.x, c.y);
dist = Math.hypot(mouse.x - c.x, mouse.y - c.y);
// cludge factor. Could not be bother with the math as the light sourse nears an object
if(dist < 30* c.scale){
rev = true;
revId = i;
}
aAdd = (Math.PI / 2) * (0.5 / (dist - 30 * c.scale));
x1 = Math.cos(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
y1 = Math.sin(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
x2 = Math.cos(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
y2 = Math.sin(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
// direction of both shadow lines
dir1 = directionC(mouse.x, mouse.y, c.x + x1, c.y + y1);
dir2 = directionC(mouse.x, mouse.y, c.x + x2, c.y + y2);
// create the shadow object to hold details
shadows[i].dir = dir;
shadows[i].d1 = dir1;
if (dir2 < dir1) { // make sure second line is always greater
dir2 += Math.PI * 2;
}
shadows[i].d2 = dir2;
shadows[i].x1 = (c.x + x1); // set the shadow start pos
shadows[i].y1 = (c.y + y1);
shadows[i].x2 = (c.x + x2); // for both lines
shadows[i].y2 = (c.y + y2);
shadows[i].circle = c; // ref the circle
shadows[i].dist = dist; // set dist from light
shadows[i].branch1 = undefined; //.A very basic tree for shadows that interspet other object
shadows[i].branch2 = undefined; //
shadows[i].branch1Dist = undefined;
shadows[i].branch2Dist = undefined;
shadows[i].active = true; // false if the shadow is in a shadow
shadows[i].id = i;
}
shadows.sort(function(a,b){ // sort by distance from light
return a.dist - b.dist;
});
// cull shdows with in shadows and connect circles with joined shadows
for(i = 0; i < shadows.length; i++){
s = shadows[i];
for(j = i + 1; j < shadows.length; j++){
s1 = shadows[j];
if(s1.d1 > s.d1 && s1.d2 < s.d2){ // if shadow in side another
s1.active = false; // cull it
}else
if(s.d1 > s1.d1 && s.d1 < s1.d2){ // if shodow intercepts going twards light
s1.branch1 = s;
s.branch1Dist = s1.dist - s.dist;
s.active = false;
}else
if(s.d2 > s1.d1 && s.d2 < s1.d2){ // away from light
s.branch2 = s1;
s.branch2Dist = s1.dist - s.dist;
s1.active = false;
}
}
}
// keep it quick so not using filter
// filter culled shadows
var shadowsShort = [];
for (i = 0; i < shadows.length; i++) {
if ((shadows[i].active && !rev) || (rev && shadows[i].id === revId)) { // to much hard work makeng shadow from inside the circles. Was a good idea at the time. But this i just an example after all;
shadowsShort.push(shadows[i])
}
}
// sort shadows in clock wise render order
if(rev){
g.addColorStop(0.3, "rgba(210,210,210,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(0,0,0,0.9)");
shadowsShort.sort(function(a,b){
return b.dir - a.dir;
});
// clear by drawing background image.
ctx.drawImage(backLight, 0, 0, canvas.width, canvas.height);
}else{
g.addColorStop(0.3, "rgba(0,0,0,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(215,215,215,0.9)");
shadowsShort.sort(function(a,b){
return a.dir - b.dir;
});
// clear by drawing background image.
ctx.drawImage(back, 0, 0, canvas.width, canvas.height);
}
// begin drawin the light area
ctx.fillStyle = g; // set the gradient as the light
ctx.beginPath();
for(i = 0; i < shadowsShort.length; i++){ // for each shadow move in to the light across the circle and then back out away from the light
s = shadowsShort[i];
x = s.x1 + Math.cos(s.d1) * boundSize;
y = s.y1 + Math.sin(s.d1) * boundSize;
if (i === 0) { // if the start move to..
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
ctx.lineTo(s.x1, s.y1);
if (s.branch1 !== undefined) { // if braching. (NOTE this is not recursive. the correct solution would to math this a function and use recursion to climb in an out)
s = s.branch1;
x = s.x1 + Math.cos(s.d1) * s.branch1Dist;
y = s.y1 + Math.sin(s.d1) * s.branch1Dist;
ctx.lineTo(x, y);
ctx.lineTo(s.x1, s.y1);
}
ctx.lineTo(s.x2, s.y2);
if (s.branch2 !== undefined) {
x = s.x2 + Math.cos(s.d2) * s.branch2Dist;
y = s.y2 + Math.sin(s.d2) * s.branch2Dist;
ctx.lineTo(x, y);
s = s.branch2;
ctx.lineTo(s.x2, s.y2);
}
x = s.x2 + Math.cos(s.d2) * boundSize;
y = s.y2 + Math.sin(s.d2) * boundSize;
ctx.lineTo(x, y);
// now fill in the light between shadows
s1 = shadowsShort[(i + 1) % shadowsShort.length];
nextDir = s1.d1;
if(rev){
if (nextDir > s.d2) {
nextDir -= Math.PI * 2
}
}else{
if (nextDir < s.d2) {
nextDir += Math.PI * 2
}
}
x = Math.cos((nextDir+s.d2)/2) * boundSize + canvas.width / 2;
y = Math.sin((nextDir+s.d2)/2) * boundSize + canvas.height / 2;
ctx.lineTo(x, y);
}
// close the path.
ctx.closePath();
// set the comp to lighten or multiply
if(rev){
ctx.globalCompositeOperation ="multiply";
}else{
ctx.globalCompositeOperation ="lighter";
}
// draw the gradient
ctx.fill()
ctx.globalCompositeOperation ="source-over";
// draw the circles
for (i = 0; i < circles.length; i++) {
c = circles[i];
ctx.drawImage(circle, c.x - 32 * c.scale, c.y - 32 * c.scale, 64 * c.scale, 64 * c.scale);
}
// feed the herbervors.
window.requestAnimationFrame(update);
}
update();
.canC { width:400px; height:400px;}
<canvas class="canC" id="canV" width=400 height=400></canvas>

Categories

Resources