How to apply physics to complex shapes? (matter.js + p5.js) - javascript

I've been trying to make a ball pit sim using matter.js + p5.js to use as an interactive website background.
With some help from Mr Shiffman's video I've managed to get it working well with circle shapes, but I want to take it to the next level and use custom blob shapes (taken from the client's logo) and apply the same physics to them.
I've been able to get the custom shapes to render using a combination of p5's Beginshape() and matter's bodies.fromVertices. It sort of works as you can see but the physics is very weird, and the collisions don't seem to match even though I used the same vertices for both.
I think it may be to do with this quote from the p5 docs
Transformations such as translate(), rotate(), and scale() do not work within beginShape().
but I don't know what I can do to get around this as I need them to be able to translate / rotate for the physics to work...
Any ideas? help much appreciated!
Codepen
var fric = .6;
var rest = .7
//blob creation function
function Blob(x, y) {
var options = {
friction: fric,
restitution: rest
}
this.body = Bodies.fromVertices(x,y, blob, options);;
World.add(world, this.body);
this.show = function() {
var pos = this.body.position;
var angle = this.body.angle;
push();
translate(pos.x, pos.y);
rotate(angle);
rectMode(CENTER);
strokeWeight(0);
fill('#546B2E')
beginShape();
curveVertex(10.4235750010825,77.51573373392407);
curveVertex(3.142478233002126,70.89274677890447);
curveVertex(0.09197006398718799,61.45980047762196);
curveVertex(1.1915720013184474,51.59196924554452);
curveVertex(4.497757286928595,42.162760563619436);
curveVertex(5.252622102311041,32.216346235505895);
curveVertex(4.731619980811491,22.230638463608106);
curveVertex(4.748780859149178,12.256964518539956);
curveVertex(8.728313738681376,3.3252404103204602);
curveVertex(17.998080279150148,0.07532797415084502);
curveVertex(27.955564903146588,0.6294681264134124);
curveVertex(37.68448491855515,2.8865688476481735);
curveVertex(46.899804284802386,6.733477319787068);
curveVertex(55.386932458422265,12.031766230704845);
curveVertex(62.886098235421045,18.623827217916812);
curveVertex(69.13243582467831,26.40824364010799);
curveVertex(73.70136375533966,35.2754654128657);
curveVertex(75.90839243871912,44.99927633563314);
curveVertex(74.84120838749334,54.8784706257129);
curveVertex(70.09272040861401,63.61579878615303);
curveVertex(62.590342401896606,70.15080526550207);
curveVertex(53.62552650480876,74.54988781923045);
curveVertex(44.08788115809841,77.55817639102708);
curveVertex(34.30859814694884,79.58860716640554);
curveVertex(24.334764892578125,80.23994384765624);
curveVertex(14.444775242328642,78.88621691226959);
endShape(CLOSE);
pop();
}
}
var clientHeight = document.getElementById('physBox').clientHeight;
var clientWidth = document.getElementById('physBox').clientWidth;
var Engine = Matter.Engine,
World = Matter.World,
Bodies = Matter.Bodies,
Common = Matter.Common,
Composite = Matter.Composite,
Mouse = Matter.Mouse,
MouseConstraint = Matter.MouseConstraint,
Vertices = Matter.Vertices;
var blob = Vertices.fromPath('10.4235750010825 77.51573373392407 3.142478233002126 70.89274677890447 0.09197006398718799 61.45980047762196 1.1915720013184474 51.59196924554452 4.497757286928595 42.162760563619436 5.252622102311041 32.216346235505895 4.731619980811491 22.230638463608106 4.748780859149178 12.256964518539956 8.728313738681376 3.3252404103204602 17.998080279150148 0.07532797415084502 27.955564903146588 0.6294681264134124 37.68448491855515 2.8865688476481735 46.899804284802386 6.733477319787068 55.386932458422265 12.031766230704845 62.886098235421045 18.623827217916812 69.13243582467831 26.40824364010799 73.70136375533966 35.2754654128657 75.90839243871912 44.99927633563314 74.84120838749334 54.8784706257129 70.09272040861401 63.61579878615303 62.590342401896606 70.15080526550207 53.62552650480876 74.54988781923045 44.08788115809841 77.55817639102708 34.30859814694884 79.58860716640554 24.334764892578125 80.23994384765624 14.444775242328642 78.88621691226959');
var engine;
var world;
var blobs =[];
var ground;
var ceiling;
var wallLeft;
var wallRight;
var mConstraint;
//start sim after x time
setTimeout(function setup() {
var cnv = createCanvas(clientWidth, clientHeight);
cnv.parent("physBox");
engine = Engine.create();
world = engine.world;
Engine.run(engine);
//add ground
ground = Bodies.rectangle(clientWidth/2, clientHeight+500, clientWidth, 1000, { isStatic: true });
World.add(world, ground);
//add ceiling
ceiling = Bodies.rectangle(clientWidth/2, -clientHeight-500, clientWidth, 1000, { isStatic: true });
World.add(world, ceiling);
//add left wall
wallLeft = Bodies.rectangle(-500, clientHeight/2, 1000, clientHeight*2, { isStatic: true });
World.add(world, wallLeft);
//add right wall
wallRight = Bodies.rectangle(clientWidth+500, clientHeight/2, 1000, clientHeight*2, { isStatic: true });
World.add(world, wallRight);
//create x bodies
for (var i = 0; i < 4; i++) {
blobs.push(new Blob(clientWidth/2, 100));
}
//mouse controls
var options = {
mouse: canvasmouse
}
var canvasmouse = Mouse.create(cnv.elt);
mConstraint = MouseConstraint.create(engine);
World.add(world,mConstraint);
}, 2000);
function draw() {
background('#EEF2FD');
//show all bodies
for (var i = 0; i < blobs.length; i++) {
blobs[i].show();
}
}
body, html {
overflow: hidden;
padding:0;
margin:0;
}
h1 {
font-family: sans-serif;
font-size: 4vw;
background: none;
position: absolute;
margin-top:10%;
margin-left: 10%;
user-select: none;
}
#physBox {
width: 100%;
height: 100vh;
padding: 0;
left: 0;
z-index: -1;
box-sizing: border-box;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.2.1/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script>
<section id="physBox">
<h1> Welcome to my website</h1>
</section>
Note: I originally tried doing this all with SVGs instead of custom shapes but struggled to understand how to make it work so I gave up, if someone could help me solve this using SVGs instead i'd be happy with that! Also, apologies for my terrible code formatting - I'm learning ;)

