How can I keep my canvas fast when creating a large map? - html

I'm trying to create a game with HTML5 and a canvas. At the moment I am using the KineticJS library.
The things I have working so far are zooming in and out, dragging from a layer within the stage, and dragging from some objects. This all works nice and fast.
However, the game also needs a landscape, where people can build houses, grainfields etc. Also there need to be some trees and other details. This all makes the canvas very full.
So to test, I made a canvas layer of 10000 x 10000 pixels and made it in 100 x 100 tiles of 100 x 100 px. Just this took so much time it didn't even load.
So I tried it with less tiles (25 x 25) and this does load, but when I try to zoom, drag the stage or drag an object, it is very, very slow and it all needs to contain many more objects.
So the main question is: Can anyone consult me of which method I can use best for the above story? Is a canvas the correct option, or are there other (and better) options?

A 10,000 x 10,000px map is waaay to large.
Conceptually, this is fine. The trick is not to hold-in-memory/draw the whole thing! ie, even if your map is that large you'll only ever draw/update the tiles/objects for the 500 x 500px, or whatever your viewport is, around your character.
See this post, and Simon's answer to achieve this.
And here is the example jsFiddle (full credit to Simon Sarris) associated with the answer.

I found a perfect solution!
I'm going to combine canvas with some regular HTML like div.
I have made this, it makes a field from 15000x15000 px within this div i make 225 new divs which will contain the canvas objects. Every canvas object will have a size from 100x100 px.
The total loading time is about 4 seconds, and I will build the zooming, dragging with just some javascript and divs.
<!DOCTYPE HTML>
<html>
<head>
<style>
body,html {
margin: 0px;
padding: 0px;
}
#container{
width: 15000px;
height: 15000px;
}
.canvas{
float: left;
margin: 0px;
padding: 0px;
height: 1000px;
width: 1000px;
}
</style>
<script src="/js/jquery.js"></script>
<script src="/js/kinetic.js"></script>
</head>
<body>
<div id="zoomer" style="position: absolute;"></div>
<div id="container">
<?php for ($x = 1;$x<=15;$x++){?>
<?php for ($y = 1;$y<=15;$y++){?>
<div class="canvas" id="blok<?php echo $x;?>-<?php echo $y;?>"></div>
<?php } ?>
<?php } ?>
</div>
<script>
var bk = {};
$.each($(".canvas"),function(){
var id = this.id;
bk[id] = new Kinetic.Stage({
container: this.id,
width: 1000,
height: 1000
});
var ly = new Kinetic.Layer();
for(x = 0;x<10;x++){
for(y = 0;y<10;y++){
var r = Math.random();
var g = Math.random();
var b = Math.random();
var tile = new Kinetic.Rect({
width: 100,
height: 100,
x: x * 100,
y: y * 100,
fill: "rgb("+ parseInt(255*r) +","+ parseInt(255*g) +","+ parseInt(255*b) +")"
});
ly.add(tile);
}
}
ly.draw();
bk[id].add(ly);
});
</script>
</body>
</html>
Anyway, thanks for the effort!

Related

HTML5 canvas: how do I query or modify the underlying bitmap dimensions as opposed to the DOM element dimensions?

