canvas with mouse that moves ball - javascript

I have a mouse image and a cheese image with line in-between. Works fine. Does nothing except look good.
I added a ball that follows the user mouse and it cleared all my images. I tried to add layers(I am a total newbie).
I want the mouse user to be able to lead just the ball on top of the mouse image to the cheese. After I get that working, I will have the cheese turn into a separate image.
The mouse user can lead the ball anywhere. It does not have to just be on the line in-between the two images.
<!DOCTYPE html>
<title>Mouse Event</title>
<style>
canvas {
border: #333 10px solid;
}
</style>
<div style = "position: relative;">
<canvas id = "layer1" width="600px" height="600px"
style="position: absolute; left: 0; top: 0; z-index: 0;></canvas>
<canvas id = "layer2" width="600px" height="600px"
style="position: absolute; left: 0; top: 0; z-index: 1;></canvas>
<script>
var canvas1 = document.querySelector("#layer1");
var context = canvas1.getContext("2d");
//Get the mouse position
//First, listen for the mouse event & call setMousePosition
//This function assigns the current horizontal and vertical mouse
//position to the mouseX,Y properties, it relies on the clientX
//and Y properties that the MouseEvent-based event arument object provides
var canvasPos = getPosition(canvas);
var mouseX = 500;
var mouseY = 500;
canvas1.addEventListener("mousemove", setMousePosition, false);
function setMousePosition(e) {
mouseX = e.clientX - canvasPos.x;//now stores the position returned by the getPosition function
mouseY = e.clientY - canvasPos.y;
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);//clears earlier positions
context.beginPath();
context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
context.fillStyle = "LightSeaGreen";
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.fill();
context.stroke();
requestAnimationFrame(update);
}
//Get the Exact Mouse Position
function getPosition(el) {
var xPosition = 0;
var yPosition = 0;
while (el) {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
el = el.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
}
update();
var canvas2 = document.querySelector("#layer2");
var context = canvas2.getContext("2d");
//draw connecting line
context.moveTo(30,30);
context.bezierCurveTo(-50,600, 500, 0, 300,500);
context.lineWidth = 15;
context.strokeStyle = "teal";
context.stroke();
context.fillStyle = "#ff6600";
context.font = "bold 35px 'Book Antiqua'";
context.fillText("Help Me", 300,100);
context.fillText("find the cheese,", 300, 135);
context.fillText("Please!", 300, 170);
//load cheeseImage
var cheeseImage = new Image();
cheeseImage.src = "images/transparentCheese.png";
cheeseImage.addEventListener("load", loadImage, false);
//load mouse image
var mouseImage = new Image();
mouseImage.src = "images/mouse.png";
mouseImage.addEventListener("load", loadImage, false);
function loadImage(e) {
context.drawImage(cheeseImage,10,10);
context.drawImage(mouseImage,210,400);
}
</script>
</div>
[mouse][1]cheese

The images are gone because the update function clears the canvas each frame of the animation (context.clearRect), but you want the canvas to clear so don't delete that.
You need to put all the code to draw the things you would like on the canvas inside the update function, or functions the update code calls.
Also its best to wait until both images have loaded before calling the update() function for the first time.
I have modified your code and added some comments, hopefully it makes sense...
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);//clears earlier positions
context.beginPath();
context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
context.fillStyle = "LightSeaGreen";
context.lineWidth = 5;
context.strokeStyle = "yellow";
context.fill();
context.stroke();
// Call functions to draw text and images on the canvas.
drawText();
drawCheese();
drawMouse();
requestAnimationFrame(update);
}
//Get the Exact Mouse Position
function getPosition(el) {
var xPosition = 0;
var yPosition = 0;
while (el) {
xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
el = el.offsetParent;
}
return {
x: xPosition,
y: yPosition
};
}
var canvas2 = document.querySelector("#layer2");
var context = canvas2.getContext("2d");
function drawText() {
//draw connecting line
context.moveTo(30,30);
context.bezierCurveTo(-50,600, 500, 0, 300,500);
context.lineWidth = 15;
context.strokeStyle = "teal";
context.stroke();
context.fillStyle = "#ff6600";
context.font = "bold 35px 'Book Antiqua'";
context.fillText("Help Me", 300,100);
context.fillText("find the cheese,", 300, 135);
context.fillText("Please!", 300, 170);
}
// Draw cheese image on canvas
function drawCheese() {
context.drawImage(cheeseImage,10,10);
}
// Draw mouse image on canvas.
function drawMouse() {
context.drawImage(mouseImage,210,400);
}
//load cheeseImage
var cheeseImage = new Image();
cheeseImage.src = "images/transparentCheese.png";
cheeseImage.addEventListener("load", loadImage, false);
//load mouse image
var mouseImage = new Image();
mouseImage.src = "images/mouse.png";
mouseImage.addEventListener("load", loadImage, false);
var imagesLoaded = 0;
// Called when image is loaded.
function loadImage(e) {
// Increment the number of images loaded
imagesLoaded += 1;
// If both images have loaded call the update function for the first time.
if (imagesLoaded == 2) {
update();
}
}

