Tricky screen sizing and scaling (on Javascript) - javascript

I have a tricky question that might just have a simple solution, although I trully don't see it now.
So, I've been working around HTML5 element and, obviously, doing the interaction methodology in JavaScript.
One of the objectives of this work is to be able to use a mobile device [MD] (iOS or Android, phone or tablet) as a remote controller for an application that will be served by another machine (eg. a laptop or external display) and both will be showing the same thing on each of the screens on different scales.
So, I wanna have an event occur when the canvas is 80% filled (or in this case, "erased" (which I already have by calculating the total number of [initial] pixels) and each device has a different count since the screen sizes/resolutions are different.
This is the tricky part: How will I be able to "scale" the MD pixel count and mirror that to the bigger screen?
For concrete measures, how will I be able to implement the following example:
I draw a line on the MD that goes for 300px wide, and for simplicity, let's say that this represents 10% of the MD canvas (which on both the screens is in fullscreen).
I want the external monitor (which has a higher resolution) to mirror this event but on an appropriate scale so that those 10% on the MD represent the same (scaled) 10% of "canvas real estate"
Just in case the text is too confusing, I'll leave the code bellow:
function totalPix(x, y) {
var total = x * y;
var objective = (total * 80) / 100;
}
function canvasApp() {
//prevent from scrolling (no bouncing)
document.body.addEventListener('touchmove', function(event){
event.preventDefault();
}, false);
if(!canvasSupport()) {
alert("No canvas support on this device!");
return;
} else if(!socketSupport) {
alert("No websocket support on this device!");
} else {
//create canvas on every load (//TODO)
var elemDiv = document.getElementById("content");
var newElem = document.createElement("canvas");
newElem.setAttribute("id", "frontscreen");
elemDiv.appendChild(newElem);
drawScreen();
function drawScreen() {
//Setup canvas
var canvas = document.getElementById("frontscreen");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
totalPix(canvas.width, canvas.height);
ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
//Foreach touchmove event, send position to server
canvas.addEventListener('touchmove', function(event) {
for (var i = 0; i<event.touches.length; i++) {
var touch = event.touches[i];
ctx.globalCompositeOperation = "destination-out";
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(touch.pageX, touch.pageY, 30, 0, 2*Math.PI, false);
ctx.fill();
ctx.stroke();
}
}, false);
window.onresize = function resizeCanvas() {drawScreen();};
}
}
}

If I understand you correctly, it's as simple as changing the size property on the HTML style property of your <canvas> element. For example, let's say you fill in a 300 by 300 px. square on Monitor A, which occupies 10% of the screen real estate (I know, big monitor). Then you load the same page on Monitor B, which is twice the size of Monitor A. (Really really big monitor, just bear with me here. It's an example.) Naturally, it will only occupy 5% of the screen's real estate.
If you want that 300px to always occupy the same percentage of size on all screens (but still be 300px on the canvas), you can do something like this:
var canvas = document.getElementById("mycanvas");
var heightAsPercent = 10;
var widthAsPercent = 10;
canvas.style.height = (heightAsPercent / 100) * screen.height;
canvas.style.width = (widthAsPercent / 100) * screen.width;
That way, the canvas will always occupy 10% of the screen, whether the monitor width is 3000px or 6000px. I've obviously chosen very verbose variable names for clarity, so feel free to modify them as needed.
The reason this works is that you're only modifying the CSS properties of the canvas, which affect only how it's rendered, not the actual <canvas> data. I came across this little trick by accident, and it drove me nuts until I figured out why it was doing this. Now it actually comes in handy. :)

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.

Resizing canvas size gives advantage to larger screens. Not sure how to keep the functionality while making it fair

So I am trying to learn javascript by making a game where you click on a canvas to move a circle and you have to dodge multiple other circles coming in your direction. I am close to the final result but I just realized that the larger your screen is, the easier it is for you to play.
The reason is that the enemies spawn at the edges of the canvas, so if the canvas is bigger, you have more time to react to them since their speed doesn't change. The game is supposedly refreshing around 60fps and each time it resizes the canvas depending on if you change your window size. The only thing I can think of is increasing the speed of enemies with the size increase but I don't know if there's any other way to go about this. Maybe I could increase the size of the of the player and the enemies to accommodate the change in window size but I don't know which is better or how to make sure I am consistent with the ratio increase.
Any suggestions would be much appreciated. Thanks for your time
Here's my full code: https://jsfiddle.net/r2f6eb89/2/
It doesn't actually run on this site so it's just a reference for my logic.
Here are the resizing functions:
/*
function setCanvasSize() {
//c.width = window.innerWidth-100;
//c.height = window.innerHeight-100;
if (window.innerWidth < window.innerHeight) {
c.width = window.innerWidth - 150;
c.height = window.innerWidth - 150;
} else {
c.width = window.innerHeight - 150;
c.height = window.innerHeight - 150;
}
}
*/
function setCanvasSize() {
c.width = 600;
c.height = 600;
}
There's actually two kinds of "width" and "height" property. One is the width and height you can actually see in your website, that is, the one you can change by the property on your html file element. The other one is the width and height you set in the javascript file, for setting the coordinate you use to print image or draw in the canvas.
Example for first type of the width/height property:
<canvas id="my_canvas" width="500" height="200"></canvas>
Example for second type of the width/height property:
var canvas = document.getElementById("my_canvas");
canvas.width=500;
canvas.height=200;
It seems like you are changing the second type of the width/height property each refresh. If that's not what you want, try modifying the first type of width/height. It seems like a simpler solution to your question. To modify the first type of property, the CSS property in javascript, here's the code:
canvas.style.width="500px";
canvas.style.height="200px";
Hope that helps :)