I'm using an HTML5 canvas element:
<style>
#myCanvas {
width: 100%;
height: 100%;
object-fit: contain;
}
</style>
<canvas id="myCanvas" ontouchstart="..." />
I understand that there is an underlying bitmap, and then there is a DOM element that this bitmap is boxed in, and the 2 objects have different dimensions, so e.g. the bitmap is scaled/squished to fit into the DOM element. It would be easier for me to understand if the bitmap dimensions always equalled the DOM element dimensions, but that's not how it works.
How do I query the respective dimensions?
How do I set the respective dimensions?
How do I see the 2 sets of dimensions in the Chrome developer tool?
How do I keep the 2 sets of dimensions in sync?
This is important because mouse-clicks and finger touches are delivered in DOM coords but we'd usually need to interpret them in canvas coords.
(And a related question: have I done it correctly, to set the DOM element to expand to the maximum dimensions allowed by the parent?).
To get the dimensions of the canvas's underlying bitmap:
var w = canvas.width;
var h = canvas.height;
To get the dimensions of the DOM element:
var w = canvas.clientWidth;
var h = canvas.clientHeight;
To set the canvas's underlying bitmap:
canvas.width = 900;
canvas.height = 400;
To set the DOM element's dimensions, this is down to CSS styles and whatever else is on the screen and it's a more complex question.
In Chrome, "Developer Tools", Chrome displays the DOM element dimensions and I couldn't find any way to get the canvas underlying bitmap dimensions other than typing in "canvas.width" and "canvas.height" into the commandline.
As for trying to arrange such that height==clientHeight and width=clientWidth , I would guess that it's possible but it's probably also difficult and probably undesirable.
So to map a touch action or mouseclick from DOM element coords to canvas bitmap coords, a simple scaling operation seems sufficient:
function elementCoordsToCanvasCoords(X,Y)
{
X = X * canvas.clientWidth / canvas.width;
Y = Y * canvas.clientHeight / canvas.height;
return [X,Y];
}
function onTouchStart(event)
{
fingerStart(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
}
function fingerStart(X, Y)
{
[X,Y] = elementCoordsToCanvasCoords(X,Y);
...
}

how to draw lines with svg or using html div

I have created an app in which instead of filling form manually on paper user can generate a request for creating email of the organisation domain through app. The user can fill the form and the form will be sent to HR department and than after approval of HR department the form will be sent to IT department and after the approval of IT manager the email will be created by IT staff. I want to give the rights for the user to see and check the status of his/her request wether it is in HR approval process or in IT approval process. All he has to do is search by ID which I will provide him on submitting the form. I attached the pic what I really want you can see. Explanation: when user search and track his request I want to show the status through flag . I mean if if is in HR process than the flag will be shown on HR . Same for IT. I have checks for all approval in my database . Like I have properties HR approval , IT approval . In my table . What I need here I don't know any Api or JS library which can provide this type of visuals. How can i achieve this. I am using .net mvc Sql and entity framework as my core technologies.
I used svg line to create that line on x-axis but it is not fulfilling my requirements , whats wrong with the code . or is there any other good way to create it ?
<svg height="210" width="500" xmlns="http://www.w3.org/2000/svg" version="1.1">
<line x1="40" x2="120" y1="100" y2="100" stroke="black" stroke-width="20" stroke-linecap="round"/>
</svg>
I would appreciate if someone guide with code snippet.
I tried in this way too but it is not working either
<div style="width:auto ; margin-left:5%" class="col-md-2">
IT
</div>
<div style="width:auto ; margin-left:5%" class="col-md-2">
HR
<div>
<img src="~/Content/148878.png" />
</div>
</div>
<div style="width:auto ; margin-left:5%" class="col-md-2">
VP
</div>
<div style="width:auto ; margin-left:5%" class="col-md-2">
FH
</div>
<div style="width:auto; margin-left:5% " class="col-md-2">
SR
</div>
<div style="width:auto ; margin-left:5%" class="col-md-2">
FH
</div>
</div>
Have you considered using a canvas instead? HTML Canvases can be easily controlled from JS and they can easily load images. You can load images and text as well, so you could easily load the image of the line, the text for the title (although that could be an image as well), and load the image of the flag wherever it needs to be. It would take very little code and very little canvas experience as well. A few google searches could tell you everything you need to know. You can find a lot of tutorials for this in the canvas tutorial at https://www.w3schools.com/graphics/default.asp
If you have any questions about using canvas that aren't answered by the tutorial, they will most likely have answers here on stack overflow.
I have attached snippet. The canvas seems a little blurry in the snippet, but you should not have this issue on an actual webpage. I also left some comments that could be very helpful in the snippet. I still urge you to visit the canvas tutorial I linked to if you decide to use a canvas. It is extremely helpful, and everything written in the snippet could be much better learned in just a few minutes in the tutorial.
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Draw your title. You may use text or load an image.
ctx.font = "15px Arial";
let text = "Email Request Process Live Tracking";
ctx.fillStyle = "#0000ff";
ctx.fillText(text,canvas.width/2 - ctx.measureText(text).width/2,20);
/* Draw the progress bar. You would probably want to use an image here
but I do not have the image, so I am drawing a gray rectangle
as a placeholder. */
ctx.fillStyle = "#a2a2a2";
let startX = 40;
let startY = canvas.height - 50;
let width = canvas.width - 80;
let height = 10;
ctx.fillRect(startX, startY, width, height);
/* Place the flag somewhere along the progress bar. I am keeping track
of the coordinates for specific positions on the bar in a JSON object
so that you only have to change the variable "progress" to change the
position. Try it out. For this you will probably want to use an image.*/
var progress = "it";
let locations = {
function: {x: startX, y: startY},
hr: {x: startX + width/5, y: startY},
it: {x: startX + 2 * (width/5), y: startY},
processed: {x: startX + 3 * (width/5), y: startY},
actionPerformed: {x: startX + 4 * (width/5), y: startY},
handedoverClosed: {x: startX + width, y: startY}
};
let img = new Image;
/* I got this image off of google images. In reality, you'll probably
want to upload your own flag image and use that instead.*/
img.src = "https://static.goshopping.dk/products/300/kay-bojesen-flag-til-garder-39021-6382-1.jpg";
let x = locations[progress].x
let y = locations[progress].y
ctx.drawImage(img, locations[progress].x, locations[progress].y - 40, 20, 40)
}
#myCanvas {
border:1px solid #d3d3d3;
width:100%;
height:100%;
}
#invis {
display:none
}
<canvas id="myCanvas"></canvas>
<!–– Normally you would not have to load this image in, but I code snippet fails to load the image in the canvas without it for whatever reason. Because it is uneccessary and should not be displayed, I've set its display to none to stop it from being rendered.-->
<img src="https://static.goshopping.dk/products/300/kay-bojesen-flag-til-garder-39021-6382-1.jpg" id="invis">