Related

Adjust canvas background in Javascript

I'm trying to drawn a rect on canvas, but I want that canvas has lightly transparent background, but that drawn rect has no background.
What I will is something as follows:
I have code as follows:
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
function drawRect(){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
/* ctx.fill(); */
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1,
rotate : 0,
};
drag = true;
}
function mouseUp() { drag = false; buttons_shown = true; update = true; }
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background:rgba(0,0,0,0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
In my example I set the background of the canvas to what I will but I cannot remove that background for the drawn rect, it has the same color as the canvas.
Here is the fiddle.
Any idea how to solve it?
This could be achieved in several ways (compositing, clip-path...) but the easiest for such a simple path is probably to use the "evenodd" fill-rule parameter of fill() method which will allow us to draw this rectangle with a hole.
The process is simply to draw a first rect the size of the canvas, then, in the same path declaration, draw your own smaller rectangle. The fill-rule will then exclude this smaller inner rectangle from the bigger one.
function drawRect() {
ctx.beginPath(); // a single path
// the big rectangle, covering the whole canvas
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
// your smaller, inner rectangle
ctx.setTransform(1, 0, 0, 1, rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
// set the fill-rule to evenodd
ctx.fill('evenodd');
// stroke
// start a new Path declaration
ctx.beginPath
// redraw only the small rect
ctx.rect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
ctx.stroke();
}
var canvas = document.getElementById('canvas');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function() {
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
// set our context's styles here
ctx.fillStyle = 'rgba(0,0,0,.5)';
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas() {
if (update) {
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// draws a rectangle with rotation
// clears canvas sets filters and draws rectangles
function drawCanvas() {
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRect()
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX: e.offsetX,
startY: e.offsetY,
w: 1,
h: 1,
rotate: 0,
};
drag = true;
}
function mouseUp() {
drag = false;
buttons_shown = true;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}
}
init();
.hide {
display: none !important;
}
canvas {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline-block;
background: rgba(0, 0, 0, 0.3);
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg" />
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide"></div>
Try filling your rect before calling ctx.stroke(), like this:
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fill();
This will produce similar effect to what you have shown in your question. Now the inside of rectangle has both css and fillStyle effect, so it's not ideal - for even better effect you would have to fill outside of rect with desired style instead of setting background in css.
first, draw a full canvas with semi-transparent background, like
ctx.fillStyle = 'rgba(32, 32, 32, 0.7)';
ctx.fillRect(0, 0, width, height);
Then just clear your rectangular form this canvas
ctx.clearRect(x, y, mini_width, mini_height);

How to rotate object in the canvas

I'm trying to rotate only one object in the canvas, not the whole canvas.
My code is as follows:
var canvas = document.getElementById('canvas');
var buttons = document.getElementById('buttons');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var buttons_shown = false;
var angle = 10;
var rotate_angle = 0;
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
document.addEventListener("keydown", keyDownPressed, false);
});
}
function keyDownPressed(e) {
var keyCode = e.keyCode;
var left_arrow = 37;
var right_arrow = 39;
var up_arrow = 38;
var down_arrow = 40;
if(keyCode === left_arrow) {
onRotateLeft()
}
if(keyCode === right_arrow){
onRotateRight()
}
}
function mouseDown(e) {
rect.startX = e.offsetX;
rect.startY = e.offsetY;
drag = true;
buttons_shown = false;
buttons.classList.add("hide");
}
function mouseUp() { drag = false; buttons_shown = true; }
function onRemoveSelectionClick(e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drag = false;
buttons_shown = false;
buttons.classList.add("hide");
}
function onRotateLeft(){
rotate_angle = rotate_angle - angle;
canvas.style.transform = 'rotate(' + rotate_angle + 'deg)';
}
function onRotateRight(){
rotate_angle = rotate_angle + angle;
canvas.style.transform = 'rotate(' + rotate_angle + 'deg)';
}
function mouseMove(e) {
if (drag) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
ctx.shadowBlur = 5;
ctx.filter = 'blur(10px)';
ctx.fillRect(rect.startX, rect.startY, rect.w, rect.h);
ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}else{
if(buttons_shown && buttons.classList.contains("hide")){
buttons.classList.remove("hide");
}
}
}
//
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background-color: yellow;
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide">
<button onclick="onRotateLeft()">Rotate Left</button>
<button onclick="onRotateRight()">Rotate right</button><br />
<button onclick="onRemoveSelectionClick()">Remove Selection</button>
</div>
Thus, if an object is drawn, I show the buttons to rotate it left or right or to delete it. If I click on for example Rotate Left button, it executes the next code:
rotate_angle = rotate_angle - angle;
canvas.style.transform = 'rotate(' + rotate_angle + 'deg)';
But that rotates my whole canvas (yellow background) but I want to rotate only the object inside the canvas.
Any idea?
Use canvas transform not CSS transform.
You need to apply the transform in the canvas when you render the rectangle.
The snippet is a copy of your code with some changes.
The rendering is done via requestAnimationFrame so as not to have needless renders when the mouse moves.
A global flag update is set to true whenever there is need to update the canvas.
An array of rectangles is used to store each rectangle.
The rectangle object also stores its rotation rect.rotate so that each rectangle has an independent rotation.
The global variable rect holds the current rectangle.
Events change the global rect object and set the update = true flag, they do not do any rendering.
The rectangle is rotated about its center
Canvas context ctx.rotate uses radians not degrees
See code for more info.
var canvas = document.getElementById('canvas');
var buttons = document.getElementById('buttons');
var img = document.getElementById('photo');
var ctx = canvas.getContext('2d');
var rect = {};
var drag = false;
var buttons_shown = false;
var angle = 10 * (Math.PI / 180) ;
var rotate_angle = 0;
var update = true; // when true updates canvas
var original_source = img.src;
img.src = original_source;
function init() {
img.addEventListener('load', function(){
canvas.width = img.width;
canvas.height = img.height;
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
document.addEventListener("keydown", keyDownPressed, false);
});
// start the rendering loop
requestAnimationFrame(updateCanvas);
}
// main render loop only updates if update is true
function updateCanvas(){
if(update){
drawCanvas();
update = false;
}
requestAnimationFrame(updateCanvas);
}
// array of rectangles
const rectangles = [];
// adds a rectangle
function addRectangle(rect){
rectangles.push(rect);
}
// draws a rectangle with rotation
function drawRect(rect){
ctx.setTransform(1,0,0,1,rect.startX + rect.w / 2, rect.startY + rect.h / 2);
ctx.rotate(rect.rotate);
ctx.beginPath();
ctx.rect(-rect.w/2, -rect.h/2, rect.w, rect.h);
ctx.fill();
ctx.stroke();
}
// clears canvas sets filters and draws rectangles
function drawCanvas(){
// restore the default transform as rectangle rendering does not restore the transform.
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.shadowBlur = 5;
ctx.filter = 'blur(10px)';
rectangles.forEach(drawRect);
}
function keyDownPressed(e) {
var keyCode = e.keyCode;
var left_arrow = 37;
var right_arrow = 39;
var up_arrow = 38;
var down_arrow = 40;
if(keyCode === left_arrow) {
onRotateLeft()
}
if(keyCode === right_arrow){
onRotateRight()
}
}
// create new rect add to array
function mouseDown(e) {
rect = {
startX : e.offsetX,
startY : e.offsetY,
w : 1,
h : 1,
rotate : 0,
};
drag = true;
buttons_shown = false;
buttons.classList.add("hide");
addRectangle(rect);
update = true;
}
function mouseUp() { drag = false; buttons_shown = true; update = true; }
// removes top rectangle and sets next down as current or if none then hides controls
function onRemoveSelectionClick(e) {
rectangles.pop();
if(rectangles.length === 0){
drag = false;
buttons_shown = false;
buttons.classList.add("hide");
}else{
rect = rectangles[rectangles.length -1];
}
update = true;
}
// rotate current rectangle
function onRotateLeft(){
rect.rotate -= angle;
update = true;
}
function onRotateRight(){
rect.rotate += angle;
update = true;
}
function mouseMove(e) {
if (drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY;
update = true;
}else{
if(buttons_shown && buttons.classList.contains("hide")){
buttons.classList.remove("hide");
}
}
}
//
init();
.hide{
display: none !important;
}
canvas{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display:inline-block;
background-color: yellow;
}
<div style="position: relative; overflow: hidden;display:inline-block;">
<img id="photo" src="http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg"/>
<canvas id="canvas"></canvas>
</div>
<div id="buttons" class="hide">
<button onclick="onRotateLeft()">Rotate Left</button>
<button onclick="onRotateRight()">Rotate right</button><br />
<button onclick="onRemoveSelectionClick()">Remove Selection</button>
</div>
You will need a separate canvas for the object; draw the object on this separate canvas and then, depending on what you actually intend to do, you can either:
Draw the auxiliary canvas' contents on the main one using context.rotate() combined with context.drawImage();
Overlay one canvas on top of another using CSS.
Then again, perhaps you could just use context.rotate() then draw on the main canvas as cited in: HTML5 Canvas Rotate Image

