Problem- can't get the accurate coordinates of mouse on page resize, when using html5 canvas - html

I wanted to create this Pixel Effect from Frontend Expert.
Although I was able to implement the entire pixel effect on a full screen canvas:
const canvas = getElementById('canvas');
canvas.height = window.innerHeight; // Gives the canvas height of fullscreen
canvas.width= window.innerWidth; // Gives the canvas width of fullscreen
and got the coordinates of the mouse pretty easily
const mouse = {
x: undefined,
y: undefined
}
canvas.addEventListner('mousemove', function(e) {
mouse.x = e.x;
mouse.y = e.y;
}
About now the canvas width and height was equal to that of the document, therefore, it was pretty easy to get the exact coordinates of the mouse.
But when I tried to implement it with 800px X 400px dimensions and used a flexbox with it (like shown in the website) my mouse coordinates got completely messed up and I spent hours in fixing but wasn't able to get the accuracy as shown in the above website. Also there were some issues related to resize.
I would like to know how can I preserve the mouse accuracy.
Your help is much appreciated.

I believe when you use e.x and e.y, even though used in an event listener for tha canvas, they return the mouse coordinate relative to the top-left pixel of the entire page, not just the canvas. If by messed up, you mean that anywhere you click, the pixel effect is offset in some constant direction, this may be your issue and you should replace e.x and e.y with e.clientX and e.clientY. The ‘client’ in e.clientX refers to which element the listener is for and specifies to give event coordinates relative to that element instead of the page. If it is messed up in some other way, then I don’t think I have an answer for that.

Related

HTML canvas best practices for resizing

Doing a simple 2d physics engine with HTML5 Canvas (collisions,graphing). I want a full screen canvas with a header navbar. How can I create this layout and handle resizing correctly?
I have tried several solutions:
One involved programatically resizing the canvas to fill its container onload() and onresize(). Canvas contents stay the same.
Another involved a responsive canvas with percents whose contents shrunk as the canvas shrunk.
Can anyone help lead us to the holy grail? The most important question is your opinion about canvas resizing best practices (should we do it?). If so, what about the debate between resizing the canvas pixels and media queries vs flex/percents vs javascript container measuring, etc.
Example/Attempts:
Example-1:
Here is the Javascript code which I used in v1 of my mock up. The corresponding HTML was just a basic document with a header and a 100% container with the canvas inside the container and being set to fill the container.
window.onload = function(){
init();
};
window.addEventListener("resize", init);
function init(){
console.log("init");
initCanvas();
drawCircle();
}
function initCanvas() {
var content = document.querySelector(".content");
var canvas = document.querySelector(".myCanvas");
canvas.width = content.clientWidth;
canvas.height = content.clientHeight;
}
Example-2:
This CodePen is an example of the resizing canvas that I made. It still retreats up under the navbar during extreme resizing though.
Resizing
It will depend on how you are rendering.
requestAnimationFrame is best practice.
Generally best practice to make any changes to the DOM is to use requestAnimationFrame to ensure that changes are presented in sync with the display hardware's refresh. requestAnimationFrame also ensure that only when the page is visible will the changes be made. ie (if the client switches tabs to another tab, your tab will not fire any requestAnimationFrame events)
It is also best to keep the canvas resolution as low as possible. Keeping the canvas at a resolution higher than the display means you will be doing a lot of needless rendering on portions that are off screen, or if you are scaling the canvas via CSS the down or upsampling can result in a variety of unwanted artifacts.
The problem with the resize event.
The resize event is triggered by a variety of sources, OS events that change the window size, mouse events, or from Javascript. None of these events are synced to the display, and some resize events can fire at very high rates (mouse driven resize can fire 100+ times a second)
Because resizing the canvas also clears the image data and resets the context state, each resize requires a re-rendering of the content. The rapid firing rate of the resize event can overwork the thread and you will start to lose events , the page will feel laggy and you can get parts of the page that are not updated in time for the next display frame.
When resizing you should try to avoid resizing when not needed. Thus the best time to resize is via a requestAnimationFrame callback.
Realtime rendering
If you are rendering in realtime then the best way to resize is to compare the canvas size to the container's or window size at the start of every render frame. If the sizes do not match then resize the canvas.
// no need for a resize event listener.
function renderLoop(){
// innerWidth / height or containor size
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
canvas.width = innerWidth;
canvas.height = innerHeight;
}
// your code
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
Static renders
If you are rendering infrequently or as needed. Then you may be best off to keep a canvas at a standard resolution offscreen and use a resizable canvas on screen to render a view of the offscreen canvas.
In that case you keep a main loop alive that will check a semaphore that indicates that there is a need to update the view. Anything that changes the canvas will then just set the flag to true.
const mainCanvas = document.createElement("canvas");
const mCtx ...
const canvas = document.getElementById("displayCanvas");
const ctx ...
// when updating content
function renderContent(){
mCtx.drawStuff...
...
updateView = true; // flag the change
}
// the resize event only need flag that there is a change
window.addEventListener("resize",()=> updateView = true );
var updateView = true;
function renderLoop(){
if(updateView){
updateView = false; // clear the flag
// is there a need to change display canvas resolution.
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
canvas.width = innerWidth;
canvas.height = innerHeight;
}
// draw the mainCanvas onto the display canvas.
ctx.drawImage(mainCanvas, viewOrigin.x, viewOrigin.y);
}
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
In your case using the above method (even for realtime) gives you better control over what part of the canvas content is seen.
CSS
Some poeple consider that CSS is the only place any type of visual content should be changed. The problem with the canvas is that CSS can not set the canvas resolution so if you use CSS you still have to set the canvas resolution.
For the canvas I do not set any CSS sizes and let the canvas resolution properties set the display size canvas.width=1920; canvas.height=1080; I can not see the point of having having to set the CSS width and height when there is no need to
Note: If you do not use requestAnimationFrame you will need to set the CSS size as you can not guarantee the canvas resolution will be set in time for the next refresh, while auto CSS updates (eg canvas.style.width="100%") will change in sync with the display device.
It works, you could check it with element inspect.
And I was thinking you wanted change the style width/height of canvas, not the width or height of canvas, they are quite different.
The width or height of canvas would just affect the ratio of things you draw on the canvas. And the CSS style width or height could change the display size of the canvas.

How to have a scrolling renderer in ThreeJS?

I'm using the OrtographicCamera.
A canvas is declared in HTML and I pass it to the renderer as shown below:
var canvas = document.getElementById("canvas")
var width = canvas.clientWidth;
var height = canvas.clientHeight;
renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: canvas
});
renderer.setClearColor(0xf0f0f0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
The problem is that the cubes I'm adding to the scene are being cut.
The scene is growing and has a height bigger than the available viewport's height.
I'd like to have scrollbars so that I can move to the bottom of the canvas and see all the cubes without having to zoom.
See the screenshot:
HTML
<div id="container">
<canvas id="canvas"></canvas>
</div>
CSS
#canvas
{
width: 100%;
height: 100%;
}
Any advice is really appreciated.
You have two things going on. The first is that the canvas needs to be larger than the client window to get scrollbars to show up. The exact size depends on your needs, but try doubling it from what you have now.
The second issue is going to be that the camera itself has left, right, top, and bottom (as well as near and far) planes that define the area within the camera's view. Increase those values when setting up the camera to get more of the scene visible in the canvas. If your width is fine, you may be able to only increase the top/bottom instead.
https://threejs.org/docs/api/cameras/OrthographicCamera.html
I used orbitcontrols and managed to get what I wanted, that is, being able to see the hidden objects in the scene. The user just have to press arrow down and arrow up to move the camera down\up.
No need to fiddle with the canvas element at all. That's good because controlling the canvas size in CSS is a bit hard and much more when there's scrolling involved and having it attached to ThreeJS renderer.
https://github.com/mattdesl/three-orbit-controls

Does an offscreen canvas have as much "space" as a normal one?

I have a situation similar to this question about copying data between canvases, but in my case I think I'm running into issues with the canvas engine itself and I'd like some understanding/guidance on what I might be doing wrong.
I'm creating an offscreen canvas with the same width and height as the onscreen canvas.
#offscreenCanvas = document.createElement('canvas')
# assign same dimensions as onscreen canvas
#offscreenCanvas.width = canvas.width
#offscreenCanvas.height = canvas.height
Then I'm drawing from the offscreen canvas to the onscreen one like this:
# grab the width and height of the canvas
{ width, height } = #canvasElement
{ x, y } = offset
# copy image from layer into canvas
#context.drawImage
#offscreenContext.canvas, -x, -y, width, height, -x, -y, width, height
The offset is also the argument into a function which translates the "live" canvas context before all this drawing takes place.
#context.save()
#context.translate(#offset.x,#offset.y)
#renderer.draw(world, #offset)
#context.restore()
In other words we're trying to grab the section of the offscreen context that corresponds to the translated offset of the on-screen context.
This has some issues. When the offset moves the 'camera' far from the origin, you encounter the 'edges' of the offscreen canvas.
Note that when I do the same rendering operations against the onscreen canvas, the elements are fine.
It seems like the offscreen canvas isn't quite as good about handling drawing off its "edges" the same way the canvas is (silently ignores drawing commands outside of its defined region.) In other words, the offscreen canvas doesn't seem to reflect any drawing I've done above or to the left of [0,0] (or alternatively, below or to the right of [width,height].) Are there ways of accommodating this?
Things I've tried:
adjusting up the width and height of the offscreen canvas (this unfortunately seems to have a hard-to-predict impact on coordinates)
I've been able to address the issue by indicating a larger offscreen canvas size. The issue with coordinates seemed to be about Isomer's origin position (which you can override by passing in a different origin.)
A minor note: as per MDN's article on canvas optimization you need to ensure the coordinates and dimensions you pass to drawImage aren't floating point (i.e, call Math.floor on them.) I was running into odd antialiasing artifacts without this.

Calculate the accurate x and y position of a div inside a rotated div

I've implemented a zoom and crop on the HTML5 Canvas. Zoom is actually increasing the height and width of the Canvas so that it looks zoomed. For crop, I wrote an algorithm to select a rectangular area using mouse and then crop it. Now, if I want to crop when the image is zoomed in or out, while selecting the crop area I have to consider the top and left position displacement caused due to the zoom , which works fine.
So I'm now implementing a rotate (using css3 transform: rotate). The problem is, when I rotate the image by a certain angle, the selection appears a little away from the actual mouse position. This used to happen for the zoom effect as well, but since I used to subtract the added left and top distance from the x and y position resp., I was able to draw the selection even when the image was zoomed. I don't understand how I should do it for a rotated image!
The following image might help you understand my problem a little more clearly:
There's a div around the canvas, reflecting the canvas. It'll have the same width, height, top, left properties as the canvas. This is done on purpose since I can't add the selection, which is absolute, as the child of the canvas. Now this cover, when selected in FireBug, still shows as a rectangle with increased width and height and changed top and left positions.
I understand I have to calculate the displacement like I'm already doing for zoom, but I don't know how to do it! I have spent a lot of time trying out stuff like Pythagoras algorithm and rotational matrix and blah blah!
Please help me out!
You can rotate each of the vertices using this function where "pnt" is a vertex, "pivot" is the point you're rotating around and "angle" is the angle in radians
function rotatePoint(pnt, pivot, angle){
var data = figureAngle(pivot, pnt),
theta = data.angle + angle,
rise = Math.sin(theta) * data.length,
run = Math.cos(theta) * data.length;
return {
x: pivot.x + run,
y: pivot.y + rise
}
}
function figureAngle(start, end){
var rise = (end.y - start.y),
run = (end.x - start.x),
length = Math.sqrt(Math.pow(rise, 2) + Math.pow(run, 2));
return {length: length, angle: Math.atan2(-rise, -run) + Math.PI};
}
Then your horizontal shift is going to be the smallest x of the 4 new vertices, and your vertical shift is going to be the smallest y.
EDIT: this assumes your top left coordinate is [0, 0] before you rotate. If not you need to subtract your starting coordinates from the results i.e. if your top-left corner starts at [50, 100], your horizontal shift would be xMin - 50, and your vertical would be yMin - 100

scaling in sprite that preserves central point of it's visible part

I'm having a sprite (canvas) which is being scaled. The canvas has a mask. The problem is that simple scaling (scaleX=newScale; scaleY=newScale;) takes the part of canvas under mask beyound the mask. So I need to move canvas after scaling in such a way that point of canvas under mask remain in the same place. I'm trying to do something like the following:
var deltaScale = newScale / scale;
//w and h are width and height of mask
canvas.scaleX = newScale;
canvas.scaleY = newScale;
canvas.x += (canvas.x + w/2) - (canvas.x + w/2) / deltaScale;
canvas.y += (canvas.y + h/2) - (canvas.y + h/2) / deltaScale;
still after that central point do not remain on the same place. Can somebody prompt me how should I move canvas after scaling?
PS: width and height of canvas is extremely big (some of 25000) if that helps.
UPD: Canvas with it's mask are added on Sprite, mask is having the same sizes as that parent sprite, canvas.x and canvas.y are negative.
The way I did something similar before was to put the canvas sprite inside another sprite eg. zoom which has its reg point in the centre of the screen (or where ever needed). You can then move canvas around inside the zoom sprite (in my case it was draggable) and scale the outer sprite so the centre is zoomed into.
Scaling and positioning different sprites I found much easier than doing an offset.