How can I dynamically resize a div?

I found some answers about dynamically doing stuff to divs, but none of them did what I want.
I am working with Raphael js vector library and need a div for the canvas where the objects are going to be drawn. For example:
<html>
<head>
<style>
#canvas
{
height: 2000px;
position: absolute;
width: 1500px;
}
</style>
</head>
<body>
<div id="canvas"></div>
<!-- some code here -->
</body>
</html>
In my JS file I did the following:
var paper = Raphael("canvas"),
r = paper.rect(100, 100, 100, 100),
c = paper.circle(500, 500, 80);
Now on my application, the user will populate the canvas by creating objects like r and c I showed above. But the problem is that once an object gets created outside of canvas' dimensions, it will not be show on the paper/canvas.
I am trying to dynamically enlarge div's size to i.e. height: 3000px, width: 2000px. I found some answers about absolute position but that did not help. Also found about div overflow, but this applies to div's parent element. Any help will be appreciated.
What you need to do is resize both the containing div and the paper. In this example I will show your circle is cropped bottom right.
When you click the canvas, it will resize to show your circle is drawn correctly.
See http://jsfiddle.net/kRzx5/7/
var paper = Raphael("canvas"),
r = paper.rect(100, 100, 100, 100),
c = paper.circle(500, 500, 80);
// size i want to resize to
var w = 1000;
var h = 1000;
var canvas = $('#canvas');
canvas.on('click', function(){
// resize container
$(this).css({width: w, height: h});
// resize paper
paper.setSize(w, h);
});
In this example I am using an arbitrary size to use, you could keep checking the positions and sizes of each object that gets added, and find the item which has largest value on x and y when checking its bounding box, then resize to this. So paper will always fit the items that get added.

Generating a random image with specific content at each page reload/refresh

So I have a banner on a site, but I want to make it so that each time the page loads, a different image appears. More precisely, I want (say 50) squares (say having a black border, white fill) of random size (say from 5 pixels to 20 pixels in size) in random positions of a 750x63 px frame, with a white background.
What would be the best way to do this? I know a little JavaScript and HTML (and am very willing to learn more), but I really have no idea where to start. This is for my personal webpage, which I wish to spruce up a bit. Right now the fanciest code I have is some JavaScript for a simple Lightbox interface.
Wow, that was easier and more fun than I expected. Here's the code, that goes in the <body> section of my HTML code, optimized for a 750x80px frame. The random integer generator I got from this other question.
<canvas id="canvas" width="750" height="80"></canvas>
<script type="text/javascript">
//Random integer generator
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
//Loop to make 80 squares
for (var i=0;i<80;i++) {
//The x-position of the square, with 5px padding in frame
var sx = getRandomInt(5,705);
//The y-position of the square, with 5px padding in frame
var sy = getRandomInt(5,35);
//The height of the square, smallest 8x8px, largest 40x40px
var sh = getRandomInt(8,40);
//First, create a black square
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect (sx, sy, sh, sh);
//Second, create a white square that's 4px shorter and thinner,
//leaving a boundary of 2px
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect (sx+2, sy+2, sh-4, sh-4);
}
}
draw();
</script>
The approach I used is from a Mozilla Developers page. The result is something like this:
Hooray!