HTML Canvas Trying to create an animated chain of rectangle with slight delay/distance between them

I am trying to create multiple animated rectangles using Html Canvas with requestAnimationFrame. As for now, I managed to do exactly what I wanted with only one animated rectangle, but I can't find out how to create more rectangles that would simply be in line and follow each other with an equal distance.
Also, there's a random data (I, II or III) inside each rectangle.
Here's my actual code:
//Referencing canvas
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
//Make Canvas fullscreen and responsive
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize, false); resize();
//FPS
var framesPerSecond = 60;
//Default Y pos to center;
var yPos = canvas.height / 2;
//Default X pos offset
var xPos = -150;
//Speed (increment)
var speed = 2;
//Our array to store rectangles objects
var rectangles = [] ;
//Dynamic Number from database
var quote = ["I", "II", "III"];
//Random number for testing purpose
var rand = quote[Math.floor(Math.random() * quote.length)];
//Draw Rectangle
function drawRectangle () {
setTimeout(function() {
requestAnimationFrame(drawRectangle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Background color
ctx.fillStyle = "yellow";
//Position, size.
var rectWidth = 70;
var rectHeigth = 55;
ctx.fillRect(xPos,yPos,rectWidth,rectHeigth);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
//Data Layer
var dataLayer = ctx.fillText(rand,xPos+(rectWidth/2),yPos+(rectHeigth/2));
xPos += speed;
//Infinite loop for test
if (xPos > 1080) {
xPos = -150;
}
}, 1000 / framesPerSecond);
}
drawRectangle ();
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Moving Blocks</title>
<style>
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
Animating arrays of objects.
For animations you are best of using a single render function that renders all the objects once a frame, rather than create a separate render frame per object.
As for the squares there are many ways that you can get them to do what you want. It is a little difficult to answer as what you want is not completely clear.
This answer will use a rectangle object that has everything needed to be rendered and move. The rectangles will be kept in an array and the main render function will update and render each rectangle in turn.
There will be a spawn function that creates rectangles untill the limit has been reached.
// constants up the top
const quote = ["I", "II", "III"];
// function selects a random Item from an array
const randItem = (array) => array[(Math.random() * array.length) | 0];
// array to hold all rectangles
const rectangles = [];
var maxRectangles = 20;
const spawnRate = 50; // number of frames between spawns
var spawnCountdown = spawnRate;
//Referencing canvas
const ctx = canvas.getContext("2d");
var w, h; // global canvas width and height.
resizeCanvas(); // size the canvas to fit the page
requestAnimationFrame(mainLoop); // this will start when all code below has been run
function mainLoop() {
// resize in the rendering frame as using the resize
// event has some issuse and this is more efficient.
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas();
}
ctx.clearRect(0, 0, w, h);
spawnRectangle(); // spawns rectangles
updateAllRectangles(); // moves all active rectangles
drawAllRectangles(); // I will let you gues what this does... :P
requestAnimationFrame(mainLoop);
}
function resizeCanvas() {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
// and reset any canvas constants
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
// function to spawn a rectangle
function spawnRectangle() {
if (rectangles.length < maxRectangles) {
if (spawnCountdown) {
spawnCountdown -= 1;
} else {
rectangles.push(
createRectangle({
y: canvas.height / 2, // set at center
text: randItem(quote),
dx: 2, // set the x speed
})
);
spawnCountdown = spawnRate;
}
}
}
// define the default rectangle
const rectangle = {
x: -40, // this is the center of the rectangle
y: 0,
dx: 0, // delta x and y are the movement per frame
dy: 0,
w: 70, // size
h: 55,
color: "yellow",
text: null,
textColor: "black",
draw() { // function to draw this rectangle
ctx.fillStyle = this.color;
ctx.fillRect(this.x - this.w / 2, this.y - this.h / 2, this.w, this.h);
ctx.fillStyle = this.textColor;
ctx.fillText(this.text, this.x, this.y);
},
update() { // moves the rectangle
this.x += this.dx;
this.y += this.dy;
if (this.x > canvas.width + this.w / 2) {
this.x = -this.w / 2;
// if the rectangle makes it to the other
// side befor all rectangles are spawnd
// then reduce the number so you dont get any
// overlap
if (rectangles.length < maxRectangles) {
maxRectangles = rectangles.length;
}
}
}
}
// creats a new rectangle. Setting can hold any unique
// data for the rectangle
function createRectangle(settings) {
return Object.assign({}, rectangle, settings);
}
function updateAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].update();
}
}
function drawAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].draw();
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background-color: #131217;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>