The root issue here is that matter.js positions objects based on their center of mass rather than the origin of the coordinate system that your vertices are in, and it is clear that the origin of the coordinate system for you blob vertices is not its center of mass since all of your vertices are positive. You can calculate the center of mass for your blob and then use that offset before drawing:
const fric = 0.6;
const rest = 0.7;
const {
Engine,
Runner,
World,
Bodies,
Body,
Common,
Composite,
Mouse,
MouseConstraint,
Vertices
} = Matter;
const blob = Vertices.fromPath('10.4235750010825 77.51573373392407 3.142478233002126 70.89274677890447 0.09197006398718799 61.45980047762196 1.1915720013184474 51.59196924554452 4.497757286928595 42.162760563619436 5.252622102311041 32.216346235505895 4.731619980811491 22.230638463608106 4.748780859149178 12.256964518539956 8.728313738681376 3.3252404103204602 17.998080279150148 0.07532797415084502 27.955564903146588 0.6294681264134124 37.68448491855515 2.8865688476481735 46.899804284802386 6.733477319787068 55.386932458422265 12.031766230704845 62.886098235421045 18.623827217916812 69.13243582467831 26.40824364010799 73.70136375533966 35.2754654128657 75.90839243871912 44.99927633563314 74.84120838749334 54.8784706257129 70.09272040861401 63.61579878615303 62.590342401896606 70.15080526550207 53.62552650480876 74.54988781923045 44.08788115809841 77.55817639102708 34.30859814694884 79.58860716640554 24.334764892578125 80.23994384765624 14.444775242328642 78.88621691226959');
// from http://paulbourke.net/geometry/polygonmesh/
function computeArea(vertices) {
let area = 0;
for (let i = 0; i < vertices.length - 1; i++) {
let v = vertices[i];
let vn = vertices[i + 1];
area += (v.x * vn.y - vn.x * v.y) / 2;
}
return area;
}
function computeCenter(vertices) {
let area = computeArea(vertices);
let cx = 0,
cy = 0;
for (let i = 0; i < vertices.length - 1; i++) {
let v = vertices[i];
let vn = vertices[i + 1];
cx += (v.x + vn.x) * (v.x * vn.y - vn.x * v.y) / (6 * area);
cy += (v.y + vn.y) * (v.x * vn.y - vn.x * v.y) / (6 * area);
}
return {
x: cx,
y: cy
};
}
const center = computeCenter(blob);
let engine;
let world;
let blobs = [];
let ground;
let ceiling;
let wallLeft;
let wallRight;
let mConstraint;
//blob creation function
function Blob(x, y) {
let options = {
friction: fric,
restitution: rest
}
this.body = Bodies.fromVertices(x, y, blob, options);
World.add(world, this.body);
// Scales the body around the center
Body.scale(this.body, 0.5, 0.5);
this.show = function() {
var pos = this.body.position;
var angle = this.body.angle;
push();
translate(pos.x, pos.y);
rotate(angle);
scale(0.5, 0.5);
translate(-center.x, -center.y);
strokeWeight(0);
fill('#546B2E')
beginShape();
for (const {
x,
y
} of blob) {
curveVertex(x, y);
}
endShape(CLOSE);
pop();
// Alternately, when drawing your blobs you could use
// the bodies vertices, but it looks like these are
// converted into a convex polygon.
push();
stroke('red');
strokeWeight(1);
noFill();
beginShape();
for (const {
x,
y
} of this.body.vertices) {
curveVertex(x, y);
}
endShape(CLOSE);
pop();
}
}
//start sim after x time
function setup() {
const cnv = createCanvas(windowWidth, Math.max(windowHeight, 300));
engine = Engine.create();
world = engine.world;
const runner = Runner.create();
Runner.run(runner, engine);
//add ground
ground = Bodies.rectangle(width / 2, height, width, 50, {
isStatic: true
});
World.add(world, ground);
//add ceiling
ceiling = Bodies.rectangle(width / 2, 0, width, 50, {
isStatic: true
});
World.add(world, ceiling);
//add left wall
wallLeft = Bodies.rectangle(0, height / 2, 50, height, {
isStatic: true
});
World.add(world, wallLeft);
//add right wall
wallRight = Bodies.rectangle(width, height / 2, 50, height, {
isStatic: true
});
World.add(world, wallRight);
//create x bodies
for (let i = 0; i < 4; i++) {
blobs.push(new Blob(random(50, width - 100), random(50, height - 100)));
}
}
function draw() {
background('#EEF2FD');
//show all bodies
for (var i = 0; i < blobs.length; i++) {
blobs[i].show();
}
}
function mouseClicked() {
blobs.push(new Blob(mouseX, mouseY));
}
html,
body {
margin: 0;
overflow-x: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.2.1/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Related

adding snow effect to specific div only

I am using code from https://www.kirupa.com/html5/the_falling_snow_effect.htm and well as it turns out the snow is falling all over my page and not just the banner div...
// Array to store our Snowflake objects
var snowflakes = [];
// Global variables to store our browser's window size
var browserWidth;
var browserHeight;
// Specify the number of snowflakes you want visible
var numberOfSnowflakes = 50;
// Flag to reset the position of the snowflakes
var resetPosition = false;
// Handle accessibility
var enableAnimations = false;
var reduceMotionQuery = matchMedia("(prefers-reduced-motion)");
// Handle animation accessibility preferences
function setAccessibilityState() {
if (reduceMotionQuery.matches) {
enableAnimations = false;
} else {
enableAnimations = true;
}
}
setAccessibilityState();
reduceMotionQuery.addListener(setAccessibilityState);
//
// It all starts here...
//
function setup() {
if (enableAnimations) {
window.addEventListener("DOMContentLoaded", generateSnowflakes, false);
window.addEventListener("resize", setResetFlag, false);
}
}
setup();
//
// Constructor for our Snowflake object
//
function Snowflake(element, speed, xPos, yPos) {
// set initial snowflake properties
this.element = element;
this.speed = speed;
this.xPos = xPos;
this.yPos = yPos;
this.scale = 1;
// declare variables used for snowflake's motion
this.counter = 0;
this.sign = Math.random() < 0.5 ? 1 : -1;
// setting an initial opacity and size for our snowflake
this.element.style.opacity = (.9 + Math.random()) / 3;
}
//
// The function responsible for actually moving our snowflake
//
Snowflake.prototype.update = function() {
// using some trigonometry to determine our x and y position
this.counter += this.speed / 5000;
this.xPos += this.sign * this.speed * Math.cos(this.counter) / 40;
this.yPos += Math.sin(this.counter) / 40 + this.speed / 30;
this.scale = .5 + Math.abs(10 * Math.cos(this.counter) / 20);
// setting our snowflake's position
setTransform(Math.round(this.xPos), Math.round(this.yPos), this.scale, this.element);
// if snowflake goes below the browser window, move it back to the top
if (this.yPos > browserHeight) {
this.yPos = -50;
}
}
//
// A performant way to set your snowflake's position and size
//
function setTransform(xPos, yPos, scale, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0) scale(${scale}, ${scale})`;
}
//
// The function responsible for creating the snowflake
//
function generateSnowflakes() {
// get our snowflake element from the DOM and store it
var originalSnowflake = document.querySelector(".snowflake");
// access our snowflake element's parent container
var snowflakeContainer = originalSnowflake.parentNode;
snowflakeContainer.style.display = "block";
// get our browser's size
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
// create each individual snowflake
for (var i = 0; i < numberOfSnowflakes; i++) {
// clone our original snowflake and add it to snowflakeContainer
var snowflakeClone = originalSnowflake.cloneNode(true);
snowflakeContainer.appendChild(snowflakeClone);
// set our snowflake's initial position and related properties
var initialXPos = getPosition(50, browserWidth);
var initialYPos = getPosition(50, browserHeight);
var speed = 5 + Math.random() * 40;
// create our Snowflake object
var snowflakeObject = new Snowflake(snowflakeClone,
speed,
initialXPos,
initialYPos);
snowflakes.push(snowflakeObject);
}
// remove the original snowflake because we no longer need it visible
snowflakeContainer.removeChild(originalSnowflake);
moveSnowflakes();
}
//
// Responsible for moving each snowflake by calling its update function
//
function moveSnowflakes() {
if (enableAnimations) {
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.update();
}
}
// Reset the position of all the snowflakes to a new value
if (resetPosition) {
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.xPos = getPosition(50, browserWidth);
snowflake.yPos = getPosition(50, browserHeight);
}
resetPosition = false;
}
requestAnimationFrame(moveSnowflakes);
}
//
// This function returns a number between (maximum - offset) and (maximum + offset)
//
function getPosition(offset, size) {
return Math.round(-1 * offset + Math.random() * (size + 2 * offset));
}
//
// Trigger a reset of all the snowflakes' positions
//
function setResetFlag(e) {
resetPosition = true;
}
#snowflakeContainer {
position: absolute;
left: 0px;
top: 0px;
display: none;
}
.snowflake {
position: fixed;
background-color: #ffffff;
user-select: none;
z-index: 1000;
pointer-events: none;
border-radius: 50%;
width: 10px;
height: 10px;
}
<div class="mainbanner">
<div id="snowflakeContainer">
<span class="snowflake"></span>
</div>
<br>
<center>
<p class="topText" style="font-size:8vw;"> Welcome to the ultimate <br>sleepover experience</p><br><br><br>
<p class="topText" style="font-size:4vw;"> By Ultimate Teepee Party</p>
<br><br><br>
<a class="btn_1" href="book.html">Book Your Party</a></center>
</div>
There has to be a way to make it show only inside that banner div and not the whole page?
if you take a look at snow demo page with inspect element, you will realise that the author embedded a whole html page into the page instead of using a div, the idea is that javascript browserHeight = document.documentElement.clientHeight refers to browser width, therefore unless modifying JS, the page only works in whole page, I could see no effect in your original code since the relative positioning is not handled well. refers to following working demo, which allows you to display the snow in whole page. if you only want the effect to apply to a certain element, try modifying the script calculation based on element position. or you can do what the author did, embed a doc into whole page
JsFiddle
<style>
#snowflakeContainer {
position: fixed;
left: 0px;
top: 0px;
display: none;
width: 100vw;
height: 100vh;
}
.snowflake {
position: fixed;
background-color: #CCC;
user-select: none;
z-index: 1000;
pointer-events: none;
border-radius: 50%;
width: 10px;
height: 10px;
background: red;
}
.banner {
position: fixed;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.title {
font-size: 8vw;
}
.author {
font-size: 4vw;
}
</style>
<div id="snowflakeContainer">
<span class="snowflake"></span>
<div class="banner">
<div class="title">
Welcome to the ultimate
</div>
<div class="title">
sleepover experience
</div>
<br />
<br />
<br />
<div class="author">
By Ultimate Teepee Party
</div>
<br />
<br />
<br />
<a class="btn_1" href="book.html">Book Your Party</a>
</div>
</div>
<script>
// Array to store our Snowflake objects
var snowflakes = [];
// Global variables to store our browser's window size
var browserWidth;
var browserHeight;
// Specify the number of snowflakes you want visible
var numberOfSnowflakes = 50;
// Flag to reset the position of the snowflakes
var resetPosition = false;
// Handle accessibility
var enableAnimations = false;
var reduceMotionQuery = matchMedia("(prefers-reduced-motion)");
// Handle animation accessibility preferences
function setAccessibilityState() {
if (reduceMotionQuery.matches) {
enableAnimations = false;
} else {
enableAnimations = true;
}
}
setAccessibilityState();
reduceMotionQuery.addListener(setAccessibilityState);
//
// It all starts here...
//
function setup() {
if (enableAnimations) {
window.addEventListener("DOMContentLoaded", generateSnowflakes, false);
window.addEventListener("resize", setResetFlag, false);
}
}
setup();
//
// Constructor for our Snowflake object
//
function Snowflake(element, speed, xPos, yPos) {
// set initial snowflake properties
this.element = element;
this.speed = speed;
this.xPos = xPos;
this.yPos = yPos;
this.scale = 1;
// declare variables used for snowflake's motion
this.counter = 0;
this.sign = Math.random() < 0.5 ? 1 : -1;
// setting an initial opacity and size for our snowflake
this.element.style.opacity = (.1 + Math.random()) / 3;
}
//
// The function responsible for actually moving our snowflake
//
Snowflake.prototype.update = function() {
// using some trigonometry to determine our x and y position
this.counter += this.speed / 5000;
this.xPos += this.sign * this.speed * Math.cos(this.counter) / 40;
this.yPos += Math.sin(this.counter) / 40 + this.speed / 30;
this.scale = .5 + Math.abs(10 * Math.cos(this.counter) / 20);
// setting our snowflake's position
setTransform(Math.round(this.xPos), Math.round(this.yPos), this.scale, this.element);
// if snowflake goes below the browser window, move it back to the top
if (this.yPos > browserHeight) {
this.yPos = -50;
}
}
//
// A performant way to set your snowflake's position and size
//
function setTransform(xPos, yPos, scale, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0) scale(${scale}, ${scale})`;
}
//
// The function responsible for creating the snowflake
//
function generateSnowflakes() {
// get our snowflake element from the DOM and store it
var originalSnowflake = document.querySelector(".snowflake");
// access our snowflake element's parent container
var snowflakeContainer = originalSnowflake.parentNode;
snowflakeContainer.style.display = "block";
// get our browser's size
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
// create each individual snowflake
for (var i = 0; i < numberOfSnowflakes; i++) {
// clone our original snowflake and add it to snowflakeContainer
var snowflakeClone = originalSnowflake.cloneNode(true);
snowflakeContainer.appendChild(snowflakeClone);
// set our snowflake's initial position and related properties
var initialXPos = getPosition(50, browserWidth);
var initialYPos = getPosition(50, browserHeight);
var speed = 5 + Math.random() * 40;
// create our Snowflake object
var snowflakeObject = new Snowflake(snowflakeClone,
speed,
initialXPos,
initialYPos);
snowflakes.push(snowflakeObject);
}
// remove the original snowflake because we no longer need it visible
snowflakeContainer.removeChild(originalSnowflake);
moveSnowflakes();
}
//
// Responsible for moving each snowflake by calling its update function
//
function moveSnowflakes() {
if (enableAnimations) {
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.update();
}
}
// Reset the position of all the snowflakes to a new value
if (resetPosition) {
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.xPos = getPosition(50, browserWidth);
snowflake.yPos = getPosition(50, browserHeight);
}
resetPosition = false;
}
requestAnimationFrame(moveSnowflakes);
}
//
// This function returns a number between (maximum - offset) and (maximum + offset)
//
function getPosition(offset, size) {
return Math.round(-1 * offset + Math.random() * (size + 2 * offset));
}
//
// Trigger a reset of all the snowflakes' positions
//
function setResetFlag(e) {
resetPosition = true;
}
</script>

