I'm working on a drum machine and I am not sure how to approach making the squares clickable. How can I alter the color of a specific square to crimson upon being clicked?
const drums = ["Crash", "CHiHat", "OHiHat", "Tom3", "Tom2", "Tom1", "Snare", "Kick"];
for (let i = 0; i < 8; i++) {
for (let v = 0; v < 16; v++) {
var block = document.getElementById('canvas');
var context = block.getContext('2d');
context.strokeRect(100 + (55 * v), (55 * i), 50, 50);
context.fillStyle = 'crimson';
context.fillRect(100 + (55 * v), (55 * i), 50, 50);
}
}
<canvas id="canvas" width="1920" height="1080"></canvas>
I tried addEventListener, but that returned an error.
A canvas is just a grid of pixels. You can't assign a click handler to a canvas shape, because that shape is just some pixels. If you aren't restricted to using canvas, try using regular elements, like this:
var drumMachine = document.getElementById("drumMachine");
var nRows = 8;
var nCols = 16;
for (let i = 0; i < nRows; i++) {
var row = document.createElement("tr");
for (let j = 0; j < nCols; j++) {
let cell = document.createElement("td");
cell.className = "clickable";
cell.addEventListener("click", function () {
this.classList.toggle("activated");
});
row.appendChild(cell);
}
drumMachine.appendChild(row);
}
#drumMachine {
margin-left: 100px;
}
.clickable {
width: 50px;
height: 50px;
background-color: black;
cursor: pointer;
margin-right: 5px;
margin-bottom: 5px;
display: inline-block;
}
.clickable.activated {
background-color: crimson;
}
<table id="drumMachine"></table>
This does a few things.
First, it defines the grid dimensions and then creates the elements in a two-dimensional loop.
It then adds an click event listener to each element (here is where HTML elements shine against HTML canvas!)
After which, the elements are assembled into a regular table setup. CSS is used to mimic your canvas setup.
If you have any questions don't hesitate to ask in the comments.
Related
I am currently doing the odin project Etch-A-sketch challenge.
I currently have it so that using JavaScript I create 16 row divs, with 16 grid squares inside each row div. So it is a 16 x 16 grid.
How do I make it so that when I put more grid squares into the container the whole grid stays the same size and the gird squares get smaller or larger in the container without it spilling over the container walls with flexbox?
Here is my codepen: https://codepen.io/Alex-Swan/pen/jOyMwzm
Javascript:
/* This function creates 16x16 grid or what ever input the user has given, the event listner at the
bottom of the function makes it that when a user hovers over a grid square it turns black.
*/
function fullGrid(e) {
for (let i = 0; i < e; i++) {
row = document.createElement("DIV");
container.appendChild(row);
row.className = "row";
for (let i = 0; i < e; i++) {
square = document.createElement("DIV");
row.appendChild(square);
square.className = "gridSquare";
}
}
for (let i = 0; i < gridSquare.length; i++) {
gridSquare[i].addEventListener("mouseover", () => {
gridSquare[i].className += " squareBlack";
});
}
}
CSS:
.container {
border: black solid 1px;
width: 400px;
height: 400px;
}
.row {
display: flex;
height: 6%;
}
.gridSquare {
border: #444242 solid 1px;
margin: auto;
height: 100%;
width: 33.3%;
}
.squareBlack {
background-color: black;
}
Fixed it!
In JavaScript I calculated to height by dividing the GridSquare amount by the 400px size of the container. Also i changed the CSS box-sizing to border box.
Javascript:
function fullGrid(e) {
for (let i = 0; i < e; i++) {
row = document.createElement("DIV");
container.appendChild(row);
row.className = "row";
for (let i = 0; i < e; i++) {
square = document.createElement("DIV");
row.appendChild(square);
square.className = "gridSquare";
square.style.width = "100%"; // <----- HERE
let height = 400 / parseInt(squareAmount); // <----- HERE
square.style.height = `${height}px`; // <----- HERE
}
}
for (let i = 0; i < gridSquare.length; i++) {
gridSquare[i].addEventListener("mouseover", () => {
gridSquare[i].className += " squareBlack";
});
}
}
I have a 16x16 grid of small squares. I have added a permanent "hover" effect to make the very first box turn red when I put my mouse over it. However, I want to add the same effect to all of the boxes on the page. I can't figure out how to do it - I have tried to add an event listener to the whole page and used target.nodeName and target.NodeValue, but to no avail. I have included the working version where the fix box turns red on mouseover.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
The immediate problem you are having is that this is only querying, and subsequently adding an event listener to, one element.
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
In the above portion of your code, querySelector only returns the first matching element. You may be looking for querySelectorAll here which returns a NodeList of matching elements.
You have two options (perhaps others if you want to restructure your code further). The naive approach is to, in fact, query for all of the cells and add event listeners to each of them.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(smallBox => {
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
Another option is to use event delegation as you identified. Here is how you can leverage that. Note: this approach is a bit tricker for an aggressive event like "mouseover" as you may get false positive targets (like the outer container for example).
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
bigContainer.addEventListener('mouseover', e => {
var target = e.target
if (target !== bigContainer) {
target.classList.add('permahover')
}
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You need to use a delegation event, because all the small boxes don't exist on the page when the page is loaded (You can figure out in the inspector element that only your first box has the event listener).
So you listen the whole container (because it is always on the page on load)
bigContainer.addEventListener('mouseover', () => {
// Code for checking if we hovered a small div & if yes applying the style
});
...and then do a comparaison with the event.target (which will be the small div hovered)
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
bigContainer.addEventListener('mouseover', () => {
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You can use forEach method to loop through all boxes and add eventListener on each one.
If all of them have .smallBox class you can do it like this:
const smallBoxes = document.querySelectorAll('.smallBox');
smallBoxes.forEach(box => box.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
}))
I hope it helped you!
let smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(el => {
el.addEventListener('mouseover', e => e.target.classList.add('permahover'));
});
you should set the eventlistener to your DOM and ask if the trigger element are one of your elements which are that specific class. So you can handle every element with that class.
var n = 16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for (var i = 1; i < n; i++) {
bigContainer.innerHTML += '<div class="row">';
for (j = 0; j < n; j++) {
bigContainer.innerHTML += '<div class="smallBox">';
}
}
document.addEventListener('mouseover', function(e) {
if (e.target && e.target.className == 'smallBox') {
var target = e.target;
target.classList.add('permahover');
}
});
Working js fiddle: https://jsfiddle.net/nwukf205/
hope i could help you :)
if you got questions just ask
Have you tried the :hover selector? Not sure if you want specify any dynamic actions here, but it's easy to do basic stuff.
https://www.w3schools.com/cssref/sel_hover.asp
a:hover {
background-color: yellow;
}
I haven't tried your example myself but something similar to this has been answered here:
Hover on element and highlight all elements with the same class
I am working on an Etch-A-Scetch project. I created grid which contains a certain amount of squares of the same size (the user is able to type in the amount of squares which should be displayed). To create the squares I used CSS grid and a Javascript for loop. Now I want to add event listeners, which change the background of each Square when moving over it. Unfortunately, it always shows errors when I try to add some. The current code doesn't show an error, it just doesn't do anything.
The method createSquares() should just create and add the amount of squares to the DOM. The user types in an amount, for example 10, and the displayed squares are 10 in x-direction and 10 in y-direction --> makes 100 squares in total. After that I want to add an event listener, which changes the background color of the square the user hovers over (the background color should stay changed). I am thankful for any help, because I'm really clueless :D
let squareDiv = document.querySelector('.squareDiv');
let squares = document.getElementById('#squares')
let squareAmount = 10;
function blackColor() {
this.style.backgroundColor = '#000';
this.style.border = '0px';
}
function createSquares() {
for (i = 0; i < squareAmount * squareAmount; i++) {
squares = document.createElement('div');
squares.setAttribute("id", "squares");
// squares.setAttribute("onmouseover", "addEventListener")
squares.style.display = 'grid';
squareDiv.style.setProperty('--columns-amount', squareAmount);
squareDiv.style.setProperty('--rows-amount', squareAmount);
squareDiv.appendChild(squares);
}
}
createSquares();
if (squares) {
squares.addEventListener('mouseover', _ => {
squares.style.backgroundColor = blackColor;
});
}
<div class="squareDiv"></div>
<div id="squares"></div>
You likely need something like this
I fixed the script, now fix the CSS
let container = document.getElementById("container")
let squareAmount = 5;
function getRandom() {
return '#'+Math.floor(Math.random()*16777215).toString(16);
}
function colorIt(sq) {
sq.style.backgroundColor = document.getElementById("random").checked? getRandom() : '#000';
sq.style.border = '0px';
}
function createSquares() {
let grid = document.createElement('div');
grid.setAttribute("id","squares")
grid.classList.add("grid");
for (i = 0; i < squareAmount * squareAmount; i++) {
square = document.createElement('div');
square.classList.add("square");
grid.appendChild(square);
}
container.innerHTML="";
container.appendChild(grid)
}
createSquares();
container.addEventListener('mouseover',
e => {
const target = e.target;
if (target.matches(".square")) colorIt(target)
}
);
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
grid-auto-rows: 1fr;
}
.grid::before {
content: '';
width: 0;
padding-bottom: 100%;
grid-row: 1 / 1;
grid-column: 1 / 1;
}
.grid > *:first-child {
grid-row: 1 / 1;
grid-column: 1 / 1;
}
/* Just to make the grid visible */
.grid > * {
background: rgba(0,0,0,0.1);
border: 1px white solid;
}
<label><input type="checkbox" id="random" />Random</label>
<div id="container"></div>
You have already created the element in DOM, please remove this.
while creating the element using function createSquares assign class instead of ID. Since, you should have only element with one ID.
Move the addEventListener inside the function after you have created the element.
When creating similar html elements with same properties it is better to group them together with class and not id. This is good because it becomes simple to loop these html elements with forEach or other looping methods you may prefer.
let squareDiv = document.querySelector('.squareDiv');
let squares = document.getElementById('#squares')
let squareAmount = 10;
function blackColor() {
this.style.backgroundColor = '#000';
this.style.border = '0px';
}
function createSquares() {
for (i = 0; i < squareAmount * squareAmount; i++) {
squares = document.createElement('div');
squares.setAttribute("class", "squares");
squares.setAttribute("style", "width: 100px; height: 100px; background: #090; margin-bottom: .3rem;");
// squares.setAttribute("onmouseover", "addEventListener")
squares.style.display = 'grid';
squareDiv.style.setProperty('--columns-amount', squareAmount);
squareDiv.style.setProperty('--rows-amount', squareAmount);
squareDiv.appendChild(squares);
}
}
createSquares();
if (squares) {
squares.addEventListener('mouseover', _ => {
squares.style.backgroundColor = blackColor;
});
}
<div class="squareDiv"></div>
<div id="squares"></div>
This question already has answers here:
Image inside div has extra space below the image
(10 answers)
Why does my image have space underneath?
(3 answers)
Align inline-block DIVs to top of container element
(5 answers)
Closed 3 years ago.
I am trying to create a chess board.I am using nested loops to do that. The problem is that there is a gap between two horizontal rows of the block. Below I have create a snippet for 3x3 board.
const board = document.querySelector('#board');
const colors = ["black","gray"]
function start(){
for(let i = 0;i<3;i++){
let br = document.createElement('br')
for(let j = 0;j<3;j++){
let block = document.createElement('div');
block.classList.add('block');
let id = (i * 8) + j
block.id = id;
block.style.backgroundColor = colors[(id+i) % 2]
board.appendChild(block)
}
board.appendChild(br)
}
}
start()
.block{
height: 70px;
width: 70px;
display:inline-block;
}
<div id="board"></div>
I already head about solution using float:left instead of display:inline-block. How could I remove the gap?
I would also like to see if there is better code for creating chessboard?
The gap is there because the <br>. #board { font-size: 0; } will remove it.
You seem to be trying to create a table with divs. It's perfectly fine, apart from the fact that you'll need to manage spaces between the blocks with margins, if you ever need them.
You could create a table and use border-collapse: collapse
const board = document.querySelector('#board');
const colors = ["black", "gray"]
function start() {
for (let i = 0; i < 3; i++) {
let tr = document.createElement('tr')
for (let j = 0; j < 3; j++) {
let block = document.createElement('td');
block.classList.add('block');
let id = (i * 8) + j
block.id = id;
block.style.backgroundColor = colors[(id + i) % 2]
tr.appendChild(block)
}
board.appendChild(tr)
}
}
start()
.block {
height: 70px;
width: 70px;
}
#board {
border-collapse: collapse;
}
<table id="board"></table>
try to use flex
function start(n){
let s='';
for(let i = 0;i<n;i++){
s+='<div class="row">'
for(let j = 0;j<n;j++){
s+=`<div class="block ${(i+j)%2?'white':''}"></div>`
}
s+='</div>'
}
board.innerHTML=s;
}
start(3)
.block{ height: 70px; width: 70px; background: black }
.white { background: gray }
.row { display: flex }
<input type="range" min="1" max="8" oninput="start(this.value)" value=3 /><br>
<div id="board"></div>
I'd recommend using canvas. You can fill the screen with rectangles, each with sidelength width ,and starting position (i*width,j*width). Each rect can be filled with a colour, by specifying the fill colour before drawing. Look into a good HTML Canvas tutorial.
I'm trying to practice my scripting by making a Battleship game. As seen here.
I'm currently trying to make the board 2D. I was able to make a for-loop in order to make the board, however, due to testing purposes, I'm just trying to make the board, upon clicking a square, it turns red... But, the bottom square always lights up. I tried to debug it by making the c value null, but then it just stops working. I know it's not saving the c value properly, but I'm wondering how to fix this.
Do I have to make 100 squares by my self or can I actually have the script do it?
maincansize = 400;
document.getElementById("Main-Canvas").style.height = maincansize;
document.getElementById("Main-Canvas").style.width = maincansize;
document.getElementById("Main-Canvas").style.position = "relative";
var ize = maincansize * .1;
for (var a = 0; a < 10; a++) {
for (var b = 0; b < 10; b++) {
var c = document.createElement("div");
var d = c;
c.onclick = function() {
myFunction()
};
function myFunction() {
console.log("A square was clicked..." + c.style.top); d.style.backgroundColor = "red";
}
c.style.height = ize;
c.style.width = ize;
c.style.left = b * ize;
c.style.top = a * ize;
c.style.borderColor = "green";
c.style.borderStyle = "outset";
c.style.position = "absolute";
console.log(ize);
document.getElementById('Main-Canvas').appendChild(c);
} //document.getElementById('Main-Canvas').innerHTML+="<br>";
}
#Main-Canvas {
background-color: #DDDDDD;
}
<div>
<div id="header"></div>
<script src="HeaderScript.js"></script>
</div>
<div id="Main-Canvas" style="height:400;width:400;">
</div>
Here's your code with some fixes:
adding 'px' to style assignment
passing the clicked element to myFunction
var maincansize = 400;
document.getElementById("Main-Canvas").style.height = maincansize;
document.getElementById("Main-Canvas").style.width = maincansize;
document.getElementById("Main-Canvas").style.position = "relative";
var ize = maincansize * .1;
for (var a = 0; a < 10; a++) {
for (var b = 0; b < 10; b++) {
var c = document.createElement("div");
c.onclick = function(ev) {
myFunction(ev.currentTarget);
};
function myFunction(el) {
console.log("A square was clicked...");
el.style.backgroundColor = "red";
}
c.style.height = ize+'px';
c.style.width = ize+'px';
c.style.left = (b * ize)+'px';
c.style.top = (a * ize)+'px';
c.style.borderColor = "green";
c.style.borderStyle = "outset";
c.style.position = "absolute";
document.getElementById('Main-Canvas').appendChild(c);
}
}
#Main-Canvas {
background-color: #DDDDDD;
}
<div id="Main-Canvas" style="height:400;width:400;">
</div>
Here's a solution with major revamps. Since you're using a set width for the container element of your board cells you can float the cells and they will wrap to the next line. Absolute positioning tends to be a bit of a bugger. If you want 10 items per row it's as easy as:
<container width> / <items per row> = <width>
Using document fragments is faster than appending each individual element one at a time to the actual DOM. Instead you append the elements to a document fragment that isn't a part of the DOM until you append it. This way you're doing a single insert for all the cells instead of 100.
I moved some of the styling to CSS, but could easily be moved back to JS if you really need to.
function onCellClick() {
this.style.backgroundColor = 'red';
console.log( 'selected' );
}
var main = document.getElementById( 'board' ),
frag = document.createDocumentFragment(),
i = 0,
len = 100;
for ( ; i < len; i++ ) {
div = document.createElement( 'div' );
div.addEventListener( 'click', onCellClick, false );
frag.appendChild( div );
}
main.appendChild( frag );
#board {
width: 500px;
height: 500px;
}
#board div {
float: left;
box-sizing: border-box;
width: 50px;
height: 50px;
border: 1px solid #ccc;
}
<div id="board"></div>