Using JS, on a canvas, I have a ball moving across the canvas. It works fine.
I have created a button element in HTML and I want the function, "changeBallDirection()" to be called onclick.
changeBallDirection() should change the variable xBallDirection, which should change the direction of the ball.
The variable xBallDirection doesn't change. I printed xBallDirection on the canvas to confirm it wasn't changing. Code is below. First time posting, so please let me know any basic breaches in etiquette I may have committed. Thanks.
<script type="application/processing" data-processing-target="pjs">
void setup() {
size(400, 400);
};
frameRate(20);
var r=0;
var xBallDirection = 1;
var changeBallDirection = function (direction) {
xBallDirection = direction;
};
//draw ball on canvas and move to the right
void draw() {
fill(45, 56, 99);
ellipse(100+r, 34, 34, 34);
r=r+xBallDirection;
//I printed xBallDirection here so that I could see if it changed when the button was pushed
text(xBallDirection,50,50);
};
</script>
<button style="width: 100px; height: 100px; background:blue" onclick="changeBallDirection(-10)">Ball Left</button>
<canvas id="pjs"> </canvas>
changeBallDirection is not in the global namespace and therefore cannot be called within the onclick event.
You should get something like ReferenceError: changeBallDirection is not defined in your console when you click the button.
Move the function outside of the draw() portion of your script and everything should work fine.
Related
I am VERY new to P5.js/processing (taking programming for artists). I am trying to make a crude game where an image (Jar Jar) bounces across the screen and another image (lightsaber) that moves with the mouse and when the mouse attached image goes over the bouncing image then the lightsaber will be mirrored and activate a sound. If this at all makes sense...
I have the bouncing image part down so far, but I am unable to make the mousePressed() function work. like I mentioned, I need the "lightsaber.png" to flip when the mouse is pressed. Also, when the mouse is pressed and is directly over the JarJar image, how would I add a score count and sound event?
Thank you!
here is my code so far:
let jarJar;
let jarJarX=5;
let jarJarY=5;
let xspeed;
let yspeed;
let lightSaber;
function preload() {
jarJar = loadImage('jarjar.png');
lightSaber= loadImage ('lightSaber.png');
}
function setup() {
createCanvas(700,700);
xspeed=random (15,22);
yspeed=random (15,22);
}
function draw() {
background(0);
image (lightSaber,mouseX,mouseY,100,100);
image(jarJar,jarJarX,jarJarY, 140, 200);
jarJarX= jarJarX+xspeed;
if (jarJarX<=-300|| jarJarX>=width+200){
xspeed=xspeed*-1;
}
jarJarY= jarJarY+yspeed;
if (jarJarY<-200|| jarJarY>=height+200 ){
yspeed=yspeed*-1;
}
//picture mirrors when mouse pressed
if mouseClicked(){
scale(-1,1);
image(lightSaber);
}
//score counter coordinate with lightsaber hitting image
//
}
Let it be known that I'm not proficient at javaScript. This said, your question is quite simple so I can help anyway.
Some framework will have simple ways to mirror images. Processing likes to scale with a negative number. I re-coded some of your stuff to accommodate my changes. The main changes goes as follows:
I added a method to draw the lightsaber so we can "animate" it (read: flip it for a couple frames when the user clicks around).
I added a 'score' global variable to track the score, and a way for the user to see that score with the text method.
I added a method called "intersect" which isn't very well coded as it's something I did back when I was a student (please don't hurt me, it works just right so I still use it from time to time). For more details on how simple collisions works, take some time to read this answer I wrote some time ago, there are nice pictures too!
I added a mouseClicked method. This method will act like an event, which means that it will be triggered by a specific call (a left mouse button click in this case). This method contains the code to check for a collision between the squares which are the images. If there's an overlap, the score will increase and jarjar will run in another direction (this part is a bonus to demonstrate that this is the place where you can get creative about the collision).
I commented the code so you can get what I'm doing more easily:
let jarJar;
let jarJarX=5;
let jarJarY=5;
let xspeed;
let yspeed;
let lightSaber;
let flipLength;
let score = 0;
function preload() {
jarJar = loadImage('jarjar.png');
lightSaber= loadImage ('lightSaber.png');
}
function setup() {
createCanvas(700, 700);
runJarJarRun();
}
function draw() {
background(0);
drawLightSaber(); // this way I can deal with the lightsaber's appearance in a dedicated method
image(jarJar, jarJarX, jarJarY, 140, 200);
jarJarX= jarJarX+xspeed;
if (jarJarX<=-300|| jarJarX>=width+200) {
xspeed=xspeed*-1;
}
jarJarY= jarJarY+yspeed;
if (jarJarY<-200|| jarJarY>=height+200 ) {
yspeed=yspeed*-1;
}
//score counter coordinate with lightsaber hitting image
textSize(30);
fill(200, 200, 0);
text('Score: ' + score, 10, 40);
}
function drawLightSaber() {
if (flipLength) { // if the number is > 0 this will be true
flipLength--; // measure how ling the saber is flipped in frames # ~60 frames per second
push(); // isolating the translate ans scale manpulations to avoid ruining the rest of the sketch
translate(mouseX + 100, 0); // makes the coordinates so once flipped the lightsaber will still appear at the same location
scale(-1.0, 1.0); // flip x-axis backwards
image (lightSaber, 0, mouseY, 100, 100);
pop(); // ends the sequence started with 'push();'
} else {
image (lightSaber, mouseX, mouseY, 100, 100);
}
}
function runJarJarRun() {
xspeed=random (5, 10);
yspeed=random (5, 10);
}
function mouseClicked() { // this method will trigger once when the left mouse button is clicked
flipLength = 10;
if (intersect(jarJarX, jarJarY, 140, 200, mouseX, mouseY, 100, 100)) {
score++;
runJarJarRun(); // as a bonus, jarjar will run in another direction on hit
// you could totally put some more special effects, like a flash, a sound, some 'mesa ouchie bad!' text, whatever speaks to you
}
}
function intersect(x1, y1, w1, h1, x2, y2, w2, h2) {
let checkX = false;
let checkY = false;
if ( (x1<x2 && (x1+w1)>x2) || (x1<(x2+w2) && (x1+w1)>x2+w2) || (x1>x2 && (x1+w1)<(x2+w2)) ) {
checkX = true;
}
if ( (y1<y2 && (y1+h1)>y2) || (y1<(y2+h2) && (y1+h1)>y2+h2) || (y1>y2 && (y1+h1)<(y2+h2)) ) {
checkY = true;
}
return (checkX && checkY);
}
If there's something you don't understand, let me know in a comment and I'll be happy to elaborate. Good luck and have fun!
Hi and welcome to stack overflow. One thing to keep in mind when submitting here (or any forum where you're looking for help with code) is to post a minimal reproducible example. You'll be much more likely to get useful responses.
You'll also want to separate out your questions, as they each have multi-step responses.
Your first question is about how to get your sketch to display something when you press the mouse down. Your syntax isn't quite correct there. Here's a minimal example of how to check for a mouse held down.
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
if (mouseIsPressed == true) {
ellipse(100, 100, 100, 100);
}
}
Just a quick note that I tried to make this as 'novice-friendly' as possible. The == true is optional and not usually included.
I am unable to draw rectangles in canvas within for loop in JavaScript. Sometimes they are visible for a fraction of second, but they disappear instantly. No errors in developer console - tested on Firefox & Chrome, so problem isn't browser-related. Seems like only I encountered this issue within my class, despite having exactly the same (or slightly adjusted to my needs) code.
Code was tested on multiple browsers, the problem itself seems to occur only on my laptop/browser - rest of my colleagues haven't encountered this issue, apart from some typos etc.
The general idea is to receive a result similar to this one:
function Draw() {
var l = liczba.value; // user-input based loop control
var xpos = ypos = 300; // Left side rectangles starting drawing position
var xneg = 600, yneg = 300; // Right side rectangles starting drawing position
var canvas = document.getElementById('image');
var context = canvas.getContext('2d');
context.fillStyle = 'red';
for(var i = 0; i < l; i++) {
context.fillRect(xpos, ypos, 100, 100);
context.strokeRect(xpos, ypos, 100, 100);
console.log("Left Canvas Painted!"); // Debug
context.fillRect(xneg, yneg, 100, 100);
context.strokeRect(xneg, yneg, 100, 100);
console.log("Right Canvas Painted!"); // Debug
xpos += 50; xneg -= 50; ypos += 50; yneg += 50; // change next canvas' starting position by...
}
}
<body>
<form method="post" id="f" onsubmit="Draw();">
<label>Podaj liczbę figur do narysowania: </label>
<input type="text" name="liczba" id="liczba">
<input type="submit" name="send" id="send" value="Zatwierdź">
</form>
<canvas id="image" width="1200" height="800">Canvasy nie działają</canvas>
</body>
Try changing the button from a submit button to a normal button and call Draw() on a click event.
<input type="button" onclick="Draw()" name="send" id="send" value="Zatwierdź">
The answer to this issue was pretty straight-forward, thanks to #jcbbdrn - a minor oversight on my part. Due to page reload on form submitting, whole canvas was flushed down the toilet. After implementing a regular button and assigning to it an onclick event, the problem has been solved.
The squares still get drawn even tough they are deleted from the array that is drawn. Shouldn't they be deleted when they are being deleted from the Array. Does the array not update inside the go function?
Javascript:
var canvas;
var ctx;
$(document).ready(function(){
$("#thebutton").on('click',function(){
thearray.pop();
})
canvas = $('#canvas').get(0);
ctx =canvas.getContext("2d");
})
class Square{
constructor(p1,p2,p3,p4){
this.p1=p1;
this.p2=p2;
this.p3=p3;
this.p4=p4;
}
start(){
var that = this;
setTimeout(function(){
that.go();
that.start();
console.log("timeout running");
},1000);
}
go(){
for(let i = 0; i<thearray.length;i++){
console.log("loop running:"+i);
if(true){
ctx.clearRect(0,0,500,500);
console.log("clearing rect");
}
ctx.rect(thearray[i].p1, thearray[i].p2, thearray[i].p3, thearray[i].p4);
ctx.stroke();
}
}
}
var thearray=[];
var thesquare1 = new Square(20,20,150,100);
var thesquare2 = new Square(100,100,200,200);
var thesquare3 = new Square(200,200,300,300);
thearray.push(thesquare1);
thearray.push(thesquare2);
thearray.push(thesquare3);
thesquare1.start();
HTML:
<canvas id="canvas" height="500" width="500"></canvas>
<button type="button" name="button" id="thebutton">Pop Array</button>
Spent almost an hour on debugging your code!
This led to the finding that if fillRect() is used instead of rect() the code works well...
and then finally found this..
(I too didn't know it before ><)
Have a look at this: link
In short, just call beginPath() after clearRect() to start a new path instead of using old path stack!!!
go(){
ctx.clearRect(0,0,500,500);
ctx.beginPath(); //This line saved the day :)))
console.log("clearing rect");
for(let i = 0; i<thearray.length;i++) {
console.log("loop running:"+i);
ctx.rect(thearray[i].p1, thearray[i].p2, thearray[i].p3, thearray[i].p4);
ctx.stroke();
}
}
using this the code works for rect() as intended :)
Note: also you have to move that clearRect() call outside the for loop otherwise it will clear the canvas after every single iteration of the loop which results in showing only the 3rd rectangle on the canvas.
Also, that if(true){} is not at all necessary.
Update: Also checkout this thread for some other alternatives to beginPath() to handle such scenario
A JavaScript beginner here :) Summary: How can I make an event listener depend on theresult of an AJAX call? If the listener is added after the call, it will be added after each call, thus accumulating the listeners. And if I do it before the call, how can I pass the call result to it? Listeners don't seem to take user-defined arguments.
Details: I am trying to write a Web photo viewer where faces would be marked on the photos. To switch between photos, I'd like to use AJAX. My PHP script returns the name of the picture file and the coordinates of faces (x, y, radius) in the form of a JSON string, e.g.
{"filename":"im1.jpg","faces":[{"x":129,"y":260,"radius":40},{"x":232,"y":297,"radius":40}]}
I want to draw circles on a canvas, based on faces, once the mouse is over the photo. Therefore I created a listener for the mouseover event. The problem is that if I add the listener after the AJAX call, the canvas gets multiple listeners and keeps drawing circles from the previous photos. So it looks like the handler needs to be defined in advance. But then I am struggling to pass the AJAX response to it. How could I achieve it?
My HTML code so far looks like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="100" height="600"
style="background-size: 100% 100%; border: 1px solid #FF0000;"></canvas>
<script>
var image = new Image();
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.addEventListener('mouseout', function(evt) {
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("Canvas cleared");
}, false);
function loadXMLDoc() {
var xmlhttp;
if (window.XMLHttpRequest) {// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
}
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var ajaxData = JSON.parse(xmlhttp.responseText);
image.src = ajaxData.filename;
image.onload = function() {
canvas.width = Math.round(canvas.height / image.height * image.width);
canvas.style.backgroundImage = "url('" + image.src + "')";
}
var faces = ajaxData.faces;
canvas.addEventListener('mouseover', function(evt) {
console.log("Canvas entered");
console.log("Faces to mark: " + faces.length);
for (i = 0; i < faces.length; i++) {
context.beginPath();
context.arc(faces[i].x, faces[i].y,
faces[i].radius, 0, 2 * Math.PI);
context.strokeStyle = "#00BBBB";
context.lineWidth = 1;
context.stroke();
}
}, false);
}
}
xmlhttp.open("GET", "get_data.php", true);
xmlhttp.send();
}
</script>
<button type="button" onclick="loadXMLDoc()">Change Content</button>
</body>
</html>
You can remove the old listener before adding a new one. But a better way would be to save the data into a variable outside the event listener, which gets updated after each AJAX response. Then the function that is called by the listener doesn't need to change at all, it just references the variable with your face data.
EDIT
You can add another variable at the top of your script: var faces;. The rest of your script will have access to it, just like your other variables. You don't need to assign a value to it initially. Now put your mouseover listener at the same level as your mouseout listener. When you get your data from the server, just assign it to faces: faces = ajaxData.faces; (don't use var here, or it will define faces as a local variable within your callback). Now the faces variable defined above will have that data, and the listener will have access to it. Every time you make an AJAX call, it will overwrite the old faces with the new. You may want to add a check within the mouseover listener to make sure the variable has data. You can check it this way:
if (typeof faces == 'object'){
// your for loop can go here
}
Before the AJAX callback sets the value of faces, typeof faces will equal 'undefined'.
JavaScript will be confusing until you get a grasp on how the language handles scope. Check this out as a good starting place.
I have this piece of code that will eventually be a little drawing tool in which the user will be able switch between a pencil and an eraser. I am trying to program the eraser at the moment. However, if the user selects the eraser radio button and then selects the pencil radio button(deselects the eraser), the eraser does not 'turn off'.
This is all done with HTML's canvas element and the eraser takes advantage of:
context.globalCompositeOperation="destination-out";
As the code below shows, I thought I might be able to go through and reverse some of the properties depending on which button was clicked, for instance changing the globalCompositeOperation to equal 'source-over' or another property of globalCompositeOperation, however as far as I know, once a property has been set for it the only way to change it is to reset the entire canvas, which would delete everything.
Then I thought I might be able to set the opacity of the brush using:
context.globalAlpha=0;
so that when the pencil tool is selected the eraser tool still erases, but at 0% opacity, so it shouldn't do anything. Unfortunatly, none of this worked.
I'm self taught so although I know a moderate amount about the HTML canvas, I've found it difficult to learn about the JS side of programming radio buttons.
I guess my question is, if some code is activated through a radiobutton onclick event, how do I deactivate the code once the radiobutton is deselected.
Thanks to any suggestions, any help is wanted!
Here's my code:
(sorry about the formatting, stack overflow didn't like my indents)
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div id="main">
<div class="tools" style="position:fixed;">Pencil:<input type="radio" name="toolSelect" onclick="pencilCheck();" id="pencilSelect"></div>
<div class="tools" style="position:fixed; left: 80px;">Rubber:<input type="radio" name="toolSelect" onclick="rubberCheck()" id="rubberSelect"></div>
<canvas id="canvas">This is a web application, you need to update your browser.</canvas><!-- the canvas -->
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
//Makes the canvas the size of the browser window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context.fillStyle = "red";
context.fillRect(40,40,250,100);
function rubberCheck(){
var dragging = false;
var erase = function(rubber){
if(dragging){
context.beginPath();
context.arc(rubber.clientX, rubber.clientY, 10, 0, Math.PI*2);
context.fill();
context.globalCompositeOperation="destination-out";
context.globalAlpha=1;
}
}
var click = function(rubber){
dragging = true;
erase(rubber);
}
var unclick = function(){
dragging = false;
}
canvas.addEventListener('mousedown', click);
canvas.addEventListener('mousemove', erase);
canvas.addEventListener('mouseup', unclick);
}
if(pencilCheck){
var dragging = false;
var erase = function(rubber){
if(dragging){
context.beginPath();
context.arc(rubber.clientX, rubber.clientY, 10, 0, Math.PI*2);
context.fill();
context.globalCompositeOperation="destination-over";
context.globalAlpha=0;
}
}
var click = function(rubber){
dragging = true;
erase(rubber);
}
var unclick = function(){
dragging = false;
}
canvas.addEventListener('mousedown', click);
canvas.addEventListener('mousemove', erase);
canvas.addEventListener('mouseup', unclick);
}
</script>
</body>
You have:
> <input type="radio" name="toolSelect" onclick="pencilCheck();" id="pencilSelect">
but there is no pencilCheck function. Later there is:
> if (pencilCheck) {
The function should probably be called "setTool" or similar. Pass a reference to the button that was clicked to the function using this and add a value attribute that is the tool that the button represents:
<input type="radio" ... value="pencil" onclick="setTool(this);" ...>
Then set the value of a variable to indicate the selected tool, say selectedTool:
// Initial state is no tool selected (?)
var selectedTool = null;
function setTool(el) {
if (el.checked) selectedTool = el.value;
}
Do the same with other radio buttons so selectedTool always represents the currently checked checkbox. Note that you have a reset button, you need to add a listener so when it's clicked you reset the value of selectedTool (that can use a click listener on the button or a reset listener on the form, if you're using a form, but you don't seem to be using one).