I am playing around with a project to learn more about node.js & html canvases.
In my project I have a canvas that I want to keep a fixed bitmap size, but fill its containing div while maintaining its aspect ratio.
I have applied a size of 500x500 to my canvas element, and then applied the following style in CSS.
canvas {
display: block;
width:100%;
height:100%;
object-fit: contain;
background-color: lightgrey;
}
Inside the javascript initially fill the canvas white so I get something like the below, so far so good.
I hook into the mouse events and use them to draw lines. I use the below function to correctly scale events to the canvas.
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect(); // abs. size of element
var raw_x = evt.clientX||evt.touches[0].clientX;
var raw_y = evt.clientY||evt.touches[0].clientY;
var min_dimension = Math.min(rect.width,rect.height);
var x_offset = 0.5*(rect.width-min_dimension);
var y_offset = 0.5*(rect.height-min_dimension);
return {
x: ((raw_x - rect.left - x_offset) / min_dimension) * canvas.width,
y: ((raw_y - rect.top - y_offset) / min_dimension) * canvas.height
}
}
This works, however when drawing on the canvas when the mouse moves over a band on the right side of the image it doesn't update until the mouse leaves the band. The band is the same size as the space on the left of the canvas so I think its related but I don't know how to investigate. I have no Issues if I resize the window till there is no space on either side of the canvas bitmap (and performance is considerably faster). The below gif should make things more clear.
Does anyone have a suggestion on what could be causing this, or a better way for me to achieve the same effect.
Note: I am running chrome version 80.0.3987.149
For anyone else that comes across this, I couldn't find a good solution, beyond using JavaScript to resize the element when the window resize event occurs.
Related
I have an image which as a "ruler" (made of basic divs positioned absolute on top of the image) that are use to measure the ends of the image. Now the idea is that if you long press one of the ruler ends (the dots at the end of the line which are draggable), the image in the background would zoom in that point, and follow the dot if the user moves it. I am able to detect the long press but I cannot get the image to zoom and follow the dot once detected. The code below is where I have done the detection and now I should apply the styling to move the image. I thought of using the transition property but couldn't get it to zoom on the dot. Any help is appreciated...
Here's a codesandbox with how the ruler works: Link
Meaningful code:
const x = get('x', varToUse); //This just gives the x coordinate of the ruler end
const y = get('y', varToUse); //This just gives the y coordinate of the ruler end
const image = ruler.current.parentElement.parentElement.childNodes[1].childNodes[1];
if (zoom) {
image.style.transform = `translate(${x * 2}px, ${y * 2}px) scale(2.0)`;
} else {
image.style.transform = `scale(1.0)`;
}
This is what the ruler looks like just to get an understanding:
You can make the image a div with background-image.
.image {
background-image: url({image_url});
}
so this way you can update the image size and position easily with this properties
.image {
background-size: x y;
background-position x y;
}
I think this way is easier to do the image resizing and zoom abilities.
another way is to use a canvas library that can help you a lot they have lots of built in functions.
I think trying it without library is better for now but as it grows try to move to a canvas library
The first reason is that in the code you provided, the DOM element that is being manipulated is a div id='root'. The image should be selected.
I'm trying to make it so users of my site can resize their canvas by simply dragging the sides of it. I'm using Fabric.js which allows me to resize elements from within the canvas but I need to resize the actual canvas itself. I'm fine with using any new libraries you recommend.
This image should help you understand a bit more of what I want.
As a side note, the Fabric.js team have an interactive toolbox here for you to experiment if you need it.
Put your fabric.js canvas in a wrapper div.
Make the wrapper resizable. Here I'm using CSS resize. It only adds a bottom-left corner as a resize control but it's good enough for the sake of the demo. To have all the edges as controls you can try something like this.
Detect the wrapper's size change. Ideally, you would use something like ResizeObserver. However, since browser support is still about 80% at the time of posting this, you might feel the need to use a polyfill or write something specific to your case. A simple setInterval with size check might prove to be sufficient.
When the wrapper's size changes, set new fabric.js canvas dimensions via setWidth() and setHeight().
const canvas = new fabric.Canvas('c')
canvas.add(new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
left: 100,
top: 50,
}))
const canvasWrapper = document.getElementById('wrapper')
// initial dimensions
canvasWrapper.style.width = '300px'
canvasWrapper.style.height = '150px'
let width
let height
setInterval(() => {
const newWidth = canvasWrapper.clientWidth
const newHeight = canvasWrapper.clientHeight
if (newWidth !== width || newHeight !== height) {
width = newWidth
height = newHeight
canvas.setWidth(newWidth)
canvas.setHeight(newHeight)
}
}, 100)
#wrapper {
border: solid 1px black;
resize: both;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="wrapper">
<canvas id="c"></canvas>
</div>
This isn't something that's going to be easy, but it's definitely possible. First, you may wonder about the native browser element resize CSS property and ResizeObserver, however those have poor support right now. Your best option is to:
Create a canvas element
Detect mousemoves on the canvas:
If the mousemove happens around the edge of the canvas, set the cursor to be the resize icon in the proper direction
If the mouse is currently down, resize the canvas based on the mouse's position.
Remember that every time you resize a canvas, the current image on the canvas is wiped.
I have a pixi.js html canvas with thousands of objects on it and I want the user to be able to zoom into it with the usual rectangular selection area. The brute force way to implement this would be to draw the rectangle on each mouse move and rerender the whole stage. But this seems like a waste of CPU. Plus this is so common in user interfaces, that I suspect that there is already some function in pixi.js or a plugin that solves this.
If there is no plugin: If I could save the whole buffer to some 2nd buffer when the user presses the mouse button, I could draw the rectangle on top, and on every mouse move, copy back the 2nd buffer to the primary buffer before drawing the rectangle. This would mean that I didn't have to redraw everything on every mouse move. But I don't think that one can clone the current buffer to some named secondary buffer.
Another alternative would be to move a rectangular DOM object on top of the canvas, but then I am afraid that the current pixel position will be hard to relate to the pixi.js / html5 canvas pixels.
Is there a better way? Or some plugin / search engine keyword that I'm missing? How would you implement a rubber band in html canvas or pixi.js ?
I ended up solving this with a separate DOM object that is moved over the canvas. The solution also requires the new interaction manager in PIXI 4, that offers a single callback for any mouse movement over the canvas.
In the following, I assume that the canvas is placed at canvasLeft and canvasTop pixels with CSS.
$(document.body).append("<div style='position:absolute; display:none; border: 1px solid black' id='tpSelectBox'></div>");
renderer = new PIXI.CanvasRenderer(0, 0, opt);
// setup the mouse zooming callbacks
renderer.plugins.interaction.on('mousedown', function(ev) {
mouseDownX = ev.data.global.x;
mouseDownY = ev.data.global.y; $("#tpSelectBox").css({left:mouseDownX+canvasLeft, top:mouseDownY+canvasTop}).show();
});
renderer.plugins.interaction.on('mousemove', function(ev) {
if (mouseDownX == null)
return;
var x = ev.data.global.x;
var y = ev.data.global.y;
var selectWidth = Math.abs(x - mouseDownX);
var selectHeight = Math.abs(y - mouseDownY);
var minX = Math.min(ev.data.global.x, mouseDownX);
var minY = Math.min(ev.data.global.y, mouseDownY);
var posCss = {
"left":minX+canvasLeft,
"top":minY+canvasTop,
"width":selectWidth,
"height":selectHeight
};
$("#tpSelectBox").css(posCss);
});
renderer.plugins.interaction.on('mouseup', function(ev) {
$("#tpSelectBox").hide();
mouseDownX = null;
mouseDownY = null;
$("#tpSelectBox").css({"width":0, "height":0});
});
For older version of PIXI, here is an example of pan/zoom without a rectangle
https://github.com/Arduinology/Pixi-Pan-and-Zoom/blob/master/js/functions.js
In May 2015, the Interaction Manager got extended to allow easier pan/zoom handling https://github.com/pixijs/pixi.js/issues/1825 which is what I'm using here.
I am making a whiteboard with angularjs, socketio an node.js.
As long as I use a fixed width/height for the canvas everywhere I can just broadcast the coordinates of the mouse/touch event and recreate the graphic in realtime. However, the problem I am facing is when trying to make the canvas have different sizes across different platforms (think desktop and a smartphone), the canvas has to be scaled and so does the graphic, but this makes things pretty slow.
The approach I am currently taking is to draw the graphic in a temporary hidden canvas of original size, then when there is a pause in the drawing stream (in other words the user has stopped doodling), I scale and copy it to the main canvas. The problem with this is, it doesn't feel very realtime at all, especially when a user keeps doodling without a pause for a while. Another approach I could try is to push all the coordinates in an array, apply 2d affine transformation on it, then redraw the entire thing. Though this too doesn't seem like a good solution for when the array size increases, repeatedly trying to apply transformations in realtime can easily eat up a lot of resources.
Is there any better way to achieve this?
do it with css scale transform. Scale the canvas to fit the size of the device be it a mobile, desktop, or tablet, or whatever.
Have your canvas be fixed width across all devices. Say it's 640px by 480px. Now we'll resize it to fit whatever window.
$(window).resize(function() {
var w = 640; // $('#mycanvas').width();
var h = 480; // $('#mycanvas').height();
var newWidth = $(window).width(); // this could be either smaller or bigger than the canvas
var newHeight = $(window).height(); // same here
var scaleX = newWidth / w;
var scaleY = newHeight / h;
$('#mycanvas').css('transform','scale(' + scaleX + ',' + scaleY +')');
$('#mycanvas').css('-webkit-transform','scale(' + scaleX + ',' + scaleY +')');
});
note: mycanvas could also be a container if any kind that holds the canvas and other divs or whatever. just make sure that w is the width of that container, etc.
note: if someone uses their fingers to draw across the screen, you may need to convert it from scaled coordinates to fixed width (640x480) coordinates.
Btw, I found a different way to do this that might be better. not sure:
Scaling canvas element with static resolution
This is a repost from StackExchange's GameDev section, yet, I find that the problem seems more applicable to StackOverflow because it pertains more to CSS, JS, and positioning techniques rather than JavaScript topics (mostly UnityScript and Phaser) discussed on the former.
I've been making a roguelike-style game in HTML5 without using canvas (only divs) with pure JS (fiddle!). I've been trying to enlarge the tile size (font size) while keeping the player centered within the camera. For some reason, when the tile size isn't equal to the map size, the camera will be slightly off.
Note that the 3D effect is on purpose; I believe it adds some much needed depth, and just looks super cool. :D
When tileScale (line 4 in the fiddle) is 9 (very undesirable; the dots on the player's y axis should be aligned, not at a slight angle):
When tileScale is 25 (on point!):
Here's some relevant (trimmed) code:
window.onresize = function(){
game.viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
game.viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
game.windowSize = Math.min(game.viewportWidth, game.viewportHeight);
// Droid Sans Mono has a ratio of 3:4 (width:height), conveniently.
// This may be problematic. I'm not sure.
game.tileWidth = game.windowSize*.6 / game.tileScale;
game.tileHeight = game.windowSize*.8 / game.tileScale;
}
// Update the camera position (needs help?)
this.updateCamera = function(){
// Get player coordinates (-.5 because we need to get the player tile center)
// times the tileWidth plus the game window (inner square) size divided by two.
var left = ((-game.player.x-.5)*game.tileWidth+game.windowSize/2)+"px";
var top = ((-game.player.y-.5)*game.tileHeight+game.windowSize/2)+"px";
game.planeContainer.style.left = left;
game.planeContainer.style.top = top;
}
How can I ensure that the dots in the center of the screen will be always lined up, instead of on a slight angle? My current evidence suggests that the position of the game.planeContainer object isn't being established correctly.
I know this is a super-tough problem, so any help will be greatly appreciated. Thanks in advance!
(Another fiddle link, in case you skimmed over the first one. :D)
Remove this line from your css stylings on .inner-text to take out the skew:
transform: translate(-50%, -50%);