Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML infinite pan-able canvas

How can I create an 'infinite' canvas with text boxes dotted around? Like this one at the Desmos site.

I have not managed to implement it myself.

like image 362
james clarke Avatar asked Dec 24 '22 12:12

james clarke


1 Answers

The simplest approach to a pan-able canvas is to draw everything relative to an offset, in this example I have called this panX & panY. Imagine it as a coordinate you use to move the viewport (the area of the infinite board that is currently visible in the canvas).

<!doctype html>
<html>
	<head>
		<meta charset="utf-8">
		<style>
			body {
				background-color: black;
			}
			
			canvas {
				position: absolute;
				margin-left: auto;
				margin-right: auto;
				left: 0;
				right: 0;
				border: solid 1px white;
				border-radius: 10px;
			}
		</style>
	</head>
	
	<body>
		<canvas id="canvas"></canvas>
		<script type="application/javascript">
			
			var imageWidth = 180;
			var imageHeight = 160;
			var canvas = null;
			var ctx = null;
			var bounds = null;
			var selectedBox = null;
			var panX = 0;
			var panY = 0;
			var mouseX = 0;
			var mouseY = 0;
			var oldMouseX = 0;
			var oldMouseY = 0;
			var mouseHeld = false;
			var boxArray = [];
			
			function DraggableBox(x,y,width,height,text) {
				this.x = x;
				this.y = y;
				this.width = width;
				this.height = height;
				this.text = text;
				this.isSelected = false;
			}
			
			DraggableBox.prototype.isCollidingWidthPoint = function(x,y) {
				return (x > this.x && x < this.x + this.width)
					&& (y > this.y && y < this.y + this.height);
			}
			
			DraggableBox.prototype.drag = function(newX,newY) {
				this.x = newX - this.width * 0.5;
				this.y = newY - this.height * 0.5;
			}
			
			DraggableBox.prototype.draw = function() {
				if (this.isSelected) {
					ctx.fillStyle = "darkcyan";
					ctx.fillRect(
						this.x - panX,
						this.y - panY,
						this.width,
						this.height
					);
					ctx.fillStyle = "black";
				} else {			
					ctx.fillRect(
						this.x - panX,
						this.y - panY,
						this.width,
						this.height
					);
				}
				
				ctx.fillStyle = "white";
				ctx.fillText(
					this.text,
					this.x + this.width * 0.5 - panX,
					this.y + this.height * 0.5 - panY,
					this.width
				);
				ctx.fillStyle = "black";
			}
			
			window.onmousedown = function(e) {
				mouseHeld = true;
			
				if (!selectedBox) {
					for (var i = boxArray.length - 1; i > -1; --i) {
						if (boxArray[i].isCollidingWidthPoint(mouseX + panX,mouseY + panY)) {
							selectedBox = boxArray[i];
							selectedBox.isSelected = true;
							requestAnimationFrame(draw);
							return;
						}
					}
				}
			}
			
			window.onmousemove = function(e) {
				mouseX = e.clientX - bounds.left;
				mouseY = e.clientY - bounds.top;
				
				if (mouseHeld) {
					if (!selectedBox) {
						panX += oldMouseX - mouseX;
						panY += oldMouseY - mouseY;
					} else {
						selectedBox.x = mouseX - selectedBox.width * 0.5 + panX;
						selectedBox.y = mouseY - selectedBox.height * 0.5 + panY;
					}
				}
				
				oldMouseX = mouseX;
				oldMouseY = mouseY;
				
				requestAnimationFrame(draw);
			}
			
			window.onmouseup = function(e) {
				mouseHeld = false;
				
				if (selectedBox) {
					selectedBox.isSelected = false;
					selectedBox = null;
					requestAnimationFrame(draw);
				}
			}
			
			function draw() {
				ctx.fillStyle = "gray";
				ctx.fillRect(0,0,imageWidth,imageHeight);
				
				var box = null;
				var xMin = 0;
				var xMax = 0;
				var yMin = 0;
				var yMax = 0;
				
				ctx.fillStyle = "black";
				
				for (var i = 0; i < boxArray.length; ++i) {
					box = boxArray[i];
					
					xMin = box.x - panX;
					xMax = box.x + box.width - panX;
					yMin = box.y - panY;
					yMax = box.y + box.height - panY;
					
					if (xMax > 0 && xMin < imageWidth && yMax > 0 && yMin < imageHeight) {
						box.draw();
					}
				}
			}
			
			window.onload = function() {
				canvas = document.getElementById("canvas");
				canvas.width = imageWidth;
				canvas.height = imageHeight;
				
				bounds = canvas.getBoundingClientRect();
				ctx = canvas.getContext("2d");
				ctx.textAlign = "center";
				ctx.font = "15px Arial"
				
				boxArray.push(new DraggableBox(Math.random() * 320,Math.random() * 240,100,25,"This is a draggable text box"));
				boxArray.push(new DraggableBox(Math.random() * 320,Math.random() * 240,100,50,"Another text box"));
				boxArray.push(new DraggableBox(Math.random() * 320,Math.random() * 240,100,50,"Text in a box"));
				boxArray.push(new DraggableBox(Math.random() * 320,Math.random() * 240,100,50,"I find this box quite texing"));
				boxArray.push(new DraggableBox(Math.random() * 320,Math.random() * 240,150,50,"You weren't supposed to find this box"));
				requestAnimationFrame(draw);
			}
			
			window.onunload = function() {
				canvas = null;
				ctx = null;
				bounds = null;
				selectedBox = null;
				boxArray = null;
			}
			
		</script>
	</body>
</html>
like image 138
Mr. Reddy Avatar answered Jan 03 '23 12:01

Mr. Reddy