Trigger a javascript function when button is clicked and stop when clicked again

So I wish to add an effect to my website of floating particles. Came across this codepen which does the exact thing - https://codepen.io/OliverKrieger/pen/EjLEVX.
I've been trying to change it a bit so that the particles only trigger when a checkbox is checked. I understand it might've been already answered on SO but in the codepen code, there is an window.onload function which automatically fires it when window is loaded. I want there to be a checkbox instead.
I tried putting the following in html -
<input type="checkbox" id="switch" onchange="function()"">
<label for="switch" >Toggle</label>
But it seems like the script still triggers automatically. Can someone help me with this please? I'm new to programming so I apologize for any vagueness. Please lemme know if I can provide more details. Any input would be really appreciated.
In your code, function is a keyword and not a function name. The function is still run as soon as the page loads because of the way it is defined in the javascript, and your HTML addition has no effect on this.
You need to change your function into a named function, declared using the function keyword:
function functionName() { ... }
And then you can call it by its name in your HTML:
<input type="checkbox" id="switch" onchange="functionName()">
This should work
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
$(window).resize(function() {
ParticleCanvas.width = ($(window).width() - 20);
ParticleCanvas.height = ($(window).height() - 10);
});
$(function() {
let request = null;
$("#switch").change(function() {
if (this.checked) {
animateDust();
} else {
window.cancelAnimationFrame(request);
}
})
var ParticleCanvas = document.getElementById("ParticleCanvas");
var context = ParticleCanvas.getContext("2d");
ParticleCanvas.width = ($(window).width() - 20);
ParticleCanvas.height = ($(window).height() - 10);
$("switch").click(function() {
animateDust();
})
// All the info stored into an array for the particles
var particles = {},
particleIndex = 0,
settings = {
density: 20,
particleSize: 2,
startingX: ParticleCanvas.width / 2,
startingY: ParticleCanvas.height,
gravity: -0.01
};
// Set up a function to create multiple particles
function Particle() {
// Establish starting positions and velocities from the settings array, the math random introduces the particles being pointed out from a random x coordinate
this.x = settings.startingX * (Math.random() * 10);
this.y = settings.startingY;
// Determine original X-axis speed based on setting limitation
this.vx = (Math.random() * 2 / 3) - (Math.random() * 3 / 3);
this.vy = -(Math.random() * 5 / 3);
// Add new particle to the index
// Object used as it's simpler to manage that an array
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = 200;
this.alpha = 1;
this.red = 0;
this.green = 255;
this.blue = 255;
}
// Some prototype methods for the particle's "draw" function
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
// Adjust for gravity
this.vy += settings.gravity;
// Age the particle
this.life++;
this.red += 2;
this.alpha -= 0.005;
// If Particle is old, it goes in the chamber for renewal
if (this.life >= this.maxLife) {
delete particles[this.id];
}
// Create the shapes
context.clearRect(settings.leftWall, settings.groundLevel, ParticleCanvas.width, ParticleCanvas.height);
context.beginPath();
context.fillStyle = "rgba(" + this.red + ", " + this.green + ", " + this.blue + ", " + this.alpha + ")";
// Draws a circle of radius 20 at the coordinates 100,100 on the ParticleCanvas
context.arc(this.x, this.y, settings.particleSize, 0, Math.PI * 2, true);
context.closePath();
context.fill();
}
function animateDust() {
context.clearRect(0, 0, ParticleCanvas.width, ParticleCanvas.height);
// Draw the particles
for (var i = 0; i < settings.density; i++) {
if (Math.random() > 0.97) {
// Introducing a random chance of creating a particle
// corresponding to an chance of 1 per second,
// per "density" value
new Particle();
}
}
for (var i in particles) {
particles[i].draw();
}
request = window.requestAnimationFrame(animateDust);
}
})
body {
background-color: #000000;
color: #555555;
margin: 0;
padding: 0;
}
#ParticleCanvas {
border: 1px solid white;
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
#container {
background: white;
padding: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="ParticleCanvas"></canvas>
<div id="container">
<form>
<input type="checkbox" id="switch" name="switch">
</form>
<label for="switch">Toggle</label>
</div>

