I am in the process of learning javascript for game design, and wanted to make a separate achievement page that the user can navigate to that will allow them to check their achievements on various games. (at the moment I am not concerned with implementing localstorage/cookies etc, I just want to work on the page for now)
So the requirements of my base idea is as follows:
Able to drag around the viewport/page to view all the achievement categories as they will likely not all be in view on smaller screens
Able to click on a category to open a small box containing all achievements belonging to that game/category
Able to mouse over all achievements in the boxes to get text descriptions of what they are
OPTIONAL: have lines connecting each box on the "overworld" to show users where nearby boxes are if they are off screen
At first, I thought I would need canvas to be able to do this. I learned a bit about it and got decently far until I realized that canvas has a lot of restrictions like not being able to do mouseover events unless manually implementing each one. Here is the current progress I was at in doing a test-run of learning canvas, but it's not very far:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function resize()
{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
window.addEventListener("resize", resize);
var global = {
scale : 1,
offset : {
x : 0,
y : 0,
},
};
var panning = {
start : {
x : null,
y : null,
},
offset : {
x : 0,
y : 0,
},
};
var canvasCenterWidth = (canvas.width / 2);
var canvasCenterHeight = (canvas.height / 2);
function draw() {
ctx.beginPath();
ctx.rect(canvasCenterWidth, canvasCenterHeight, 100, 100);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.beginPath();
ctx.arc(350, 250, 50, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
}
draw();
canvas.addEventListener("mousedown", startPan);
function pan() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(panning.offset.x, panning.offset.y);
draw();
}
function startPan(e) {
window.addEventListener("mousemove", trackMouse);
window.addEventListener("mousemove", pan);
window.addEventListener("mouseup", endPan);
panning.start.x = e.clientX;
panning.start.y = e.clientY;
}
function endPan(e) {
window.removeEventListener("mousemove", trackMouse);
window.removeEventListener("mousemove", pan);
window.removeEventListener("mouseup", endPan);
panning.start.x = null;
panning.start.y = null;
global.offset.x = panning.offset.x;
global.offset.y = panning.offset.y;
}
function trackMouse(e) {
var offsetX = e.clientX - panning.start.x;
var offsetY = e.clientY - panning.start.y;
panning.offset.x = global.offset.x + offsetX;
panning.offset.y = global.offset.y + offsetY;
}
body{
overflow: hidden;
}
canvas {
top: 0;
left: 0;
position: absolute; }
<canvas id="canvas"></canvas>
So I guess my question is now: what is the best way to implement this? Is it feasable to do it with canvas, or should I just scrap that and try to figure out something with div movement? Should I be concerned with performance issues and should that affect how I implement it?
Related
I have developed a game in NodeJS where you have to guess an image's name meanwhile the image depixelates.
The problem is that the server uses canvas to pixelate the image but the render don't fit entirely in the frame as you can see :
The pixelate function :
function pixelate(image, ctx, canvas, value) {
var size = value / 100,
w = canvas.width * size,
h = canvas.height * size;
ctx.drawImage(image, 0, 0, w, h);
ctx.msImageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(canvas, 0, 0, w, h, 0, 0, canvas.width, canvas.height)
}
And the loop were i pixelate the image :
function image_pixel(bool = 1) {
if (bool) {
if (pixel_state > 24) {
restartGame("", false);
} else {
loadImage('image.jpg').then((image) => {
pixel_state += 0.1;
var canvas = createCanvas(image.width, image.height);
var ctx = canvas.getContext('2d');
pixelate(image, ctx, canvas, pixel_state);
io.emit('image', canvas.toDataURL());
})
}
} else { // Image without pixelisation
loadImage('image.jpg').then((image) => {
var canvas = createCanvas(image.width, image.height);
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
io.emit('image', canvas.toDataURL());
})
}
};
I tried to round the "w" and "h", the image will fill entirely in frame but some of the images sent will be the same so it'll feel laggy for the user.
Finally found something, I resized all of my pictures to square aspect ratio and then for the "pixel_state" if it's like 100/(2^x) i won't have any ghost pixels almost anymore.
I am developing a image masking tool that helps the user mark a certain regions on an underlying image. I'd like the mask to have a consistent alpha-value. The masking tool is to be implemented in HTML5 canvas.
The issue I'm facing is that when I create multiple over-lapping strokes, their alpha values stack, making it less-transparent in the overlaps. Whereas I'd like a consistent alpha value regardless of overlapping strokes, as the user might need multiple strokes to fully mask a region.
Here is the attached fiddle:
http://jsfiddle.net/o5x70fbd/
Let me know if this is a known solution
I don't know if this is the solution you are looking for. My idea is to use 2 canvases. In the first canvas the color of the stroke is opaque. This canvas is hidden. I'm using display:nonebut you can let it unattached to the DOM.
Then you copy the first canvas as an image in a second one with ctx2.globalAlpha = .5;. this will give you a a consistent alpha value.
The changes I've made to your code: I'm putting the points in arrays and I'm drawing using the points:
var canvas = document.getElementById("myCanvas");
var canvas2 = document.getElementById("_2");
var ctx = canvas.getContext("2d");
var ctx2 = _2.getContext("2d");
var drawing = false;
let points = [];
var painting = document.getElementById("paint");
var paint_style = getComputedStyle(painting);
canvas.width = canvas2.width = parseInt(paint_style.getPropertyValue("width"));
canvas.height = canvas2.height = parseInt(
paint_style.getPropertyValue("height")
);
var mouse = {
x: 0,
y: 0
};
let count = -1;
ctx.lineWidth = 30;
ctx.lineJoin = "round";
ctx.lineCap = "round";
_2.addEventListener(
"mousemove",
function(e) {
if (drawing) {
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
points[count].push({ x: mouse.x, y: mouse.y });
onPaint();
}
},
false
);
_2.addEventListener(
"mousedown",
function(e) {
drawing = true;
count++;
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop;
let ry = [];
ry.push({ x: mouse.x, y: mouse.y });
points.push(ry);
},
false
);
_2.addEventListener(
"mouseup",
function() {
drawing = false;
onPaint();
},
false
);
function onPaint() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
points.forEach(ry => {
ctx.beginPath();
ctx.moveTo(ry[0].x, ry[0].y);
ry.forEach(p => {
ctx.lineTo(p.x, p.y);
});
ctx.strokeStyle = "#00CC99";
ctx.stroke();
});
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.globalAlpha = 0.5;
ctx2.drawImage(canvas, 0, 0);
}
body {
margin: 0px;
padding: 0px;
}
#paint {
width: 98%;
height: 550px;
border: 5px solid red;
}
#myCanvas{display:none;}
#_2{background:url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/222579/castell.jpg)}
<div id="paint">
<canvas id="myCanvas"></canvas>
<canvas id="_2"></canvas>
</div>
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);
I created a script that makes a canvas circle follow the mouse when is X is bigger.However as you can see it only works during the mouse move. when the mouse stops I couldn't find a way to make the circle move. Plus, did i use the correct logic for making this code?
Heres a snippet:
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var PI = Math.PI;
window.requestAnimFrame = (function(){
return window.requestAnimationFrame || // La forme standardisée
window.webkitRequestAnimationFrame || // Pour Chrome et Safari
window.mozRequestAnimationFrame || // Pour Firefox
window.oRequestAnimationFrame || // Pour Opera
window.msRequestAnimationFrame || // Pour Internet Explorer
function(callback){ // Pour les élèves du dernier rang
window.setTimeout(callback, 1000 / 60);
};
})();
function pos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function elm_fixe() {
ctx.fillStyle = "rgba(050, 155, 255, 1)";
ctx.fillRect(0, 0, 30, 30, 1);
for (var x = 0, y = 0, alpha = 1; alpha >= 0; alpha -= 0.1, x += 40) {
ctx.fillStyle = "rgba(050, 155, 255, " + alpha + ")";
ctx.fillRect(x, 0, 30, 30);
}
}
function cercle(x, y) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(x, y, 30, 0, PI * 2, true);
ctx.fill();
}
var x = 250,
y = 250;
function bouger(e) {
console.log(e.clientX)
if ( pos(canvas, e).x > x) {
x += 1;
};
}
function draw(e) {
ctx.clearRect(0, 0, 800, 500);
bouger(e);
cercle(x, y);
elm_fixe();
}
/* window.requestAnimFrame(function() {
canvas.addEventListener('mousemove', function(e) {
window.requestAnimFrame(function() { draw(e) });
});
}
);
*/
window.addEventListener('mousemove', function(e) {
draw(e);
});
<canvas height="500" width="800" id="canvas"></canvas>
First of all, when experimenting with this it's important to focus on getting the task at hand done, so I removed your fixed drawn elements, as that's easy.
The main issue you are having is that you only update onmousemove, which could get in your way. The best thing to do is simply to store the mouse coordinates in a separate object, here I have done it as such:
var mouse = {x: 0, y: 0};
After that, simply update the coordinates when the mousemove event fires. Now we remember the position, which means in the future you could actually animate this circle from point to point as it does not rely on the event to actually know the values.
A polyfill for requestAnimationFrame is actually no longer necessary, almost every browser supports it except some older ones.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var mouse = {x: 0, y: 0};
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function circle() {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 30, 0, Math.PI * 2, true);
ctx.fill();
}
function draw(e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circle();
window.requestAnimationFrame(draw);
}
document.addEventListener('mousemove', function(e) {
mouse.x = e.pageX > mouse.x ? e.pageX : mouse.x;
mouse.y = e.pageY > mouse.y ? e.pageY : mouse.y;
});
window.requestAnimationFrame(draw);
html, body { height: 100%; }
body { overflow: hidden; padding: 0; margin: 0;}
<canvas id="canvas"></canvas>
It might be better to rename some functions, just for future obviousness. I have also learned the hard way, a long time ago, that you should keep your function names in English, mostly because programming happens to be based on English. This way every user on this site can decipher what a function might do, and future developers will be able to debug your code without knowing french. For example, I would rename circle to something like drawCircleAtMousePosition - its a mouthful, but nobody can confuse what this function does.
Another advantage of using a stored variable is that you can do your pos (which is a really bad name for a function - maybe localiseCoordinatesTo(canvas)) right in the onmousemove event, so you never have to think about this at a later point.
Update: Animating
Here is an implementation that uses a very simple linear interpolation to animate the circle from place to place:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// We will need a from value, a to value, and store a time;
var mouse = {from: {x: 0, y: 0}, to: {x: 0, y: 0}, time: Date.now()};
// As well as a duration
var duration = 1000;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function position(){
// This will calculate the position
var time = Date.now(), progress;
if(time > mouse.time + duration)
return mouse.to;
else
progress = (time - mouse.time) / duration;
return {
x: mouse.from.x + (mouse.to.x - mouse.from.x) * progress,
y: mouse.from.y + (mouse.to.y - mouse.from.y) * progress
}
}
function circle() {
ctx.fillStyle = "red";
ctx.beginPath();
var pos = position();
ctx.arc(pos.x, pos.y, 30, 0, Math.PI * 2, true);
ctx.fill();
}
function draw(e) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circle();
window.requestAnimationFrame(draw);
}
document.addEventListener('mousemove', function(e) {
// Update FROM to the current position
mouse.from = position();
// Reassign the to values
mouse.to.x = e.pageX > mouse.to.x ? e.pageX : mouse.to.x;
mouse.to.y = e.pageY > mouse.to.y ? e.pageY : mouse.to.y;
// Update the animation start time.
mouse.time = Date.now();
});
window.requestAnimationFrame(draw);
html, body { height: 100%; }
body { overflow: hidden; padding: 0; margin: 0;}
<canvas id="canvas"></canvas>
I made a small program that:
changes the mouse cursor inside the canvas to a black square
gives the black square a nice trail that fades away over time (the point of the program)
Here's the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.save();
ctx.globalAlpha = 0.1; // the opacity (i.e. fade) being applied to the canvas on each function re-run
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
ctx.fillStyle = "black";
ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
Here's a live example: https://jsfiddle.net/L6j71crw/2/
Problem
However the trail does not fade away completely, and leaves a ghosting trail.
Q: How can I remove the ghosting trail?
I have tried using clearRect() in different ways, but it just clears the entire animation leaving nothing to display. At best it just removes the trail and only fades the square cursor alone, but it still doesn't make the cursor completely transparent when the fading process is completed. I have tried finding posts about it, but I found nothing that gave a definitive answer and—most importantly—no posts with a working example.
Any ideas?
Try having a list of positions, this won't leave a ghost trail!
my code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var Positions = [];
var maxlength = 20;
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
var V2 = function(x, y){this.x = x; this.y = y;};
function getMousePos(canvas, e) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var e = 0; e != Positions.length; e++)
{
ctx.fillStyle = ctx.fillStyle = "rgba(0, 0, 0, " + 1 / e + ")";
ctx.fillRect(Positions[e].x, Positions[e].y, 8, 8);
}
if(Positions.length > 1)
Positions.pop()
//ctx.save();
//ctx.globalAlpha = 0.5; // the opacity (i.e. fade) being applied to the canvas on each function re-run
//ctx.fillStyle = "#fff";
//ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
//ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
Positions.unshift(new V2(pos.x, pos.y));
if(Positions.length > maxlength)
Positions.pop()
//ctx.fillStyle = "black";
//ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
JSFiddle: https://jsfiddle.net/L6j71crw/9/
Edit: made the cursor constant.