Canvas draw following the path

I want to do the following on a HTML5 canvas / or SVG:
Have a background path, move cursor over and draw (fill) the background path
After the user complete drawing have a callback function
My problem is that I dont have any idea how to check if the drawed line is following the path.
Can someone explain me how to do this or maybe give some tips?
http://jsbin.com/reguyuxawo/edit?html,js,console,output
function drawBgPath() {
context.beginPath();
context.moveTo(100, 20);
context.lineTo(200, 160);
context.quadraticCurveTo(230, 200, 250, 120);
context.bezierCurveTo(290, -40, 300, 200, 400, 150);
context.lineTo(500, 90);
context.lineWidth = 5;
context.strokeStyle = 'rgba(0,0,0,.2)';
context.stroke();
}
Create a hidden canvas that stores the origin path as question Canvas, lets say, as #q.
Draw the question on the #c.
When user about to draw, get the pixel value from question to see whether its on a line or not.
Decide the draw color by the info above.
var mousePressed = false;
var lastX, lastY;
var ctx;
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var canvasq = document.getElementById('q');
var contextq = canvasq.getContext('2d');
canvas.width = 500;
canvas.height = 500;
canvasq.width = 500;
canvasq.height = 500;
$('#c').mousedown(function (e) {
mousePressed = true;
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
});
$('#c').mousemove(function (e) {
if (mousePressed) {
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
}
});
$('#c').mouseup(function (e) {
mousePressed = false;
});
$('#c').mouseleave(function (e) {
mousePressed = false;
});
function drawBgPath() {
contextq.beginPath();
contextq.moveTo(100, 20);
contextq.lineTo(200, 160);
contextq.quadraticCurveTo(230, 200, 250, 120);
contextq.bezierCurveTo(290, -40, 300, 200, 400, 150);
contextq.lineTo(500, 90);
contextq.lineWidth = 5;
contextq.strokeStyle = 'rgba(0,0,0,.2)';
contextq.stroke();
context.drawImage(canvasq, 0, 0);
}
function Draw(x, y, isDown) {
// If not integer, getImageData will get a 2x2 region.
x = Math.round(x);
y = Math.round(y);
if (isDown) {
var pixel = contextq.getImageData(x, y, 1, 1);
// If the canvas is not draw by line, the opacity value will be 0.
var color = (pixel.data[3] === 0) ? 'red' : 'purple';
context.beginPath();
context.strokeStyle = color;
context.lineWidth = 5;
context.lineJoin = "round";
context.moveTo(lastX, lastY);
context.lineTo(x, y);
context.closePath();
context.stroke();
}
lastX = x; lastY = y;
}
drawBgPath();
Draw();
#q {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<canvas id="c"></canvas>
<canvas id="q"></canvas>
Another way is:
Create 2 additional canvas, for answer and question.
When mouse down, draw the path on answer first.
Then compare answer canvas with question canvas.
Draw the compared answer on the canvas for show.
I'll just demo how it can be achieve here. You can clip the draw region to improve the performance.
It's somehow hard to decide whether the path is complete or not. But you can still:
Clip the answer image by question, then compare their pixel value one-by-one.
If pixel on question has color, total + 1, if both pixel have color and color is same, count + 1.
Check if count/total is over a specific threshold.
It may be slow if the image is large, so I'd prefer to only check it when user mouseup or click a check button.
I've also tried to use .toDataURL to compare their value by string, however, its too strict and can't let you have a threshold.
var mousePressed = false;
var lastX, lastY;
var ctx;
// Question part
var qCanvas = document.createElement('canvas');
var qContext = qCanvas.getContext('2d');
var aCanvas = document.createElement('canvas');
var aContext = aCanvas.getContext('2d');
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
qCanvas.width = 500;
qCanvas.height = 500;
aCanvas.width = 500;
aCanvas.height = 500;
$('#c').mousedown(function (e) {
mousePressed = true;
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false);
});
$('#c').mousemove(function (e) {
if (mousePressed) {
Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, true);
}
});
$('#c').mouseup(function (e) {
mousePressed = false;
});
$('#c').mouseleave(function (e) {
mousePressed = false;
});
function drawBgPath() {
qContext.beginPath();
qContext.moveTo(100, 20);
qContext.lineTo(200, 160);
qContext.quadraticCurveTo(230, 200, 250, 120);
qContext.bezierCurveTo(290, -40, 300, 200, 400, 150);
qContext.lineTo(500, 90);
qContext.lineWidth = 5;
qContext.strokeStyle = 'rgb(0,0,0)';
qContext.stroke();
// Draw Question on canvas
context.save();
context.globalAlpha = 0.2;
context.drawImage(qCanvas, 0, 0);
context.restore();
// Now fill the question with purple.
qContext.fillStyle = 'purple';
qContext.globalCompositeOperation = 'source-atop';
qContext.fillRect(0, 0, qCanvas.width, qCanvas.height);
}
function Draw(x, y, isDown) {
if (isDown) {
// First draw on answer canvas
aContext.beginPath();
aContext.strokeStyle = 'red';
console.log(x, y);
aContext.lineWidth = 5;
aContext.lineJoin = "round";
aContext.moveTo(lastX, lastY);
aContext.lineTo(x, y);
aContext.closePath();
aContext.stroke();
// Compare answer with question.
aContext.save();
aContext.globalCompositeOperation = 'source-atop';
aContext.drawImage(qCanvas, 0, 0);
aContext.restore();
// Draw the result on what you want to show.
context.drawImage(aCanvas, 0, 0);
}
lastX = x; lastY = y;
}
var cv = document.createElement('canvas');
cv.width = 500;
cv.height = 500;
//document.body.appendChild(cv);
var ctx = cv.getContext('2d');
function checkAnswer() {
cv.width = 500;
cv.height = 500;
ctx.globalCompositeOperation = 'source-over';
ctx.drawImage(aCanvas, 0, 0);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(qCanvas, 0, 0);
var qData = qContext.getImageData(0, 0, 500, 500).data;
var aData = ctx.getImageData(0, 0, 500, 500).data;
var idx = 0, i, j;
var count = 0, total = 0;
for (i = 0; i < 500; ++i) {
for (j = 0; j < 500; ++j) {
if (qData[idx] !== 0) {
++total;
if (aData[idx] === qData[idx]) {
++count;
}
}
idx += 4;
}
}
console.log(count,total);
// Threshold.
if (count/total > 0.95) {
alert('Complete');
}
}
drawBgPath();
Draw();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<canvas id="c"></canvas>
<button onclick="checkAnswer()">check</button>

