JS using wrong coordinates when drawing on canvas with margin - javascript

Hey guys I have problem with using canvas together with some basic CSS styling.
So I wanted to get myself familiar a little bit more with canvas element so I followed Dev Ed's (Youtube) tutorial on how to make a drawing app with canvas.
I wanted to upgrade it a little bit by adding some color options for lines and background, added a title and so on. I wanted to center canvas element as well as give it fixed size.
Now when I'm trying to use it, canvas uses wrong coordinates to draw lines.
Thanks in advance.
Code that controls mouse coorinates:
function draw(e) {
if (!painting) return;
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
Link to full code on codepen: https://codepen.io/Skafec/pen/NWKzxrg

I recently created a couple of functions for that. The real coordinate is a composition of pointer position, translated using the bounding rect position, and apply a factor ( real size vs display size)
function translatedX(x){
var rect = canvas.getBoundingClientRect();
var factor = canvas.width / rect.width;
return factor * (x - rect.left);
}
function translatedY(y){
var rect = canvas.getBoundingClientRect();
var factor = canvas.width / rect.width;
return factor * (y - rect.top);
}
See https://codepen.io/fraigo/pen/jONKWaz

Related

Is there a bug in HTML Canvas' fillText function?

I just came across some unexpected behaviour on the HTML Canvas element; I tried to strip the problem down as much as I could. In short, it appears the ctx.fillText fails to render the text in specific regions of the canvas.
This is the smallest script I could write to consistently reproduce the bug (I tested it on different machines, OSs and browsers). It creates a black canvas (1.25×1.25 in drawing space units, 1000×1000 in pixels, the drawing space origin is in the middle) and draws red dots as the mouse passes over it, but there are several horizontal stripes in which it fails to do so.
// define boundaries of drawing space
const left = -.625;
const tops = -.625;
const scale = 800;
const width = 1.25;
const height = 1.25;
// create canvas
let canvas = document.createElement("canvas");
canvas.width = scale * width;
canvas.height = scale * height;
canvas.style.backgroundColor = "black";
document.body.appendChild(canvas);
// create context
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.translate(-left, -tops);
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.font = `.03px arial`;
ctx.fillStyle = "rgba(208, 64, 64, 1)";
// coordinates follow mouse
addEventListener("mousemove", event => {
let mouseX = left + event.layerX / scale;
let mouseY = tops + event.layerY / scale;
ctx.fillText(".", mouseX, mouseY);
});
You can paste it in your dev-tools in about:blank and see for yourself. In any case here's a gif too:
As you can see, there are several horizontal stripes left untouched, even though I pass over them with the mouse. Also, the entirety of the bottom 80% of the canvas is unaffected.
A few important notes:
This occurs with any text, but I used the dot because it takes up the least amount of space, while bigger symbols easily bridged the gaps in the upper portion and made it more difficult to see.
The mousemove event is not the culprit, mouseX and mouseY update properly and smoothly.
It is not due to my mouse being dragged too quickly, it leaves the gaps no matter how slow I move.
It is not due to how small the scale is, as the x dimension has the same scaling as the y dimension, but the former doesn't present this issue.
This does not occur with the ctx.strokeText method, which works fine.
It also does not occur with ctx.fillPath.
Am I doing something wrong? Or is this actually a bug?
The problem is the extremely small font size you're trying to render. You shouldn't use values like .03px - it makes sense for renderers to not be able to render something like that correctly, considering the typical smallest paintable size on a display is 1 pixel (a little smaller than that on high DPI displays, but probably not much less than .25px). It may work for painting simple lines, but rendering is more complicated than that (e.g. hinting).
Try the following values:
scale = 80
width = 12.5
height = 12.5
ctx.font = `.3px arial`;
Alternatively, try to paint a dot or a square rather than a "." string.
As a side note, I was able to reproduce the problem on Chrome, but on Firefox it actually renders fine.

JavaScript: Is it possible to cut a shape out of a rectangle to make a transparent hole in it?

I am trying to make a 2d top-down game in Javascript at the moment. I've currently got a day/night system working where a black rectangle gradually becomes more opaque (as the day goes on) before it finally is fully opaque, simulating the peak of the night where the player can not see.
I want to implement an artificial light system, where the player could use a torch that will illuminate a small area in-front of them. However, my problem is that I don't seem to be able to find a way to 'cut out' a shape from my opaque rectangle. By cutting out a shape, it would look like the player has a torch.
Please find an example mock-up image I made below to show what I mean.
http://i.imgur.com/VqnTwoR.png
Obviously the shape shouldn't be as roughly drawn as that :)
Thanks for your time,
Cam
EDIT: The code used to draw the rectangle is as follows:
context.fillStyle = "#000033";
context.globalAlpha = checkLight(gameData.worldData.time);
context.fillRect(0, 0, 512, 480);
//This is where you have to add the cut out triangles for light!
context.stroke();
Instead of drawing a rectangle over the scene to darken it when the "light" is on, instead draw an image with the "lit" area completely transparent and the rest of the "dark" area more opaque.
One way is to use declare a triangular clipping area and draw your revealed scene. The scene would display only inside the defined clipping area.
Example code and a Demo:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var offset = 50;
var img = new Image();
img.onload = function() {
knockoutAndRefill(50,100,300,50,75,350);
};
img.src = 'http://guideimg.alibaba.com/images/trip/1/03/18/7/landscape-arch_68367.jpg';
function knockoutAndRefill(x0,y0,x1,y1,x2,y2){
context.save();
context.fillStyle='black';
context.fillRect(0,0,canvas.width,canvas.height);
context.beginPath();
context.moveTo(x0,y0);
context.lineTo(x1,y1);
context.lineTo(x2,y2);
context.closePath();
context.clip();
context.drawImage(img,0,0);
context.restore();
}
<canvas id=myCanvas width=500 height=400>

draw rectangle over html with javascript

I know there's a similar question here, but neither the question nor the answer have any code.
What I want to do, is to port this functionality to a 100% Javascript solution. Right now I'm able to draw a rectangle on top of HTML content using PHP.
I scrape a website with CasperJS, take a snapshot, send the snapshot path back to PHP along with a json object that contains all the information necessary to draw a rectangle with GD libraries. That works, but now I want to port that functionality into Javascript.
The way I'm getting the rectangle information is using getBoundingClientRect() that returns an object with top,bottom,height, width, left,and right.
Is there any way to "draw" the HTML of the website to a canvas element, and then draw a Rectangle on that canvas element?
Or is there any way to draw a rectangle on top of the HTML using Javascript?
Hope my question is clear enough.
This is the function that I'm using to get the coordinates of the elements that I want to draw a Rectangle around.
function getListingRectangles() {
return Array.prototype.map.call(document.querySelectorAll('.box'), function(e) {
return e.getBoundingClientRect();
});
You can create a canvas element and place it on top of the HTML page:
//Position parameters used for drawing the rectangle
var x = 100;
var y = 150;
var width = 200;
var height = 150;
var canvas = document.createElement('canvas'); //Create a canvas element
//Set canvas width/height
canvas.style.width='100%';
canvas.style.height='100%';
//Set canvas drawing area width/height
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
//Position canvas
canvas.style.position='absolute';
canvas.style.left=0;
canvas.style.top=0;
canvas.style.zIndex=100000;
canvas.style.pointerEvents='none'; //Make sure you can click 'through' the canvas
document.body.appendChild(canvas); //Append canvas to body element
var context = canvas.getContext('2d');
//Draw rectangle
context.rect(x, y, width, height);
context.fillStyle = 'yellow';
context.fill();
This code should add a yellow rectangle at [100, 150] pixels from the top left corner of the page.

Rotate Moving Object on Canvas

I'm trying to draw an object based on it's rotate property, but it seems to get confused when the object is moving.
ctx.save();
ctx.translate(this.width * 0.5, this.height*0.5);
ctx.rotate(DegToRad(45));
ctx.translate(-this.width*0.5,-this.height*0.5);
ctx.drawImage(this.img, this.spriteOffset, 0, this.width, this.height, this.x, this.y,this.width,this.height);
ctx.restore();
The image is drawn rotated 45 degrees, however it now moves in a down-left direction, when the object should only be moving downwards. The movement code is simply handled by incrementing the this.y position. Is there a simpler way to accomplish this?
This is because the whole canvas is being rotated.
What you could try is looking into a framework like Kinetic JS, which creates a sort of SVG API for the canvas.
This site has tons of information on how to use it as well, including rotation, transition, transformation, and pretty much anything else that you might need in working with it.
This should suit your needs rather well.
I think I should provide a low-level, framework-free option as well. Basically, using raw javascript and HTML to pull this off.
Now, as I understood your question, you are trying to make an object (let's assume a black square) move downwards AND have it spin. The only way I can think of to spin it in the canvas without going into path hell is to rotate the entire rendering context. BUT you can also import an image into canvas, for instance, a transparent black diamond (i.e. that same square rotated). So you'd use a separate canvas to render each step of the rotation for the square.
Essentially something like this:
var canvas2 = document.createElement('canvas'), ctx2 = canvas2.getContext('2d');
//do something with the second canvas
//let's assume the second canvas is the same size as the square
ctx.drawImage(canvas2, squareX, squareY);
See my attempt
As you can see, it is a bit wonky, but it does do essentially what the question asks; it moves the square down, and rotates it. It also plots the result of that rotation below the actual canvas so you can see what's happening under the hood, but the square cuts out due to the "center" being on the top left of the square, and not in the middle.
In the end, it really comes down to how you want to do it.
I was playing around with the API and found it was easier to just keep track of where the object should be. here's an example of a square moving diagonally across the screen and rotating.
<canvas id="canvas" style="width: 100%; height: 100%;">
</canvas>
<script>
var DELAY = 15; // ms
var RECT_WIDTH = 100; // px
var RECT_HEIGHT = 100; // px
var canvas = document.getElementById('canvas');
// set intrinsic dimensions
canvas.width = 1000;
canvas.height = 1000;
var ctx = canvas.getContext('2d')
ctx.fillStyle = 'teal';
var step = 0
var vx = 2
var animate = setInterval(function () {
ctx.resetTransform()
ctx.clearRect(0, 0, 1000, 1000);
ctx.translate(vx * step, vx * step);
// rotation in place, translate to center of square and back
ctx.translate(RECT_WIDTH / 2, RECT_HEIGHT / 2);
ctx.rotate((Math.PI / 180) * step);
ctx.translate(-(RECT_WIDTH / 2), -(RECT_HEIGHT / 2));
// Draw the rectangle
ctx.fillRect(0, 0, RECT_WIDTH, RECT_HEIGHT);
step = step + 1
}, DELAY)
setTimeout(function () {
clearInterval(animate);
}, 5000);
</script>
Here, I use vx and keep track of the steps to translate, and calculate what the rotation will be in radians based on the steps a new not caring what the previous state was. Make sure you rotate across the center of where you're square will be as well.
Use ctx.translate to set the object's position before applying the rotation. This should fix the problem.
ctx.save();
ctx.translate(this.x, this.y);
ctx.translate(this.width * 0.5, this.height*0.5);
ctx.rotate(DegToRad(45));
ctx.translate(-this.width*0.5,-this.height*0.5);
ctx.drawImage(this.img, this.spriteOffset, 0, this.width, this.height, 0, 0,this.width,this.height);
ctx.restore();

Draw a cross at the mouses location on a canvas, non persistently

I have this bound to the mousemove event of my canvas:
function(e){
var contDiv = $('#current_system_map');
var offset = contDiv.offset();
x = e.clientX-offset.left;
y = e.clientY-offset.top;
context.beginPath();
context.moveTo(0,y);
context.lineTo(595,y);
context.moveTo(x,0);
context.lineTo(x,595);
context.strokeStyle = "rgb(255,255,255)";
context.stroke();
}
and it works fine, to a point. The drawn cross is persistent, so when the mouse moves a new cross is drawn but the old one remains. I've tried essentially re-drawing the canvas but that cause the cross to be laggy and remain quite away behind the mouse.
So i need to know how to draw the cross and make it dis-appear without having to re-draw everything on the canvas
http://jsfiddle.net/PgKEt/2/
This is the best that I can do.
If you try to use setInterval and such to animate it, it will keep redrawing even when it does not need to. So by doing this, you essentially only redraw when the mouse moves, and only draw 2 lines, instead of whatever content you want it on top.
In addition, if you have any detection such as mousedown and such, it has to be on whatever canvas is on the top, otherwise it will not detect them anymore.
Usually if you draw something on the canvas you will have to redrawn the canvas contents to erase it. I suggest you use an image element as a cursor and position it absolutely above the
Or you could try the old trick and draw the cursor in the canvas with globalCompositeOperation='xor', then draw it again in the same place to erase it. Afterwards you will need to restore globalCompositeOperation to source-over.
This approach works fast enough for me in Firefox 3.6.8 to do in a mousemove event. Save the image before you draw the crosshair and then restore it to erase:
To save:
savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")
The to restore:
ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)
If you do not want to store it persistently, you can also take a look at SVG.
Try
ctx.clearRect(0,0,YourCanvasHeight,YourCanvasWidth);
In my case I implemented a circle and every time the user clicks inside it, this instruction returns and deletes the previous points.
This is the complete code:
function getMousePosition(canvas, event) {
let rect = canvas.getBoundingClientRect();
let x = event.offsetX; //event.clientX - rect.left;
let y = event.offsetY; //event.clientY - rect.top;
drawPoint(canvas,x,y);
};
function drawPoint(canvas,x,y) {
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.clearRect(0,0,200,200);
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
}
};
$(document).ready(function(){
let canvasElem = document.getElementById("circle");
canvasElem.addEventListener("click", function(e)
{
getMousePosition(canvasElem, e);
});
});

Categories

Resources