I'm currently experimenting with 2D arrays. I can't think of a way at the moment to do this, but I want to be able to click one of the cells and have it change to the opposite color. To demonstrate, if I click a blue cell, I want it to turn red, and the opposite action if I click a red cell. Here is my code:
<!DOCTYPE html>
<html>
<head><title>Visualizing 2D Arrays</title>
<style>
#stage
{
position: relative;
}
.cell
{
position: absolute;
width: 30px;
height: 30px;
border: 1px solid black;
background-color: blue;
}
</style>
</head>
<body>
<div id="stage"></div>
<script>
//Get a reference to the stage
var stage = document.querySelector("#stage");
//The 2D array that defines the pattern
var pattern =
[
[1, 0, 1],
[0, 1, 0],
[1, 0, 1]
];
//The sixe of each cell
var SIZE = 30;
//The space between each cell
var SPACE = 10;
//Display the array
var ROWS = pattern.length;
var COLUMNS = pattern[0].length;
for(var row = 0; row < ROWS; row++)
{
for(var column = 0; column < COLUMNS; column++)
{
//Create a div HTML element called cell
var cell = document.createElement("div");
//Set its CSS class to "cell"
cell.setAttribute("class", "cell");
//Add the div HTML element to the stage
stage.appendChild(cell);
//Make it black if it's a "1"
if(pattern[row][column] === 1)
{
cell.style.backgroundColor = "red";
}
//Position the cell in the correct place
//with 10 pixels of space around it
cell.style.top = row * (SIZE + SPACE) + "px";
cell.style.left = column * (SIZE + SPACE) + "px";
}
}
</script>
</body>
Just to simplify the problem, else condition is added where blue color is set to the element.
To have a click listener, addEventListener method of node/element is used. There are other ways to attach click listeners too. addEventListener expects second argument to be a callback function which is a function expression to be called once click event takes place on the event. element.style.backgroundColor will get/set the backgroundColor to the element.
var stage = document.querySelector("#stage");
//The 2D array that defines the pattern
var pattern = [
[1, 0, 1],
[0, 1, 0],
[1, 0, 1]
];
//The sixe of each cell
var SIZE = 30;
//The space between each cell
var SPACE = 10;
//Display the array
var ROWS = pattern.length;
var COLUMNS = pattern[0].length;
for (var row = 0; row < ROWS; row++) {
for (var column = 0; column < COLUMNS; column++) {
//Create a div HTML element called cell
var cell = document.createElement("div");
//Set its CSS class to "cell"
cell.setAttribute("class", "cell");
//Add the div HTML element to the stage
stage.appendChild(cell);
//Make it black if it's a "1"
if (pattern[row][column] === 1) {
cell.style.backgroundColor = "red";
} else {
cell.style.backgroundColor = "blue";
}
//Position the cell in the correct place
//with 10 pixels of space around it
cell.style.top = row * (SIZE + SPACE) + "px";
cell.style.left = column * (SIZE + SPACE) + "px";
cell.addEventListener('click', function() {
this.style.backgroundColor = this.style.backgroundColor == 'red' ? 'blue' : 'red';
});
}
}
#stage {
position: relative;
}
.cell {
position: absolute;
width: 30px;
height: 30px;
border: 1px solid black;
}
<div id="stage"></div>
Related
I'm using JavaScript to create a stack of 16 boxes. I don't think I have the makeBox() function in the right place.
let makeBox = function() {
let box = document.createElement('div');
document.body.appendChild(box);
box.style.width = '28px';
box.style.height = '28px';
box.style.border = '1px solid black';
return box;
};
let makeGrid = function(numberOfRows) {
let y = 0;
let x = 0;
while (y < numberOfRows) {
x = 0;
while (x < numberOfRows) {
x = x + 1;
}
y = y + 1;
}
makeBox();
};
makeGrid(16);
I'm just getting one box in the browser. If anyone has any experience with this, if they could please help.
If you want to make a grid of boxes CSS Grid can help. It saves effort on creating nested loops. Just loop from 0 to the number passed in as the argument multiplied that same number, and create a box on each iteration. Then add it to the element that's been set up to control the grid.
I would also use a class for the box too.
function makeBox(x) {
const box = document.createElement('div');
box.classList.add('box');
box.textContent = x;
return box;
};
// The grid will be the argument (a number)
// multiplied by that number again, so you just need
// to loop from 0 to that number
function makeGrid(n) {
const grid = document.querySelector('#grid');
for (let x = 0; x < n * n; x++) {
grid.appendChild(makeBox(x));
}
};
makeGrid(16);
#grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 2px; }
.box { width: 28px; height: 28px; border: 1px solid black; text-align: center; }
<div id="grid"></div>
how does one get the cubes made in the grid to turn black with mouse running over? any help would be appreciated.
function Grid(z) {
for (y=0; y < z; y++) {
for (x=0; x < z; x++) {
size = 700 / z;
var div = document.querySelector('#container');
var block = document.createElement('div');
block.style.height = size + 'px';
block.style.width = size + 'px';
block.classList.add('cube');
div.appendChild(block);
}
}
}
function changeBlockColor() {
Grid(16);
var s = document.querySelector('.cube');
s.addEventListener('onmouseover', function(){
s.setAttribute('style', 'background: black');
});
}
changeBlockColor();
I would use CSS to achieve this effect.
.cube {
background-color:red;
}
.cube:hover {
background-color: black;
}
The following is for if you want the cubes to stay black after you finished hovering over them (and then hovered out).
First, your s = document.querySelector('.cube'); will make s only point to the first element with class cube. To solve that, make s an array of all elements of class cube, by using s = document.querySelectorAll('.cube'); instead.
Next, you need to loop through the array s and add the event listener to all its elements:
for(var i = 0; i < s.length; i++) {
s[i].addEventListener('mouseover', function(){
this.setAttribute('style', 'background: black');
});
}
Notice the use of this inside the handler. Inside handler code, this refers to the object that triggered the event (the cube moused over in your case).
No need to have hundred of mouseover Event Listener. Just one is enough
var
divContainer = document.getElementById('container'),
CubeClass = 'cube';
function Grid(z)
{
var sizePx = Math.floor(700 / z) + 'px';
for (let y = 0; y < z; y++)
{
for (let x = 0; x < z; x++)
{
let block = document.createElement('div');
block.style.height = sizePx;
block.style.width = sizePx;
block.className = CubeClass;
divContainer.appendChild(block);
};
};
}
divContainer.onmouseover = function(e)
{
if (!e.target.classList.contains( CubeClass )) return;
e.target.setAttribute('style', 'background: black');
}
divContainer.onmouseout = function(e) // if you need it...
{
if (!e.target.classList.contains( CubeClass )) return;
e.target.removeAttribute('style');
}
The event name you want is mouseover, not onmouseover
Also, querySelector will only find the first matching element, so you need to use querySelectorAll or getElementsByClassName instead
Finally, you need to iterate over all the elements you matched, which are returned in an Object, not an Array, so you need to use a for loop.
Solution
function createGrid(z) {
for (var y = 0; y < z; ++y) {
for (var x = 0; x < z; ++x) {
var size = 700 / z;
var div = document.getElementById('container');
var block = document.createElement('div');
block.style.height = size + 'px';
block.style.width = size + 'px';
block.classList.add('cube');
div.appendChild(block);
}
}
changeBlockColor()
}
function changeBlockColor(){
var cubes = document.querySelectorAll('.cube')
for (var i = 0; i < cubes.length; i++) {
cubes[i].addEventListener("mouseover", function(e) {
e.target.classList.add('active')
})
}
}
createGrid(16);
#container {
display: flex;
flex-wrap: wrap;
}
.cube {
background-color: red;
}
.active {
background-color: black;
}
<div id="container"></div>
Implementation Details
Naming is all preference in JS, but traditionally, names that start with a capital letter are for a class, so I renamed you function to createGrid
At the end of createGrid I call changeBlockColor, rather than call createGrid from inside changeBlockColor, logically it make more sense.
I created a CSS class called active to handle changing the color, as using setAttribute('style') was erasing the height and width styles you applied inside you Grid function.
Feedback
You use querySelector exclusively, you should get to know getElementById as well.
You use var a few times, but don't declare y, x, or size
You can define y and x in your for loop with for(var y=0 and for(var x=0
CSS Solution
Assuming you want the color to revert on mouseout, you can achieve this same effect with CSS using .cube:hover
.cube {
display: inline-block;
width: 50px;
height: 50px;
background-color: red;
}
.cube:hover {
background-color: black;
}
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
<div class="cube"></div>
Performance
A side note about using querySelector('#container') vs getElementById('container'). The first has to traverse the entire DOM looking for the selector, the latter can just go to the internal list of ids and return the reference.
https://jsperf.com/so53824751
Documentation
https://developer.mozilla.org/en-US/docs/Web/Events/mouseover
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
I want to create a field of cells. The field has a size of 10x10. When reaching a maximum count of cells in a row, it should start a new row.
Currently all my cell divs are placed below.
function initGame() {
var mapSize = 10; // create a field of 10x10
var cellsPerRow = 10; // 10 cells per row
for (var x = 0; x < mapSize; x++) {
for (var y = 0; y < mapSize; y++) {
createCell(x, y); // create a cell on index x (horizontal) and y (vertical)
}
}
}
function createCell(x, y) {
// store this cell position to a data class
var cellDiv = $("<div></div>"); // create the cell div
cellDiv.addClass("cell"); // add some css
$(document.body).append(cellDiv); // add the cell div to the parent
}
.cell{
height: 50px;
width: 50px;
border-style: solid;
border-width: 1px;
border-color: black;
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onLoad="initGame()">
</body>
Create wrapper for each 10 cell.
function initGame() {
var mapSize = 10; // create a field of 10x10
var cellsPerRow = 10; // 10 cells per row
for (var x = 0; x < mapSize; x++) {
$(document.body).append("<div>");
for (var y = 0; y < mapSize; y++) {
createCell(x, y); // create a cell on index x (horizontal) and y (vertical)
}
$(document.body).append("</div>");
}
}
function createCell(x, y) {
// store this cell position to a data class
var cellDiv = $("<div></div>"); // create the cell div
cellDiv.addClass("cell"); // add some css
$(document.body).append(cellDiv); // add the cell div to the parent
}
.cell{
height: 50px;
width: 50px;
border-style: solid;
border-width: 1px;
border-color: black;
background: red;
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onLoad="initGame()">
</body>
use display:inline-block; for .cells and to stop 10 each row add a <br> tag after 10 divs in row.
function initGame() {
var mapSize = 10; // create a field of 10x10
var cellsPerRow = 10; // 10 cells per row
for (var x = 0; x < mapSize; x++) {
for (var y = 0; y < mapSize; y++) {
createCell(x, y);
}
$(document.body).append("<br>");
}
}
function createCell(x, y) {
// store this cell position to a data class
var cellDiv = $("<div></div>"); // create the cell div
cellDiv.addClass("cell"); // add some css
$(document.body).append(cellDiv); // add the cell div to the parent
}
.cell {
height: 50px;
width: 50px;
border-style: solid;
border-width: 1px;
border-color: black;
background: red;
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onLoad="initGame()">
</body>
You can use absolute positioning and set for each of them css property. Here is the fiddle
function initGame() {
var mapSize = 10; // create a field of 10x10
var cellsPerRow = 10; // 10 cells per row
for (var x = 0; x < mapSize; x++) {
for (var y = 0; y < mapSize; y++) {
createCell(x, y); // create a cell on index x (horizontal) and y (vertical)
}
}
}
function createCell(x, y) {
// store this cell position to a data class
var cellDiv = $("<div></div>"); // create the cell div
cellDiv.addClass("cell"); // add some css
cellDiv.css({
left: Math.floor(x*50),
top: Math.floor(y*50)
});
$(document.body).append(cellDiv); // add the cell div to the parent
}
initGame()
.cell{
height: 50px;
width: 50px;
border-style: solid;
border-width: 1px;
border-color: black;
background: red;
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
You can create a row div that contains multiple cell div
function initGame() {
const rows = 10;
const columns = 10;
for(let r=0;r<rows;r++){
let row = createRow(r);
for(let c=0;c<columns;c++){
createCell(row, r, c);
}
}
}
function createRow(rowNumber){
let row = document.createElement('DIV');
row.className += ' row'
$(document.body).append(row);
return row;
}
function createCell(domRow, rowNumber, columnNumber){
let column = document.createElement('DIV');
column.className += ' cell';
$(domRow).append(column);
}
.cell{
height: 50px;
width: 50px;
border: 1px solid grey;
background: tomato;
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onLoad="initGame()">
</body>
I need to find a way to draw a 1000x1000 squares grid, each square is clickable and they must be independently color changeable. Like mines game. I can use HTML (pure or using Canvas or SVG), CSS and JavaScript for this.
I know how to create one grid with these characteristics with JavaScript and CSS, it does well with 10x10 squares, with 100x100 the squares will turn into tall rectangles and 1000x1000 it loads, but the "squares" are soo much compressed that borders meet each other and renders a full gray page.
I tried using HTML and JavaScript to draw SVG squares, the squares' size problem solves, but I don't know how to make they change color when clicked and when I set to load 1000x1000 squares it will freeze the browse and eventually crash the tab.
Is this feasible in any way?
EDIT
Sorry if I wasn't clear, but yes, I need scroll bars in that. They are no problem for me.
You can see the two trials I described here:
JavaScript and CSS
var lastClicked;
var grid = clickableGrid(100,100,function(el,row,col,i){
console.log("You clicked on element:",el);
console.log("You clicked on row:",row);
console.log("You clicked on col:",col);
console.log("You clicked on item #:",i);
el.className='clicked';
if (lastClicked) lastClicked.className='';
lastClicked = el;
});
document.body.appendChild(grid);
function clickableGrid( rows, cols, callback ){
var i=0;
var grid = document.createElement('table');
grid.className = 'grid';
for (var r=0;r<rows;++r){
var tr = grid.appendChild(document.createElement('tr'));
for (var c=0;c<cols;++c){
var cell = tr.appendChild(document.createElement('td'));
++i;
cell.addEventListener('click',(function(el,r,c,i){
return function(){
callback(el,r,c,i);
}
})(cell,r,c,i),false);
}
}
return grid;
}
.grid { margin:1em auto; border-collapse:collapse }
.grid td {
cursor:pointer;
width:30px; height:30px;
border:1px solid #ccc;
}
.grid td.clicked {
background-color:gray;
}
JavaScript and HTML
document.createSvg = function(tagName) {
var svgNS = "http://www.w3.org/2000/svg";
return this.createElementNS(svgNS, tagName);
};
var numberPerSide = 20;
var size = 10;
var pixelsPerSide = 400;
var grid = function(numberPerSide, size, pixelsPerSide, colors) {
var svg = document.createSvg("svg");
svg.setAttribute("width", pixelsPerSide);
svg.setAttribute("height", pixelsPerSide);
svg.setAttribute("viewBox", [0, 0, numberPerSide * size, numberPerSide * size].join(" "));
for(var i = 0; i < numberPerSide; i++) {
for(var j = 0; j < numberPerSide; j++) {
var color1 = colors[(i+j) % colors.length];
var color2 = colors[(i+j+1) % colors.length];
var g = document.createSvg("g");
g.setAttribute("transform", ["translate(", i*size, ",", j*size, ")"].join(""));
var number = numberPerSide * i + j;
var box = document.createSvg("rect");
box.setAttribute("width", size);
box.setAttribute("height", size);
box.setAttribute("fill", color1);
box.setAttribute("id", "b" + number);
g.appendChild(box);
svg.appendChild(g);
}
}
svg.addEventListener(
"click",
function(e){
var id = e.target.id;
if(id)
alert(id.substring(1));
},
false);
return svg;
};
var container = document.getElementById("container");
container.appendChild(grid(100, 10, 2000, ["gray", "white"]));
<div id="container">
</div>
I will be trying implementing the given answers and ASAP I'll accept or update this question. Thanks.
SOLUTION
Just to record, I managed to do it using canvas to draw the grid and the clicked squares and added an event listener to know where the user clicks.
Here is the code in JavaScript and HTML:
function getSquare(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: 1 + (evt.clientX - rect.left) - (evt.clientX - rect.left)%10,
y: 1 + (evt.clientY - rect.top) - (evt.clientY - rect.top)%10
};
}
function drawGrid(context) {
for (var x = 0.5; x < 10001; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 10000);
}
for (var y = 0.5; y < 10001; y += 10) {
context.moveTo(0, y);
context.lineTo(10000, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
function fillSquare(context, x, y){
context.fillStyle = "gray"
context.fillRect(x,y,9,9);
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
drawGrid(context);
canvas.addEventListener('click', function(evt) {
var mousePos = getSquare(canvas, evt);
fillSquare(context, mousePos.x, mousePos.y)
}, false);
<body>
<canvas id="myCanvas" width="10000" height="10000"></canvas>
</body>
Generating such a large grid with HTML is bound to be problematic.
Drawing the grid on a Canvas and using a mouse-picker technique to determine which cell was clicked would be much more efficient.
This would require 1 onclick and/or hover event instead of 1,000,000.
It also requires much less HTML code.
I wouldn't initialize all the squares right off, but instead as they are clicked -
(function() {
var divMain = document.getElementById('main'),
divMainPosition = divMain.getBoundingClientRect(),
squareSize = 4,
square = function(coord) {
var x = coord.clientX - divMainPosition.x + document.body.scrollLeft +
document.documentElement.scrollLeft,
y = coord.clientY - divMainPosition.y + document.body.scrollTop +
document.documentElement.scrollTop;
return {
x:Math.floor(x / squareSize),
y:Math.floor(y / squareSize)
}
}
divMain.addEventListener('click', function(evt) {
var sqr = document.createElement('div'),
coord = square(evt);
sqr.className = 'clickedSquare';
sqr.style.width = squareSize + 'px';
sqr.style.height = squareSize + 'px';
sqr.style.left = (coord.x * squareSize) + 'px';
sqr.style.top = (coord.y * squareSize) + 'px';
sqr.addEventListener('click', function(evt) {
console.log(this);
this.parentNode.removeChild(this);
evt.stopPropagation();
});
this.appendChild(sqr);
});
}());
#main {
width:4000px;
height:4000px;
background-color:#eeeeee;
position:relative;
}
.clickedSquare {
background-color:#dd8888;
position:absolute;
}
<div id="main">
</div>
Uses CSS positioning to determine which square was clicked on,
doesn't initialize a square until it's needed.
Granted I imagine this would start to have a negative impact to use r experience, but that would ultimately depend on their browser and machine.
Use the same format you noramlly use, but add this:
sqauareElement.height = 10 //height to use
squareElement.width = 10 //width to use
This will add quite a large scroll due to the size, but it's the only logical explanation I can come up with.
The canvas approach is fine, but event delegation makes it possible to do this with a table or <div> elements with a single listener:
const tbodyEl = document.querySelector("table tbody");
tbodyEl.addEventListener("click", event => {
const cell = event.target.closest("td");
if (!cell || !tbodyEl.contains(cell)) {
return;
}
const row = +cell.getAttribute("data-row");
const col = +cell.getAttribute("data-col");
console.log(row, col);
});
const rows = 100;
const cols = 100;
for (let i = 0; i < rows; i++) {
const rowEl = document.createElement("tr");
tbodyEl.appendChild(rowEl);
for (let j = 0; j < cols; j++) {
const cellEl = document.createElement("td");
rowEl.appendChild(cellEl);
cellEl.classList.add("cell");
cellEl.dataset.row = i;
cellEl.dataset.col = j;
}
}
.cell {
height: 4px;
width: 4px;
cursor: pointer;
border: 1px solid black;
}
table {
border-collapse: collapse;
}
<table><tbody></tbody></table>
I have a table created with javascript. I would like to change color of a cell to red, when you click on it. I know i should propably use onClick event. But I am not sure how to use it in this specific task.
var width = parseInt(prompt("Put width", "here"));
var height = parseInt(prompt("Put height", "here"));
function myFunction() {
var table = document.getElementById("chessboard");
for (var i = 0; i < height; i++) {
var row = table.insertRow(i);
for (var j = 0; j < width; j++) {
row.insertCell(j);
}
};
}
#chessboard {
border: 1px solid black;
border-collapse: collapse
}
td {
width: 40px;
height: 40px
}
tr:nth-child(odd) td:nth-child(even) {
background: black
}
tr:nth-child(even) td:nth-child(odd) {
background: black
}
<body onload="myFunction()">
<div>
<table id="chessboard"></table>
</div>
</body>
You can add an onclick listener to each cell which add a selected class to it. You also just need to track what was selected previously so you can remove the selected class from it:
var width = parseInt(prompt("Put width", "here"));
var height = parseInt(prompt("Put height", "here"));
// tracks the selected cell
var selectedCell = null;
// handles the clicks
function selectCell() {
// remove from previous if there is one
if (selectedCell != null) {
selectedCell.classList.remove('selected');
}
// mark cell as selected
selectedCell = this;
this.classList.add('selected');
}
function myFunction() {
var table = document.getElementById("chessboard");
for (var i = 0; i < height; i++) {
var row = table.insertRow(i);
for (var j = 0; j < width; j++) {
var cell = row.insertCell(j);
// bind the selectCell function to this cell
cell.onclick = selectCell.bind(cell);
}
};
}
#chessboard {
border: 1px solid black;
border-collapse: collapse
}
td {
width: 40px;
height: 40px
}
tr:nth-child(odd) td:nth-child(even) {
background: black;
}
tr:nth-child(even) td:nth-child(odd) {
background: black;
}
td.selected {
background: red !important;
}
<body onload="myFunction()">
<div>
<table id="chessboard"></table>
</div>
</body>
Add a click event to all cells.
First, you'll need to store the original color somewhere so that you can change it back to normal when clicked again.
To do that, you could make use of data-* attribute. Now, to assign the correct white and black colors to corresponding cells' data-color attributes, you could use this JavaScript equivalent,
row.children[j].setAttribute('data-color', ((i % 2 != 0 && j % 2 == 0) || (i % 2 == 0 && j % 2 != 0)) ? 'black' : 'white')
for this part of CSS,
tr:nth-child(odd) td:nth-child(even) {
background: black
}
tr:nth-child(even) td:nth-child(odd) {
background: black
}
Now, since the original colors are stored, you could simply extract the corresponding colors stored in the data-color attribute and change it back to normal if its backgroundColor is red.
var width = parseInt(prompt("Put width", "here"));
var height = parseInt(prompt("Put height", "here"));
function myFunction() {
var table = document.getElementById("chessboard");
for (var i = 0; i < height; i++) {
var row = table.insertRow(i);
for (var j = 0; j < width; j++) {
row.insertCell(j);
row.children[j].setAttribute('data-color', ((i % 2 != 0 && j % 2 == 0) || (i % 2 == 0 && j % 2 != 0)) ? 'black' : 'white')
row.children[j].addEventListener('click', function() {
this.style.backgroundColor = (this.style.backgroundColor == 'red') ? this.getAttribute('data-color') : this.style.backgroundColor = 'red';
});
}
};
}
#chessboard {
border: 1px solid black;
border-collapse: collapse
}
td {
width: 40px;
height: 40px
}
tr:nth-child(odd) td:nth-child(even) {
background: black
}
tr:nth-child(even) td:nth-child(odd) {
background: black
}
<body onload="myFunction()">
<div>
<table id="chessboard"></table>
</div>
</body>