Chroma keying with javascript & jQuery

Okay, we need your help! We (with our informatics class) are building a digital scratchmap! Like this:
(source: megagadgets.nl)
With your mouse you should be able to scratch out the places you've been to. Now we're stuck. We have a canvas and we draw the image of a world map. Then when the user clicks and drags a stroke gets add on top of the world map.
Now we want to convert the (green drawn) strokes to transparency so we can reveal the image behind it. (Just like scratching out the places you've been to and revealing the map behind it (in colour)).
This is our html:
<body>
<h1>Scratchmap</h1>
<hr>
<canvas id="ball" width="600px" height ="600px">
</canvas>
<canvas id="ball2" width="600px" height ="600px">
</canvas>
</body>
And this is our javascript:
// Set variables
var a_canvas = document.getElementById("ball");
var context = a_canvas.getContext("2d");
var a_canvas2 = document.getElementById("ball2");
var context2 = a_canvas2.getContext("2d");
var img = new Image();
img.onload = function () {
context.drawImage(img, img_x, img_y);
}
img.src = "worldmap.png"
var mouse_pos_x = [];
var mouse_pos_y = [];
var thickness = 0;
var arraycount = 0;
var mouse_down = false;
var mouse_skip = [];
function update() {}
document.body.onmousedown = function () {
mouse_down = true;
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
arraycount += 1;
}
document.body.onmouseup = function () {
if (mouse_down) {
mouse_down = false;
mouse_skip.push(arraycount);
}
}
document.body.onmousemove = function () {
if (mouse_down) {
var mouseX, mouseY;
if (event.offsetX) {
mouseX = event.offsetX;
mouseY = event.offsetY;
} else if (event.layerX) {
mouseX = event.layerX;
mouseY = event.layerY;
}
context.drawImage(img, 0, 0);
mouse_pos_x.push(mouseX);
mouse_pos_y.push(mouseY);
context.lineWidth = 2.5;
context.strokeStyle = "#00FF00";
context.moveTo(mouse_pos_x[arraycount - 1], mouse_pos_y[arraycount - 1]);
context.lineTo(mouse_pos_x[arraycount], mouse_pos_y[arraycount]);
context.stroke();
arraycount += 1;
var imgdata = context.getImageData(0, 0, a_canvas.width, a_canvas.height);
var l = imgdata.data.length / 4;
for (var i = 0; i < l; i++) {
var r = imgdata.data[i * 4 + 0];
var g = imgdata.data[i * 4 + 1];
var b = imgdata.data[i * 4 + 2];
if (g < 255) {
imgdata.data[i * 4 + 3] = 0;
}
}
context2.putImageData(imgdata, 0, 0);
}
}
setInterval(update, 10);
Now when we remove the draw_image() the green color becomes yellow on the other canvas. But with the draw_image() nothing gets drawn on the second canvas.
What's going wrong? Or do you have a way to do this with other Javascript or not in javascript at all?
Any help would be appreciated!
Luud Janssen & Friends
You can do this with a slightly different approach:
Set the hidden image as CSS background
Draw the cover image on top using context
Change composite mode to destination-out
Anything now drawn will erase instead of draw revealing the (CSS set) image behind
Live demo
The key code (see demo linked above for details):
function start() {
/// draw top image - background image is already set with CSS
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
/// KEY: this will earse where next drawing is drawn
ctx.globalCompositeOperation = 'destination-out';
canvas.onmousedown = handleMouseDown;
canvas.onmousemove = handleMouseMove;
window.onmouseup = handleMouseUp;
}
Then it's just a matter of tracking the mouse position and draw any shape to erase that area, for example a circle:
function erase(x, y) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, pi2);
ctx.fill();
}
Random images for illustrative purposes

Categories

Resources