Not able to get output in html svg

I am working on a project where i need to create resizable rectangles on my image.
I found this codepen useful:
https://codepen.io/taye/pen/xEJeo
the problem is when i write same code in my codepen, its not working same. see
here
https://codepen.io/KushalParikh/pen/rJOeOy.
Can you please tell me what I am missing. I am new to javascript and svg.
my code
var svgCanvas = document.querySelector('svg'),
svgNS = 'http://www.w3.org/2000/svg',
rectangles = [];
function Rectangle (x, y, w, h, svgCanvas) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.stroke = 5;
this.el = document.createElementNS(svgNS, 'rect');
this.el.setAttribute('data-index', rectangles.length);
this.el.setAttribute('class', 'edit-rectangle');
rectangles.push(this);
this.draw();
svgCanvas.appendChild(this.el);
}
Rectangle.prototype.draw = function () {
this.el.setAttribute('x', this.x + this.stroke / 2);
this.el.setAttribute('y', this.y + this.stroke / 2);
this.el.setAttribute('width' , this.w - this.stroke);
this.el.setAttribute('height', this.h - this.stroke);
this.el.setAttribute('stroke-width', this.stroke);
}
interact('.edit-rectangle')
// change how interact gets the
// dimensions of '.edit-rectangle' elements
.rectChecker(function (element) {
// find the Rectangle object that the element belongs to
var rectangle = rectangles[element.getAttribute('data-index')];
// return a suitable object for interact.js
return {
left : rectangle.x,
top : rectangle.y,
right : rectangle.x + rectangle.w,
bottom: rectangle.y + rectangle.h
};
})
.inertia({
// don't jump to the resume location
// https://github.com/taye/interact.js/issues/13
zeroResumeDelta: true
})
.restrict({
// restrict to a parent element that matches this CSS selector
drag: 'svg',
// only restrict before ending the drag
endOnly: true,
// consider the element's dimensions when restricting
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
})
.draggable({
max: Infinity,
onmove: function (event) {
var rectangle = rectangles[event.target.getAttribute('data-index')];
rectangle.x += event.dx;
rectangle.y += event.dy;
rectangle.draw();
}
})
.resizable({
max: Infinity,
onmove: function (event) {
var rectangle = rectangles[event.target.getAttribute('data-index')];
rectangle.w = Math.max(rectangle.w + event.dx, 10);
rectangle.h = Math.max(rectangle.h + event.dy, 10);
rectangle.draw();
}
});
interact.maxInteractions(Infinity);
for (var i = 0; i < 5; i++) {
new Rectangle(50 + 100 * i, 80, 80, 80, svgCanvas);
}
svg {
width: 100%;
height: 240px;
background-color: #2e9;
-ms-touch-action: none;
touch-action: none;
}
.edit-rectangle {
fill: #92e;
stroke: black;
}
body { margin: 0; }
<svg>
</svg>
Indeed your pen is not working properly. You forgot to add interact.js to your pen as necessary javascript resource. Just go to Settings|Javascript and add something like https://c4d6f7d727e094887e93-4ea74b676357550bd514a6a5b344c625.ssl.cf2.rackcdn.com/interact-1.1.1.min.js
You need to import
https://c4d6f7d727e094887e93-4ea74b676357550bd514a6a5b344c625.ssl.cf2.rackcdn.com/interact-1.1.1.min.js
in your JS

