Draw a Canvas on input boxes in html - javascript

I'm trying to draw a canvas line diagonally on top 2 textboxes placed diagonally (like samsung's pattern passcode) on a button click, but am unable to do so.
I tried this, but without making the input position absolute (which overlaps some text boxes), I'm not finding a good solution.
<!DOCTYPE html>
<html>
<head>
<style>
* {
z-index: 0;
}
canvas {
z-index: 1;
position: absolute;
}
</style>
</head>
<body>
<div id="grid">
<script>
for(let i = 0; i < 2; i++){
for(let j = 0; j < 2; j++){
document.getElementById("grid").innerHTML += `<input type="text" id="cell-${i}-${j}" maxlength="1" value="${2*i+j}" pattern="[A-Za-z]"/>`;
}
document.getElementById("grid").innerHTML += "<br/>";
}
</script>
<button onclick="draw()" id="draw_btn">Draw Line</button>
</div>
<script>
function draw(){
const canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.stroke();
document.body.appendChild(canvas);
}
</script>
</body>
</html>

One approach is to make the canvas absolutely positioned, and place it over the top of the text boxes.
Absolutely positioned elements are positioned relative to their nearest absolutely or relatively positioned parent. If we pop position: relative on our #grid box, and place the canvas inside, then we can position it within the bounds of the grid itself, rather than the whole document.
for(let i = 0; i < 2; i++){
for(let j = 0; j < 2; j++){
document.getElementById("grid").innerHTML += `<input type="text" id="cell-${i}-${j}" maxlength="1" value="${2*i+j}" pattern="[A-Za-z]"/>`;
}
document.getElementById("grid").innerHTML += "<br/>";
}
function draw(){
const canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 40;
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 40);
ctx.stroke();
document.getElementById("grid").appendChild(canvas);
}
#grid {
position: relative;
}
#grid input {
width: 150px;
height: 20px;
box-sizing: border-box;
}
#grid canvas {
position: absolute;
left: 0;
top: 0;
pointer-events: none;
}
<div id="grid"></div>
<button onclick="draw()" id="draw_btn">Draw Line</button>
I'd also recommend adding
pointer-events: none;
to the canvas, as this means the user can still interact with the input boxes below.

Related

How can I draw JavaScript graphical elements on top of custom image?

I have some Javascript drawing random square elements in the DOM. I have a gif (Image) I want these elements to appear over but they keep appearing underneath the gif. I tried defining z-depth and layout parameters to move these elements on top of the image here, but this produced no difference.
Any assistance in achieving the result (drawing elements onclick, on top of this gif) would be much appreciated.
I ultimately want to draw various other images over this image onclick, restricted to this particular area on top of the gif. If someone can suggest a solution to this as well I would be very much grateful!
(Code features some unused elements from my past attempts)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="div.css" />
</head>
<body>
<div style="cursor: pointer;" id="boxy" >
<img src="bg.gif" alt="unfinished bingo card" onclick="create()" />
</div>
</div>
<script>
var body = document.getElementsByTagName("body")[0];
var canvas = document.createElement("canvas");
canvas.height = 1300;
canvas.width = 1300;
var context = canvas.getContext("2d");
body.appendChild(canvas);
var rects = [];
function create() {
// Opacity
context.globalAlpha = 0.7;
var color = '#' + Math.round(0xffffff * Math.random()).toString(16);
context.fillStyle = color;
//Each rectangle's size is (20 ~ 100, 20 ~ 100)
var coordx = Math.random() * canvas.width;
var coordy = Math.random() * canvas.width;
var width = Math.random() * 80 + 20;
var height = Math.random() * 80 + 20;
var rect = {
x: coordx,
y: coordy,
w: width,
h: height
}
var ok = true;
rects.forEach(function (item) {
if (isCollide(rect, item)) {
console.log("collide");
ok = false;
} else {
console.log("no collision");
}
})
if (ok) {
context.fillRect(coordx, coordy, width, height);
rects.push(rect);
} else {
console.log('rect dropped');
}
console.log(rects);
}
function isCollide(a, b) {
return !(
((a.y + a.h) < (b.y)) ||
(a.y > (b.y + b.h)) ||
((a.x + a.w) < b.x) ||
(a.x > (b.x + b.w))
);
}
document.getElementById('boxy').addEventListener('click', create);
document.getElementById('canvas').style.position = "relative";
document.getElementById('canvas').style.zIndex = "10";
</script>
</body>
</html>
#my-div {
width: 1300x;
height: 1300px;
z-index: -1;
}
a.fill-div {
display: block;
height: 100%;
width: 100%;
text-decoration: none;
}
#boxy {
display: inline-block;
height: 100%;
width: 100%;
text-decoration: none;
z-index: -1;
}
.canvas {
display: inline-block;
height: 100%;
width: 100%;
text-decoration: none;
z-index: 10;
}
You have to use position:absolute; to take it out of the html flow.
Now anything added after the image will be placed like the image was never there.
img {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: -10;
}
div {
font-size: 2rem;
color: white;
}
<img src="https://images.unsplash.com/photo-1664273107076-b6d1fbfb973b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80">
<div>Hello i am on top of the image
</div>