Android significantly slower in resizing and moving multiple canvas elements

I need to build a kind of map in canvas, which must be able to hold more than 10.000 elements and thus has quiet big dimensions in some cases (> 8000px width, >4000 px height). Also I need to pan and zoom the map.
After some fiddeling around with existing libraries (Paper.js) and possible other solutions (Leaflet Map) I eventually wrote an own library from scratch, because the main requirement is, that is should be really really fast (loading, mouseovers, ...) and none of the libraries I tried could offer all of the aspects.
The structure is as follows:
I have one map object with an associated Control object, which registers events and has resize methods etc.
A map is divided in mutliple even sized tiles (1024px x 1024px - customizable) because using the map with only one canvas at a size over 8000px width made it incredibly slow
Each tile is associated with a canvas
The elements (just circles) are added to one or multiple tiles (If it's on the edge) - more specifically to the tiles' canvas.
The tiles are placed within an container div which has the dimensions of the map area (when not zoomed out)
The container div is placed within a viewport div to enable the map being displayed as a "widget"
Zooming scales every tile/canvas and the container. For sake of performance I sacrificed smooth zoom and implemented a customizable amount of zoom steps, which still feels okay.
Panning set's the topand left style of the container.
Events used are window.resize, mousewheel, DOMMouseScrol, mousedown, mouseup, mousemove, touchstart,touchend,touchmove and Hammertime pinch
This alltogether runs satisfying on Desktop Browsers, and iPhones (tested with SE, 6S) but on every Android device I tested it (Samsung S4, One Plus One and another 1 year old device, and android studio emulator) it runs extremly slow. Drawing of the Map is fine in speed, but zooming and panning is near to impossible.
The code is too comprehensive to post it here, so I'm asking you if there are any known problems with canvas on android, that could explain this problem, or maybe some issues with the way I built the structure that could produce issues with android. I'm really clueless here, since it works on desktop and iPhone.
The real problem you're hitting is you're overloading the GPU. Loading that much data all and once then moving it around is going to put a toll on the GPU and likely force the browser into software rendering mode, which is a big performance hit.
Instead, I'd suggest changing your approach. Rather than having various large canvases, you should have one canvas that is, at most, the size of the users screen. Then, utilize methods of the canvas API such as scale and translate to render what you need. For an added bonus, avoid trying to render things which are off screen.
It may seem like having to redraw the scene every time you move around would be slow but it's not. The reality is that either you specify exactly what needs to be drawn or the browser has to attempt to draw all of it again when you shift it around. Here's a brief example of how you can render and move large images.
var ctx = document.querySelector('canvas').getContext('2d');
var img = new Image();
img.src = 'https://placeimg.com/1000/1000/nature';
img.onload = start;
function start() {
var xDirection = -1;
var yDirection = -1;
var xPosition = 0;
var yPosition = 0;
var prev = Date.now();
(function render() {
var now = Date.now();
var delta = (now - prev) / 1000;
xPosition += xDirection * delta * 20;
yPosition += yDirection * delta * 40;
if (xPosition > 0) {
xPosition = 0;
xDirection *= -1;
} else if (xPosition < -320) {
xPosition = -320;
xDirection *= -1;
}
if (yPosition > 0) {
yPosition = 0;
yDirection *= -1;
} else if (yPosition < -240) {
yPosition = -240;
yDirection *= -1;
}
prev = now;
ctx.save();
ctx.translate(xPosition, yPosition);
ctx.drawImage(img, 0, 0);
ctx.restore();
requestAnimationFrame(render);
})();
}
body {
background: #111;
}
canvas {
background: #FFF;
}
<canvas width="320" height="240"></canvas>

How to scale my canvas in reason, with the zoom scale?

I am working on a multiple web game using JavaScript. My canvas is currently set to the width and the height of my screen.
html
<canvas id = "canvas"></canvas>
javascript
var c=document.getElementById("canvas");
var ctx = c.getContext("2d");
//Making canvas scale;
c.width = window.innerWidth;
c.height = window.innerHeight;
function resize(){
//Add some code
}
My problem is, I do not want my players to zoom out, well not by default. It will make the game look bad and give the players an edge over everyone else. So I need to add some code to go into the resize method, that regardless of scale, the canvas will not be zoomed out. If the end result is something blurry at 300%+ that is fine.
IMPORTANT: the resize function cannot remove or reset the canvas back to default.
There are various ways to scale a canvas.
First off, there are 2 main parameters for the canvas size:
-Canvas Pixel Count. Set via canvas.width = 1000
-Canvas Display Pixel Size. Set via canvas.style.width = '1000px'
If you want all players to see a 1000x1000 region but displaying it fullscreen:
canvas.width = 1000;
canvas.height = 1000;
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
There is also another option with canvas.style.transform = 'scale(2,2)'.
This method is the closest thing to the browser zoom done via Ctrl+ or Ctrl-.
The big advantage of transform is that the scaling is applied to all DOM children elements. If your game is using HTML for its interface, then this is the way to go. (By applying the scaling on the div containing the canvas + HTML interface.

Resize HTML5 canvas element

How can I achieve, so that the HTML5 canvas element ist resizeable?
I would like to implement this element so that you can scale it in any size. The event of the scaling should be the mouse which snaps the edge and the user resizes the element.
I've already read about how you can achieve this on a object in the canvas element. But in my case I need this on the canvas element itself (<canvas>).
Setting a canvas's width or height properties has the effect of clearing the canvas. Even canvas.width = canvas.width; will cause you to lose everything in the canvas. Sometimes this is desirable, but in your case it probably isn't.
What you will probably need to do is something like this
var myCanvas = document.getElementById('myCanvas');
var tempCanvas = document.createElement('canvas');
tempCanvas.width = myCanvas.width;
tempCanvas.height = myCanvas.height;
// save your canvas into temp canvas
tempCanvas.getContext('2d').drawImage(myCanvas, 0, 0);
// resize my canvas as needed, probably in response to mouse events
myCanvas.width = newWidth;
myCanvas.height = newHeight;
// draw temp canvas back into myCanvas, scaled as needed
myCanvas.getContext('2d').drawImage(tempCanvas, 0, 0, tempCanvas.width, tempCanvas.height, 0, 0, myCanvas.width, myCanvas.height);
In most browsers, the scaling will be done with a bicubic scaling algorithm, causing it to get blurry. In some cases you can set a CSS property to cause nearest neighbor on the canvas if you want, but browser support for this is very spotty right now. You can instead manually do a nearest neighbor scale , as this question shows: How to stretch images with no antialiasing
Alternative CSS Approach
Another approach is to scale the canvas using CSS. In Chrome/Safari/IE you can just do:
<canvas style="zoom:200%" />
In Firefox, you can use a scale transform to achieve the same effect:
<canvas style="-moz-transform:scale(2)" />
In many ways this approach is easier, but it comes with its own little gotchas and browser specific quirks.
I think you need to bind the onresize event to your body of document.
Then inside the the event you need to resize the canvas using window.innerWidth and window.innerHeight.
Have a look # http://kile.stravaganza.org/lab/js/canvas_resize/ (view source)
Although it's a bit late to answer this question, I'd like to share what I found to solve the same question. Take it a look please.
panel.css
#Panel {
width: 100%;
height: 30%;
}
app.js
var widthG = 0, height=G = 0;
function updateScale() {
widthG = parseInt($('#Panel').width());
heightG = parseInt($('#anel').height());
}
...
function updateCanvas() {
ctx = $('#canvas').get(0).getContext('2d');
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
ctx.canvas.width = widthG;
ctx.canvas.width = heightG;
}
I also tried to re-assign these properties by css syntax, but it doesn't work.
$('#canvas').width(panelW);
$('#canvas').height(panelH);
Hope this helps ppl suffered from the same question.

Categories

Resources