Text is vertically stretched on my canvas, making it unreadable

I have a canvas that I am drawing on top of a OpenLayers map. The canvas is colored in gradient colors and now I am trying to add text on top of it.
However the text seems to be stretched making it completely unreadable.
function createSpeedBar(min, max, speeds) {
//List of integer speeds
var fullSpeeds = [];
//List of unique speed values (no duplicates)
var uniqueSpeeds = [];
//Filling fullSpeeds using minimum and maximum values
for (i = min; i <= max; i++) {
fullSpeeds.push(i);
}
//Filling uniqueSpeeds with unique values
$.each(speeds, function (i, el) {
if ($.inArray(el, uniqueSpeeds) === -1) uniqueSpeeds.push(el);
});
//Sorting uniqueSpeeds (low to high)
uniqueSpeeds = uniqueSpeeds.sort(function (a, b) {
return a - b;
});
//Getting canvas element
var canvas = document.getElementById("speedList");
var context = canvas.getContext("2d");
context.rect(0, 0, canvas.width, canvas.height);
var grd = context.createLinearGradient(0, 0, 0, 170);
var hslMin = 0;
var hslMax = 240;
//Defining hslColors using uniqueSpeeds
var hslValues = uniqueSpeeds.map(function (value) {
if ($.inArray(value, fullSpeeds)){
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: true,
speed: value
};
} else {
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: false
};
};
});
var count = 1;
var length = hslValues.length;
//Gradient coloring using hslColors
hslValues.forEach(function (value) {
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
grd.addColorStop(count / length, color)
count += 1;
});
context.fillStyle = grd;
context.fill();
//Setting up coloring and drawing of text
count = 1
var height = canvas.height;
var width = canvas.width;
var elementHeight = height / length;
//Drawing text on canvas
hslValues.forEach(function (value) {
context.font = "12px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "black";
if (value.full === true) {
context.fillText(value.speed, width / 2, ((elementHeight / 2) + elementHeight * count));
}
count += 1;
});
}
As it might be clear I am trying to create a bar displaying the intensities of the speed on the map where I have colored some markers. However the text on the canvas comes out like this:
Right now I have made the height of the canvas inherit the height of the map which is 500. The width of the canvas is set manually using css:
html
<div id="map" class="map">
<canvas id="speedList"></canvas>
</div>
css
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
width: 20px;
z-index: 1000;
height: inherit;
margin: 0;
padding: 0;
}
I am currently working on a Fiddle, but it takes a little time to reproduce, and I bet the issue is not that big, so hopefully someone knows how to fix it before I finish the Fiddle.
The main problem here is the CSS adjustment of the width and height of the canvas element.
If it helps to understand the problem, think of <canvas> the same way you would think of <img/>, if you take an img, and give it a width and height of 50 x 500, it would stretch too.
The fix is to ensure that you set the width an height of the canvas element itself, before it processes it's content, like this:
<canvas id="speedList" width="20" height="500"></canvas>
You then also need to make sure your remove the width and height properties inside your CSS, like this:
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
z-index: 1000;
margin: 0;
padding: 0;
}