How to move canvas objects each time I click the button?

I'm trying to make a game and I have the blocks spawning randomly in the grid in the one fixed row (for example row 5 [horizontally]). Every time I click the button I want the blocks to move from row 5 to row 4(vertically). And then I want the new random blocks to generate on row 5 and so on. How can I do this? Right now it only spawning the new blocks under the first row (on the row 6, then 7 and so on).
//Accessing canvas
var canvas = document.getElementById('grid');
var ctx = canvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
// Drawing grid
var drawingGrid = function() {
for (x = 0; x <= w; x += 60) {
for (y = 0; y <= h; y += 60) {
// Gray grid
ctx.globalCompositeOperation = 'destination-over';
ctx.strokeStyle = "#cccccc";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.moveTo(0, y);
ctx.lineTo(w, y);
if (x % 240 === 0) {
// Black X-axis grid |
ctx.globalCompositeOperation = "source-over";
if (x === 0 || x === 480) {
ctx.lineWidth = 5;
} else {
ctx.lineWidth = 1;
}
// Middle vertical line
if (x === 240) {
// 0-480
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 480);
ctx.strokeStyle = "#222831";
ctx.stroke();
// 480-560
ctx.beginPath();
ctx.moveTo(x, 480);
ctx.lineTo(x, 540);
ctx.strokeStyle = "#cccccc";
ctx.globalCompositeOperation = 'destination-over';
} else {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.strokeStyle = "#222831";
}
} else if (y % 240 === 0 || y === 540) {
// Black Y-axis grid _
ctx.globalCompositeOperation = "source-over";
ctx.strokeStyle = "#222831";
if (y === 0 || y === 540) {
ctx.lineWidth = 5;
} else if (y === 480) {
ctx.lineWidth = 2.5;
} else {
ctx.lineWidth = 1;
}
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(h, y);
}
ctx.stroke();
}
}
};
drawingGrid(480, 540, 'grid');
// Starting coordinates
var posX = 0;
var posY = 240;
// Move blocks on Y axis
function moveY(){
posY +=60;
}
// Spawn random amount of blocks on the field
function gener(){
posX = 60*Math.floor(8*Math.random());
}
function spawnRandomObject() {
// Game Object
ctx.fillStyle = '#f2a365';
ctx.globalCompositeOperation = "destination-over";
ctx.beginPath();
ctx.fillRect(posX, posY, 60, 60);
ctx.stroke();
}
// Blocks moving up
document.getElementById("button").addEventListener("click", function(){
// Spawn random amount of objects
for (var i=0; i<Math.floor((Math.random()*8)+1)*2; i++){
gener();
spawnRandomObject();
}
moveY();
});
body{
background-color: #ececec;
}
canvas{
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
.row{
padding: 20px;
margin: 0;
}
.firstrow{
width:20%;
}
.mainrow{
width:60%;
display: block;
}
.thirdrow{
width: 20%;
}
.header{
background-color: #222831;
color: #ececec;
padding: 20px;
}
.container{
margin-top: 20px;
padding: 0px;
display: flex;
box-sizing: inherit;
}
.thirdrow{
text-align: right;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Shmetris</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- Local CSS -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Header -->
<div class="header">
<h1>Shmetris</h1>
</div>
<div class="container">
<!-- Controls -->
<div class="row first">
<div class="col">
<h1>Score: 0</h1>
<br>
<button type="button" class="btn btn-dark" id="button">Refresh</button>
</div>
</div>
<!-- Game Area -->
<div class="row mainrow">
<div class="col-" id="gamearea">
<!-- Canvas -->
<canvas id="grid" width="480" height="540" style="background: #ececec "></canvas>
</div>
</div>
<!-- -->
<div class="row thirdrow">
<div class="col" style="text-align:left;">
</div>
</div>
</div>
<!-- Script -->
<script src="app.js"></script>
</body>
</html>
I removed your moveY() function and added the moveRow() function. The game objects (the rects) are saved to a list objects. Their y position is increased on each button click. The old drawn positions are removed, the new positions are drawn. Then the randomly generated blocks are added in the 5th row.
Note that the clearRect() function inside the removeCell() also removes parts of the grid. This causes to redraw the grid every time. You can improve the code by splitting your grid creation in using a sub function which draws the grid on only one cell. Then you can redraw the grid only on those cells that are needed. This probably is a performance boost and makes the code more beautiful in my eyes, but it works like this too.
I also suggest to use length instead of 60 and calculate the thicker grid lines by the length, e.g. 8 * length instead of 480 and so on.
//Accessing canvas
var canvas = document.getElementById('grid');
var ctx = canvas.getContext('2d');
var w = ctx.canvas.width;
var h = ctx.canvas.height;
// Drawing grid
var drawingGrid = function() {
for (x = 0; x <= w; x += 60) {
for (y = 0; y <= h; y += 60) {
// Gray grid
ctx.globalCompositeOperation = 'destination-over';
ctx.strokeStyle = "#cccccc";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.moveTo(0, y);
ctx.lineTo(w, y);
if (x % 240 === 0) {
// Black X-axis grid |
ctx.globalCompositeOperation = "source-over";
if (x === 0 || x === 480) {
ctx.lineWidth = 5;
} else {
ctx.lineWidth = 1;
}
// Middle vertical line
if (x === 240) {
// 0-480
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, 480);
ctx.strokeStyle = "#222831";
ctx.stroke();
// 480-560
ctx.beginPath();
ctx.moveTo(x, 480);
ctx.lineTo(x, 540);
ctx.strokeStyle = "#cccccc";
ctx.globalCompositeOperation = 'destination-over';
} else {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.strokeStyle = "#222831";
}
} else if (y % 240 === 0 || y === 540) {
// Black Y-axis grid _
ctx.globalCompositeOperation = "source-over";
ctx.strokeStyle = "#222831";
if (y === 0 || y === 540) {
ctx.lineWidth = 5;
} else if (y === 480) {
ctx.lineWidth = 2.5;
} else {
ctx.lineWidth = 1;
}
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(h, y);
}
ctx.stroke();
}
}
};
drawingGrid(480, 540, 'grid');
var length = 60;
// Starting coordinates
var posX = 0;
var posY = 4 * length;
var objects = []
function moveRows(){
for(var i = 0; i < objects.length; i++){
// remove old objects
removeCell(objects[i][0], objects[i][1]);
// move objects
objects[i][1] += length;
}
drawingGrid();
for(var i = 0; i < objects.length; i++){
// redraw objects on new location
drawCell(objects[i][0], objects[i][1]);
}
}
// Spawn random amount of blocks on the field
function gener(){
posX = length*Math.floor(8*Math.random());
}
function spawnRandomObject() {
// Game Object
drawCell(posX, posY);
objects.push([posX, posY]);
}
function drawCell(x, y, color){
ctx.fillStyle = "#f2a365";
ctx.globalCompositeOperation = "destination-over";
ctx.beginPath();
ctx.fillRect(x, y, length, length);
ctx.stroke();
}
function removeCell(x, y){
ctx.clearRect(x, y, length, length);
}
// Blocks moving up
document.getElementById("button").addEventListener("click", function(){
// Spawn random amount of objects
moveRows();
for (var i=0; i<Math.floor((Math.random()*8)+1)*2; i++){
gener();
spawnRandomObject();
}
});
body{
background-color: #ececec;
}
canvas{
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
}
.row{
padding: 20px;
margin: 0;
}
.firstrow{
width:20%;
}
.mainrow{
width:60%;
display: block;
}
.thirdrow{
width: 20%;
}
.header{
background-color: #222831;
color: #ececec;
padding: 20px;
}
.container{
margin-top: 20px;
padding: 0px;
display: flex;
box-sizing: inherit;
}
.thirdrow{
text-align: right;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Shmetris</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<!-- Local CSS -->
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Header -->
<div class="header">
<h1>Shmetris</h1>
</div>
<div class="container">
<!-- Controls -->
<div class="row first">
<div class="col">
<h1>Score: 0</h1>
<br>
<button type="button" class="btn btn-dark" id="button">Refresh</button>
</div>
</div>
<!-- Game Area -->
<div class="row mainrow">
<div class="col-" id="gamearea">
<!-- Canvas -->
<canvas id="grid" width="480" height="540" style="background: #ececec "></canvas>
</div>
</div>
<!-- -->
<div class="row thirdrow">
<div class="col" style="text-align:left;">
</div>
</div>
</div>
<!-- Script -->
<script src="app.js"></script>
</body>
</html>

How do I ensure canvas is contained in viewport

I have a canvas that is the only thing on a page I want really want to see. I'm trying to make it such that when I resize my window, I always have the entire canvas viewable with no scroll bars and maintain the aspect ratio.
MCVE
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let w = canvas.width;
let h = canvas.height;
ctx.fillStyle = 'teal';
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.rect(5, 5, w - 5, h - 5);
ctx.stroke();
* {
background-color: white;
max-height: 100%;
max-width: 100%;
margin: 0;
padding: 0;
}
div#canvasDiv {}
canvas#canvas {
display: block;
margin: auto;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<div id="canvasDiv">
<canvas id="canvas" width="500" height="500" />
</div>
</body>
</html>
All here at jsfiddle
With this attempt, it rescales just fine when I resize the width. However, when I resize the height, I get overflow.
Resize Width with appropriate rescaling
Resize Height with unwanted overflow
You may like to read this article: [Centering in CSS: A Complete Guide](https://css-tricks.com/centering-css-complete-guide/
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let w = canvas.width;
let h = canvas.height;
ctx.fillStyle = 'teal';
ctx.fillRect(0, 0, w, h);
ctx.strokeStyle = 'red';
ctx.lineWidth = 10;
ctx.rect(5, 5, w - 5, h - 5);
ctx.stroke();
* {
background-color: white;
max-height: 100%;
max-width: 100%;
margin: 0;
padding: 0;
}
canvas#canvas {
display: block;
margin: auto;
position:absolute;
top:0;bottom:0;
left:0;
right:0;
}
<div id="canvasDiv">
<canvas id="canvas" width="500" height="500"/>
</div>

HTML5 Canvas y-height not correct

So I'm just trying to make a simple animated HMTL canvas with an animated block that moves around the canvas using WASD. I initially noticed that painting a rectangle on the canvas of size 5,5 made what looked like a rectangle of size 5,10. When testing my redrawing function and printing to the console the x and y location of the element in the canvas, I noticed that my rectangle can go from 1-300 in the X direction, but only 1-150 in the Y direction. This happens even though the canvas is styled as 300,300. Can anyone figure out if I've done something silly?
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script type="text/javascript" src="script.js" defer></script>
</head>
<body>
<div class="text-holder holder" id="instruction-holder">
<p class="text" id="instruction">Use WASD to navigate around the viewer</p>
</div>
<div class="holder" id="canvas-holder">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
and the css
#canvas {
width: 300px;
height:300px;
margin-left: auto;
margin-right: auto;
display: block;
border-style: solid;
border-color: grey;
border-radius: 1px;
}
.holder {
display: block;
margin-left: auto;
margin-right: auto;
text-align: center;
}
and the js
var UP = "87", DOWN = "83", LEFT = "65", RIGHT = "68", X = 10, Y = 5, XSIZE = 10, YSIZE = 5;
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var xPos, yPos;
window.addEventListener("load",init);
document.addEventListener('keydown',move);
function init() {
xPos = 1;
yPos = 1;
ctx.fillStyle = "black";
ctx.fillRect(xPos,yPos,XSIZE,YSIZE);
}
function clear() {
ctx.clearRect(0,0,300,300);
}
function reDraw(delX, delY) {
console.log(yPos+delY + " " + (xPos+delX));
if (xPos+delX > 0 && xPos+delX < 300 &&
yPos+delY > 0 && yPos+delY < 150) {
clear();
ctx.fillStyle = "black";
xPos = xPos+delX;
yPos = yPos+delY;
ctx.fillRect(xPos,yPos,XSIZE,YSIZE);
}
}
function move(ev) {
var delX, delY;
var key = ev.keyCode;
if (key == UP) {
delX = 0;
delY = -Y;
} else if (key == DOWN) {
delX = 0;
delY = Y;
} else if (key == LEFT) {
delX = -X;
delY = 0;
} else if (key == RIGHT) {
delX = X;
delY = 0;
}
if (delX != undefined && delY != undefined) {
reDraw(delX, delY);
}
}
You have to set the size of canvas explicitly or it will use the default size of 300x150 (CSS only scales the element, not the bitmap - the bitmap of 300x150 is stretched to fit what you see on screen by the CSS rule, but the bitmap will remain the same size internally):
<canvas id="canvas" width=300 height=300></canvas>
Then just remove these:
#canvas {
/* width: 300px; */
/* height:300px; */
...

Drawing with HTML 5 canvas rectangles (pixel size) gets a black rectangle instead of blue?

I expect to draw a large blue rectangle of 320 X 240 pixels on the Canvas in Firefox but instead I get a black rectangle. I'm drawing pixels actually just to experiment and for some reason this doesn't work properly. Everything in the code seems to make sense inside the JavaScript draw function but it's a nogo? Any ideas, here's the code,
<html>
<head>
<title>Canvas</title>
<script type="text/javascript">
function draw()
{
var canvas = document.getElementById("canvas");
if (canvas.getContext)
{
var ctx = canvas.getContext("2d");
var red = 0;
var green = 0;
var blue = 255;
ctx.fillStyle = "rgb( red, green, blue)";
for(var i = 0; i < 320; i++)
{
for(var j = 0; j < 240; j++)
{
ctx.fillRect (i , j , 1, 1);
}
}
}
}
</script>
<style type="text/css">
body
{
margin: 50px 50px;
}
canvas
{
border: 1px solid black;
}
#container
{
position: relative;
width: 640px;
height: 480px;
margin: 0 auto;
}
</style>
</head>
<body onload="draw();">
<div id = "container">
<canvas id="canvas" width="640px" height="480px"></canvas>
</div>
</body>
</html>
You need to use ..." + variable + "... to let the String use the variable's values. As it is you were passing rgb( red, green, blue) as the fillStyle, which then defaults to black because it's invalid
ctx.fillStyle = "rgb("+red+","+green+","+blue+")";
Demo
ctx.fillStyle = "rgb(" + [red,green,blue].join(',') + ")";

Categories

Resources