Multiple Canvas Zooms behaving erratically

I'm running into a problem with the HTML5 Canvas tag and adjusting the scale several times. After zooming twice, the canvas only uses a fraction of the available canvas height & width, even though I'm adjusting for the zoom level.
<html>
<head>
<script>
var ctx;
var nScale = 1.00;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 2000x2000
drawGrid(); // should be 20 boxes across & 20 down
// NOTE: At this point, the grid is drawing boxes # 20x20 but only using 1/4 of the
// canvas size.
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nWidth, nHeight;
nWidth = Math.floor(ctx.canvas.width / nScale);
nHeight = Math.floor(ctx.canvas.height / nScale);
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
If I were to multiply the height & width by 2 when drawing the grid for the last time it'll draw out the entire canvas size, but I can't figure out why that would be required.
What I'm wondering is:
Is there a way to query a canvas context to find out what the scale value (or the calculated height/width) is? Or am I approaching this correctly and keeping track of values myself?
If so, then I assume it must be something with my math that's messing this up; I just can't pinpoint it. I'm sure I'm just too close to this problem and not seeing the issue. Another set of eyes would help.
Any suggestions would be appreciated.
Thanks!
I've got a version working on http://jsfiddle.net/sBXTn/5/ without using save/restore. This is the code change:
nScale = nZoomLevel * nScale
ctx.scale(nZoomLevel, nZoomLevel);
Previously using ctx.scale(nScale, nScale) meant that when you zoomed by 0.25 (0.5 twice) you were zooming by 0.25 on a context that was 1000x1000. This meant it increased the size to 4000x4000. Using nZoomLevel means you are zooming in relation to the dimensions of the current context.
I was way on the wrong track with this one and I feel silly for it. My math was correct in calculating the effective scale value (1 * 0.5 = 0.5, then scaling it by 0.5 again does = 0.25), but what I was doing was calculating the effective width & height by the original width & height, not the updated width & height.
So if I've scaled the original down to 0.5, the dimensions of the original 500x500 would be 1000x1000. Scaling it further by 0.5, the effective scale is 0.25, but the new width & height should be 4000x4000 (found by scaling 1000 by 0.25 and NOT 500 by 0.25).
Here's the updated code:
var ctx;
var nScale = 1.00;
var nEffWidth, nEffHeight;
function pageLoad() {
ctx=document.getElementById('cnvUni').getContext('2d');
nEffWidth = ctx.canvas.width;
nEffHeight = ctx.canvas.height;
// canvas on page load is 500x500
drawGrid(); // 5 boxes across & 5 down
zoom(0.5); // canvas should be now zoomed out to 1000x1000
drawGrid(); // 10 boxes across & 10 down
zoom(0.5); // effective zoom is now 0.25 = 4000x4000 based on new scaled width/height
drawGrid(); // should be 40 boxes across & 40 down
}
function zoom(nZoomLevel) {
nScale = nZoomLevel * nScale
nEffHeight = nEffHeight / nScale;
nEffWidth = nEffWidth / nScale;
ctx.scale(nScale,nScale);
}
function drawGrid() {
var nGridSize = 100;
var nGridY = 0;
var nGridX = 0;
// sets a random colour each time grid is drawn.
ctx.strokeStyle = 'rgb(' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ',' + Math.floor(Math.random()*256) + ')';
for (nGridY=0;nGridY < nEffHeight;nGridY+=nGridSize) {
for (nGridX=0;nGridX < nEffWidth;nGridX+=nGridSize) {
// draw the box;
ctx.strokeRect(nGridX, nGridY, nGridSize, nGridSize);
}
}
}
</script>
</head>
<body onload="pageLoad();">
<canvas id="cnvUni" width="500" height="500">
Canvas doesn't work.
</canvas>
</body>
</html>
Thanks for everyone who took a peek!