Draw clickable grid of 1 million squares

I need to find a way to draw a 1000x1000 squares grid, each square is clickable and they must be independently color changeable. Like mines game. I can use HTML (pure or using Canvas or SVG), CSS and JavaScript for this.
I know how to create one grid with these characteristics with JavaScript and CSS, it does well with 10x10 squares, with 100x100 the squares will turn into tall rectangles and 1000x1000 it loads, but the "squares" are soo much compressed that borders meet each other and renders a full gray page.
I tried using HTML and JavaScript to draw SVG squares, the squares' size problem solves, but I don't know how to make they change color when clicked and when I set to load 1000x1000 squares it will freeze the browse and eventually crash the tab.
Is this feasible in any way?
EDIT
Sorry if I wasn't clear, but yes, I need scroll bars in that. They are no problem for me.
You can see the two trials I described here:
JavaScript and CSS
var lastClicked;
var grid = clickableGrid(100,100,function(el,row,col,i){
console.log("You clicked on element:",el);
console.log("You clicked on row:",row);
console.log("You clicked on col:",col);
console.log("You clicked on item #:",i);
el.className='clicked';
if (lastClicked) lastClicked.className='';
lastClicked = el;
});
document.body.appendChild(grid);
function clickableGrid( rows, cols, callback ){
var i=0;
var grid = document.createElement('table');
grid.className = 'grid';
for (var r=0;r<rows;++r){
var tr = grid.appendChild(document.createElement('tr'));
for (var c=0;c<cols;++c){
var cell = tr.appendChild(document.createElement('td'));
++i;
cell.addEventListener('click',(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),false);
}
}
return grid;
}
.grid { margin:1em auto; border-collapse:collapse }
.grid td {
cursor:pointer;
width:30px; height:30px;
border:1px solid #ccc;
}
.grid td.clicked {
background-color:gray;
}
JavaScript and HTML
document.createSvg = function(tagName) {
var svgNS = "http://www.w3.org/2000/svg";
return this.createElementNS(svgNS, tagName);
};
var numberPerSide = 20;
var size = 10;
var pixelsPerSide = 400;
var grid = function(numberPerSide, size, pixelsPerSide, colors) {
var svg = document.createSvg("svg");
svg.setAttribute("width", pixelsPerSide);
svg.setAttribute("height", pixelsPerSide);
svg.setAttribute("viewBox", [0, 0, numberPerSide * size, numberPerSide * size].join(" "));
for(var i = 0; i < numberPerSide; i++) {
for(var j = 0; j < numberPerSide; j++) {
var color1 = colors[(i+j) % colors.length];
var color2 = colors[(i+j+1) % colors.length];
var g = document.createSvg("g");
g.setAttribute("transform", ["translate(", i*size, ",", j*size, ")"].join(""));
var number = numberPerSide * i + j;
var box = document.createSvg("rect");
box.setAttribute("width", size);
box.setAttribute("height", size);
box.setAttribute("fill", color1);
box.setAttribute("id", "b" + number);
g.appendChild(box);
svg.appendChild(g);
}
}
svg.addEventListener(
"click",
function(e){
var id = e.target.id;
if(id)
alert(id.substring(1));
},
false);
return svg;
};
var container = document.getElementById("container");
container.appendChild(grid(100, 10, 2000, ["gray", "white"]));
<div id="container">
</div>
I will be trying implementing the given answers and ASAP I'll accept or update this question. Thanks.
SOLUTION
Just to record, I managed to do it using canvas to draw the grid and the clicked squares and added an event listener to know where the user clicks.
Here is the code in JavaScript and HTML:
function getSquare(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: 1 + (evt.clientX - rect.left) - (evt.clientX - rect.left)%10,
y: 1 + (evt.clientY - rect.top) - (evt.clientY - rect.top)%10
};
}
function drawGrid(context) {
for (var x = 0.5; x < 10001; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 10000);
}
for (var y = 0.5; y < 10001; y += 10) {
context.moveTo(0, y);
context.lineTo(10000, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
function fillSquare(context, x, y){
context.fillStyle = "gray"
context.fillRect(x,y,9,9);
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
drawGrid(context);
canvas.addEventListener('click', function(evt) {
var mousePos = getSquare(canvas, evt);
fillSquare(context, mousePos.x, mousePos.y)
}, false);
<body>
<canvas id="myCanvas" width="10000" height="10000"></canvas>
</body>
Generating such a large grid with HTML is bound to be problematic.
Drawing the grid on a Canvas and using a mouse-picker technique to determine which cell was clicked would be much more efficient.
This would require 1 onclick and/or hover event instead of 1,000,000.
It also requires much less HTML code.
I wouldn't initialize all the squares right off, but instead as they are clicked -
(function() {
var divMain = document.getElementById('main'),
divMainPosition = divMain.getBoundingClientRect(),
squareSize = 4,
square = function(coord) {
var x = coord.clientX - divMainPosition.x + document.body.scrollLeft +
document.documentElement.scrollLeft,
y = coord.clientY - divMainPosition.y + document.body.scrollTop +
document.documentElement.scrollTop;
return {
x:Math.floor(x / squareSize),
y:Math.floor(y / squareSize)
}
}
divMain.addEventListener('click', function(evt) {
var sqr = document.createElement('div'),
coord = square(evt);
sqr.className = 'clickedSquare';
sqr.style.width = squareSize + 'px';
sqr.style.height = squareSize + 'px';
sqr.style.left = (coord.x * squareSize) + 'px';
sqr.style.top = (coord.y * squareSize) + 'px';
sqr.addEventListener('click', function(evt) {
console.log(this);
this.parentNode.removeChild(this);
evt.stopPropagation();
});
this.appendChild(sqr);
});
}());
#main {
width:4000px;
height:4000px;
background-color:#eeeeee;
position:relative;
}
.clickedSquare {
background-color:#dd8888;
position:absolute;
}
<div id="main">
</div>
Uses CSS positioning to determine which square was clicked on,
doesn't initialize a square until it's needed.
Granted I imagine this would start to have a negative impact to use r experience, but that would ultimately depend on their browser and machine.
Use the same format you noramlly use, but add this:
sqauareElement.height = 10 //height to use
squareElement.width = 10 //width to use
This will add quite a large scroll due to the size, but it's the only logical explanation I can come up with.
The canvas approach is fine, but event delegation makes it possible to do this with a table or <div> elements with a single listener:
const tbodyEl = document.querySelector("table tbody");
tbodyEl.addEventListener("click", event => {
const cell = event.target.closest("td");
if (!cell || !tbodyEl.contains(cell)) {
return;
}
const row = +cell.getAttribute("data-row");
const col = +cell.getAttribute("data-col");
console.log(row, col);
});
const rows = 100;
const cols = 100;
for (let i = 0; i < rows; i++) {
const rowEl = document.createElement("tr");
tbodyEl.appendChild(rowEl);
for (let j = 0; j < cols; j++) {
const cellEl = document.createElement("td");
rowEl.appendChild(cellEl);
cellEl.classList.add("cell");
cellEl.dataset.row = i;
cellEl.dataset.col = j;
}
}
.cell {
height: 4px;
width: 4px;
cursor: pointer;
border: 1px solid black;
}
table {
border-collapse: collapse;
}
<table><tbody></tbody></table>

Categories