I have a background, let's say it's green grass. On top of the background I have a black overlay. What I want now is to make a movable hole in the overlay so that you can see the background like in the image below.
I am pretty new to canvas so I'm not sure what I'm supposed to look for. Alpha mask?
So my question is how can I achieve the effect demonstrated in the image above?
If it were HTML I would have two images of the grass, one as the background and one above the overlay in a div with a border radius that can move and just calculate positions.
Thanks.
Are you looking for a moving "flashlight" kind of effect?
If so, you can do that by drawing a circular path and then using it as a clipping region with: context.clip();
Anything drawn after the .clip() will be viewed through the clipping path.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/pRzxt/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var radius=50;
var x=100;
var dx=10;
var y=100;
var dy=10;
var delay=10;
var img=new Image();
img.onload=function(){
var canvas1=document.getElementById("image");
var ctxImg=canvas1.getContext("2d");
ctxImg.drawImage(img,0,0,img.width,img.height,0,0,canvas.width,canvas.height);
animate();
}
img.src="http://lh3.ggpht.com/_Z-i7eF_ACGI/TRxpFywLCxI/AAAAAAAAAD8/ACsxiuO_C1g/house%20vector.png";
function animate() {
if(--delay<0){
// update
x+=dx;
if(x-radius<0 || x+radius>=canvas.width){dx=-dx;}
y+=dy;
if(y-radius<0 || y+radius>=canvas.height){dy=-dy;}
delay=10;
// draw stuff
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.arc(x,y, radius, 0, 2 * Math.PI, false);
ctx.clip();
ctx.drawImage(img,0,0,img.width,img.height,0,0,canvas.width,canvas.height);
ctx.restore();
}
// request new frame
requestAnimFrame(function() {
animate();
});
}
}); // end $(function(){});
</script>
</head>
<body>
<p>Image clipped by moving circle</p>
<canvas id="canvas" width=300 height=200></canvas>
<br/><p>Unclipped image</p>
<canvas id="image" width=300 height=200></canvas>
</body>
</html>
I did something similar for a little canvas test I was working on a while back. What you can do is create a div with a canvas element on top of the bottom layer and draw a radial gradient with transparency on that top layer canvas. I have a fiddle here: http://jsfiddle.net/CnEBQ/14/
In particular look at this bit of code for the radial gradient. The context is attached to the top div canvas:
var gradient = tCTX.createRadialGradient(CANVAS_SIZE.x/2, CANVAS_SIZE.y/2, 250, CANVAS_SIZE.x/2, CANVAS_SIZE.y/2, 0);
gradient.addColorStop(0, "#000");
gradient.addColorStop(1, "transparent");
tCTX.fillStyle = gradient;
tCTX.fillRect(0, 0, CANVAS_SIZE.x, CANVAS_SIZE.y);
There may be a little bad code in there, but it should give you an idea of whether this is where you want to go. You could redraw the top layer "hole" as needed on a timer or some event to get it moving. And you can play with the gradient setting to get more or less "darkness".
I'm having quite a time trying to find a good reference to the functions I'm using here, but a decent place to look for more explanation of these functions is
the Mozilla Canvas API
where the parameters to the gradient functions are explained. Especially useful since they're not immediately obvious.
Related
I am working on a simple JS/HTML/CSS project. I have a box that's very big (400vw, 400vh) and it acts like a map. What I want to do is a mini-map for that huge thing on the top left corner.
What I understand is that it has to be done with canvas by getting the whole page, put it in canvas and then display it in another box that's just for example 10 times smaller. If my logic is correct, then what is wrong in the code?
<html>
<head>
<script defer src="js/main.js"></script>
<link rel="stylesheet" href="css/main.css">
<style>
#minimapCanvas {
position: absolute;
top: 0;
left: 0;
}
</style>
<script>
window.onload = function() {
// Get the canvas element and its context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Draw something on the main canvas
context.fillRect(0, 0, canvas.width, canvas.height);
saveCanvas();
}
function saveCanvas() {
// Get the canvas element and its context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Get the image data from the canvas
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// Create a new canvas for the minimap
var minimapCanvas = document.getElementById("minimapCanvas");
minimapCanvas.width = 100;
minimapCanvas.height = 100;
var minimapContext = minimapCanvas.getContext("2d");
// Draw the image data on the minimap canvas
minimapContext.putImageData(imageData, 0, 0, 0, 0, minimapCanvas.width, minimapCanvas.height);
}
</script>
</head>
<body>
<div class="box">
<canvas id="myCanvas" width="500" height="500"></canvas>
<canvas id="minimapCanvas" width="100" height="100"></canvas>
</div>
</body>
</html>
and after I run the application I get a big black box instead of the minimap -
I have been trying to search every possible topic about JS mini-map in stackoverflow but couldn't find my solution. I also tried Chat GPT (which usually helps me).
EDIT:
Here is a github link to my repository (NOTE: you have to run it with a live server for example in VSCode, in order for the character's image to work properly) -
https://github.com/ApooBG/minimap
I am looking for a way to wrap a bitmap image around the canvas, for an infinite scrolling effect. I'm looking at EaselJS but clean javascript code will also suffice.
Right now I am displacing an image to the left, and when it reaches a certain mark, it resets itself.
Coming from actionscript, there was an option to "wrap" the pixels of a bitmap around to the other side, thereby never really displacing the image, instead you were wrapping the pixels inside the image. Is this possible in javascript with canvas?
My current code:
this.update = function() {
// super large graphic
_roadContainer.x -= 9;
if(_roadContainer.x < -291) _roadContainer.x = 0;
}
Start with a good landscape image.
Flip the image horizontally using context.scale(-1,1).
Combine the flipped image to the right side of the original image.
Because we have exactly mirrored the images, the far left and right sides of the combined image are exactly the same.
Therefore, as we pan across the combined image and “run out of image”, we can just add another copy of the combined image to the right side and we have infinite + seamless panning.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/ywDp5/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// thanks Paul Irish for this RAF fallback shim
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var infiniteImage;
var infiniteImageWidth;
var img=document.createElement("img");
img.onload=function(){
// use a tempCanvas to create a horizontal mirror image
// This makes the panning appear seamless when
// transitioning to a new image on the right
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=img.width*2;
tempCanvas.height=img.height;
tempCtx.drawImage(img,0,0);
tempCtx.save();
tempCtx.translate(tempCanvas.width,0);
tempCtx.scale(-1,1);
tempCtx.drawImage(img,0,0);
tempCtx.restore();
infiniteImageWidth=img.width*2;
infiniteImage=document.createElement("img");
infiniteImage.onload=function(){
pan();
}
infiniteImage.src=tempCanvas.toDataURL();
}
img.crossOrigin="anonymous";
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/mountain.jpg";
var fps = 60;
var offsetLeft=0;
function pan() {
// increase the left offset
offsetLeft+=1;
if(offsetLeft>infiniteImageWidth){ offsetLeft=0; }
ctx.drawImage(infiniteImage,-offsetLeft,0);
ctx.drawImage(infiniteImage,infiniteImage.width-offsetLeft,0);
setTimeout(function() {
requestAnimFrame(pan);
}, 1000 / fps);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=500 height=143></canvas><br>
</body>
</html>
You can achieve this quite easily with the html5 canvas.
Look at the drawImage specification here :
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#drawing-images
drawImage comes in 3 flavors, the first being a simple copy of the image, the second allowing to scale the image, and the third is the one you seek, it allows to perform clipping in a single call.
What you have to do :
- have a counter that will move from zero to the width of your image, then loop to zero again.
- each frame, draw the maximum of the image that you can on the canvas.
- If there is still some part of the canvas not drawn, draw again the image starting from zero to fill the canvas.
i made a fiddle, the only part that matters is in the animate function (other things are tools i often use in my fiddles).
(Rq : I assumed in this example that two images would be enough to fill the canvas.)
http://jsfiddle.net/gamealchemist/5VJhp/
var startx = Math.round(startPos);
var clippedWidth = Math.min(landscape.width - startx, canvasWidth);
// fill left part of canvas with (clipped) image.
ctx.drawImage(landscape, startx, 0, clippedWidth, landscape.height,
0, 0, clippedWidth, landscape.height);
if (clippedWidth < canvasWidth) {
// if we do not fill the canvas
var remaining = canvasWidth - clippedWidth;
ctx.drawImage(landscape, 0, 0, remaining, landscape.height,
clippedWidth, 0, remaining, landscape.height);
}
// have the start position move and loop
startPos += dt * rotSpeed;
startPos %= landscape.width;
To answer my own question: I found a way to achieve this effect with EaselJS. The great benefit of this method is that you don't have to check for the position of your bitmap. It will scroll infinitely - without ever resetting the position to 0.
The trick is to fill a shape with a bitmapfill. You can set a bitmapfill to repeat infinitely. Then you use a Matrix2D to decide the offset of the bitmapfill. Since it repeats automatically, this will generate a scrolling effect.
function.createRoad() {
// road has a matrix, shape and image
_m = new createjs.Matrix2D();
// this gets the image from the preloader - but this can be any image
_r = queue.getResult("road");
// this creates a shape that will hold the repeating bitmap
_roadshape = new createjs.Shape();
// put the shape on the canvas
addChild(_roadshape);
}
//
// the draw code gets repeatedly called, for example by requestanimationframe
//
function.drawRoad() {
// var _speed = 4;
_m.translate(-_speed, 0);
_roadshape.graphics.clear().beginBitmapFill(_r, "repeat", _m).rect(0, 0, 900, 400);
}
I am trying to translate and save my canvas each time I press right key, so that it moves horizontally each time I press right key. This is the function I am using to move the canvas horizontally each time which is working for the first time and but not later.
function clearRight(){
var canvas1=document.getElementById('plot');
ctx=canvas1.getContext("2d");
ctx.restore();
ctx.translate(-1000,0);
ctx.save();
ctx.clearRect(0,0,canvas1.width,canvas1.height);
}
rightKey(){
clearRight();
draw();
}
Can anyone please point where am I going wrong in trying to move the canvas?
UPDATE
I have solved the issue I was facing.
To move the canvas horizontally
var translated=0;
function clear()
{
var canvas1=document.getElementById('plot');
var ctx=canvas1.getContext("2d");
ctx.restore();
ctx.save();
ctx.clearRect(0,0,canvas1.width,canvas1.height);
}
function rightKey()
{
clear();
ctx.clearRect(0,0,canvas1.width,canvas1.height);
translated += 10;
ctx.translate(-translated,300);
draw(ctx);
}
I'm assuming there's a reason why you're not just drawing an oversized canvas contained in a smaller div-wrapper with scrollbars enabled...so here's an alternative.
You could draw your entire plotted graph to a separate canvas.
Then to pan left/right you can draw that temporary canvas to your main canvas with an offset.
A Demo: http://jsfiddle.net/m1erickson/GfRLq/
Before panning right:
After panning right:
About dynamically changing plots:
If your plots are dynamic, then you can still use this panning technique.
Just update the tempCanvas with each new plot.
Example code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var offset=0;
// create some test data
var points=[];
for(var i=0;i<50;i++){
points.push({x:i*40,y:100+Math.random()*100-50});
}
// create a temporary canvas
var tempCanvas=document.createElement("canvas");
var tempCtx=tempCanvas.getContext("2d");
tempCanvas.width=40*points.length;
tempCanvas.height=300;
// draw your complete plot on the tempCanvas
// draw the line
tempCtx.beginPath();
tempCtx.moveTo(points[0].x,points[0].y);
for(var i=0;i<points.length;i++){
var point=points[i];
tempCtx.lineTo(point.x,point.y);
}
tempCtx.lineWidth=5;
tempCtx.strokeStyle="blue";
tempCtx.stroke();
// draw the markers
for(var i=0;i<points.length;i++){
var point=points[i];
tempCtx.beginPath();
tempCtx.arc(point.x,point.y,10,0,Math.PI*2);
tempCtx.closePath();
tempCtx.fillStyle="black";
tempCtx.fill();
tempCtx.fillStyle="white";
tempCtx.fillText(i,point.x-3,point.y+3);
}
ctx.drawImage(tempCanvas,0,0)
// function to draw the canvas with your specified offset
function drawPlotWithOffset(offset){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(tempCanvas,offset,0,canvas.width,canvas.height,0,0,canvas.width,canvas.height)
}
$("#left").click(function(){
offset-=20;
if(offset<0){offset=0;}
drawPlotWithOffset(offset);
});
$("#right").click(function(){
offset+=20;
if(offset>tempCanvas.width-canvas.width){
offset=tempCanvas.width-canvas.width;
}
drawPlotWithOffset(offset);
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="left">Pan Left</button><br>
<button id="right">Pan Right</button><br>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I am failing at getting a DOM Image onclick event to work.
var context = document.getElementById('canvas').getContext('2d');
var image = new Image();
image.src = "foo.png"
image.onclick = function(e) { console.log("clicked"); }
setInterval(function() {
context.drawImage(image, 100, 100, 50, 50);
};
Why do I not get the log message when i click on the image. In developer tools i can see the onclick function is not null for the image.
Yes, what Musa said...and a few other things.
Some changes to your code
Image.src=”foo.png” should come after the image.onclick function
Context.drawImage should be inside the image.onclick function
setInterval is not needed as far as I can see
Try this:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var context=document.getElementById("canvas").getContext("2d");
var image=new Image();
image.onload=function(){
context.drawImage(image,0,0);
}
image.src="http://i.imgur.com/nJiIJIJ.png";
document.getElementById("canvas").addEventListener("click", function(){console.log("clicked");}, false);
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
You cannot set onclick for a particular image added in canvas . You can set onclick for the whole canvas alone so you have to use any third party js or else you should do some calculations which finds that you clicked on the image of the canvas ..
Other users are right.
The image you draw on the canvas is a DOM element but it is rendered in a position which is not stored in the DOM.
This doesn't mean you can access it's position and compare it with the mouse position.
I'm using an external library here, but it does what you need: http://jsfiddle.net/Saturnix/cygUH/
this is the library used.
Since I can't post link to jsfiddles without posting the code, here's the script I've wrote for you.
function demo(g) {
g.ctx.font = "bold 16px Arial";
g.draw = function () {
g.ctx.clearRect(0, 0, g.width, g.height)
var posX = 0;
var posY = 0;
g.ctx.drawImage(image, posX, posY);
if (g.mouseX > posX && g.mouseX < image.width &&
g.mouseY > posY && g.mouseY < image.height &&
g.mousePressed)
g.ctx.fillText("You're clicking the image!", g.mouseX, g.mouseY);
}
}
You can cast a ray (with an onclick on the canvas) into the canvas and manually test your images for intersection with the ray. You should write a
objectsUnderPoint( x, y );
function that returns an array of all the images that intersect with the ray at x, y.
This is the way it is usually done in 3D engines as well. You ofcourse need to keep the image position as a property of the image for intersection testing.
I would like to achieve jQuery Knob-like effect using HTML5 Canvas, but with a circle knob/cursor, instead of the stroke cursor effect that jQuery Knob does.
Based on jQuery Knob code, I was managed to connect the onMouseMove event with my circle knob/cursor so the circle knob moves according to the X and Y coordinates of where the mouse is. However I cannot "restrict" the knob to move only on/along the circle path just like this example, so if I click/mousedown inside the circle path, the circle knob moves to inside the path.
Is there any way to achieve this only using Canavas and jQuery, not Raphael like the example above?
One of my thoughts was to move the circle knob back on track (on the path) whenever mousemove event occurs outside the path (like this). However no luck in succeeding the calculation for this. Is there any math/geometry formula I can use to achieve this?
Just a little bit of trigonometry will answer your question!
This code will find the point on a circle closest to a mouseclick.
var rads = Math.atan2(mouseY - knobCenterY, mouseX - knobCenterX);
var indicatorX=knobRadius*Math.cos(rads)+knobCenterX;
var indicatorY=knobRadius*Math.sin(rads)+knobCenterY;
This code will put an indicator on the knob closest to where the user clicks
And here is a Fiddle --- http://jsfiddle.net/m1erickson/pL5jP/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!--[if lt IE 9]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var circleArc=Math.PI*2;
// drawing design properties
var knobCenterX=100;
var knobCenterY=100;
var knobRadius=50;
var knobColor="green";
var indicatorRadius=5;
var indicatorColor="yellow";
Draw(canvas.width/2,1); // just to get started
function Draw(mouseX,mouseY){
// given mousePosition, what is the nearest point on the knob
var rads = Math.atan2(mouseY - knobCenterY, mouseX - knobCenterX);
var indicatorX=knobRadius*Math.cos(rads)+knobCenterX;
var indicatorY=knobRadius*Math.sin(rads)+knobCenterY;
// start drawing
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw knob
ctx.beginPath();
ctx.arc(knobCenterX,knobCenterY,knobRadius,0,circleArc,false);
ctx.fillStyle="ivory";
ctx.fill();
ctx.lineWidth=2;
ctx.strokeStyle=knobColor;
ctx.stroke();
// draw indicator
ctx.beginPath();
ctx.arc(indicatorX, indicatorY, indicatorRadius, 0, circleArc, false);
ctx.fillStyle = indicatorColor;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
function handleMouseDown(e){
MouseX=parseInt(e.clientX-offsetX);
MouseY=parseInt(e.clientY-offsetY);
Draw(MouseX,MouseY);
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
}); // end $(function(){});
</script>
</head>
<body>
<br/><p>Click anywhere in the canvas to set the knob indicator</p><br/>
<canvas id="canvas" width=200 height=200></canvas>
</body>
</html>