Reputation: 3
I'm creating an Etch-a-Sketch for The Odin Project using HTML, CSS, & JavaScript. I'm having some difficulties with the JavaScript for the "shader pen" I've implemented.
Note: The "pens" described in this question are not to be confused with code pens.
There are two pens: a black pen that turns a cell black and the shader pen which, on the first pass, turns the cell black but changes the opacity to 0.1 so that it's almost transparent. This creates the illusion of slight darkening since the background behind the cell is the same as the original color of the cell. After each subsequent pass with the shader pen, the opacity increases by 0.1.
The shader pen is the default pen. It works fine at first, but when I click on the black pen and then go back to the shader pen, the shader pen increases the opacity once and stops. It also overrides the black cells.
Here's how I set up the pens:
makeGrid(16); // generate grid
pen("shader"); // default pen: shader
document.querySelector('#black').addEventListener("click", e => pen("black"));
document.querySelector('#shader').addEventListener("click", e => pen("shader"));
function pen(selected) {
let cells = document.querySelectorAll('div.cell');
cells.forEach(cell => {
cell.addEventListener('mouseover', function(e) {
if (selected == "black") {
// turns cell black
cell.classList.remove('shade');
cell.style.backgroundColor = '#101010';
cell.style.opacity = '1';
console.log('Make cell black')
} else if (selected == "shader") {
let opacity = cell.style.opacity;
if (cell.classList.contains("shade")) {
// increases opacity by 0.1
cell.style.opacity = (Number(opacity) + 0.1);
console.log('If there is shade, increase opacity.')
} else {
// turns cell to 0.1 opacity
cell.classList.add('shade');
cell.setAttribute('style', 'opacity:0.1');
cell.style.backgroundColor = '#101010';
console.log('Else, add shade class.')
}
}
})
});
}
I ran this code with a console.log
statement in each conditional block and it looks like the black pen is still running when I go back to using the shader pen. My best guess is that this is what's causing the problem, but I'm not sure how to fix it.
I've created a CodePen with the rest of the code so you can see it in action. As previously mentioned, the default pen is the shader. It works as intended at the beginning, but it just stops after the black pen is selected.
Any help or guidance would be greatly appreciated.
Upvotes: 0
Views: 784
Reputation: 924
I changed a lot, so I can't describe all of the changes, but here's a link to my forked codepen:
https://codepen.io/scoutskylar/pen/ExxWPMb
The biggest change was removing the black
and shade
classes and just changing the opacity instead. (All of the cells now start with 0 opacity.) I also added erasers just for fun. :)
The other big thing was making the event handlers once instead of adding a handler every time the pen changed.
Here's the code:
var currentPen = "shader"; // default pen
makeGrid(16); // generate grid
document.querySelector("#black").addEventListener("click", e => {
currentPen = "black";
});
document.querySelector("#shader").addEventListener("click", e => {
currentPen = "shader";
});
document.querySelector("#eraser").addEventListener("click", e => {
currentPen = "eraser";
});
document.querySelector("#deshader").addEventListener("click", e => {
currentPen = "deshader";
});
// generate grid
function makeGrid(dimension) {
const canvas = document.querySelector("#canvas");
const canvasWidth = document.getElementById("canvas").offsetWidth;
const cellWidth = canvasWidth / dimension;
for (let x = 1; x <= dimension * dimension; x++) {
const makeCell = document.createElement("div");
makeCell.classList.add("cell");
canvas.appendChild(makeCell);
}
canvas.style.gridTemplateRows = `repeat(${dimension}, ${cellWidth}px [row-start]`;
canvas.style.gridTemplateColumns = `repeat(${dimension}, ${cellWidth}px [column-start]`;
let cells = document.querySelectorAll("div.cell");
cells.forEach(cell => {
cell.addEventListener("mouseover", function(e) {
if (currentPen == "black") {
// turns cell black
cell.style.opacity = "1";
// console.log("Make cell black");
} else if (currentPen == "shader") {
// turns cell 0.1 opacity darker
let opacity = Number(cell.style.opacity);
cell.style.opacity = opacity >= 1 ? "1" : opacity + 0.1 + "";
// console.log("Increase opacity");
} else if (currentPen == "eraser") {
// resets color
cell.style.opacity = "0";
// console.log("Reset opacity");
} else if (currentPen == "deshader") {
// turns cell 0.1 opacity lighter
let opacity = Number(cell.style.opacity);
cell.style.opacity = opacity <= 0 ? "0" : opacity - 0.1 + "";
// console.log("Decrease opacity");
}
});
});
}
body {
margin: 0;
background-color: #514c53;
color: #fff;
font-size: 18pt;
}
/* DIV STYLING */
#container {
margin: 20px auto;
max-width: 400px;
display: flex;
flex-wrap: wrap;
}
.full {
width: 100%;
}
.left {
width: 75%;
margin-top: 15px;
}
.right {
width: 25%;
text-align: right;
}
#canvas {
display: grid;
flex-wrap: wrap;
grid-template-rows: repeat(16, 30px [row-start]);
grid-template-columns: repeat(16, 30px [col-start]);
background: #e8dfd6;
}
.cell {
/* background: #e8dfd6; */
background-color: #101010;
opacity: 0;
}
/* CELL SHADING */
/* .black {
background-color: #101010;
} */
/* .shade {
background-color: #101010;
width: 100%;
height: 100%;
} */
/* ELEMENT STYLING */
button {
font-weight: 700;
margin: 10px 0;
background-color: #b9967d;
color: #fff;
padding: 0px 15px;
text-shadow: 1px 1px #2e2e2e;
box-shadow: 3px 3px #2e2e2e;
border-radius: 5px;
border: 0px;
text-transform: uppercase;
height: 30px;
outline: none;
transition: 0.3s;
cursor: pointer;
}
button:hover {
background-color: #aa8062;
box-shadow: 0px -3px #2e2e2e;
}
button:active {
background-color: #997963;
}
.circle {
display: inline-block;
height: 15px;
width: 15px;
border: 1px solid #fff;
border-radius: 50%;
margin: 1px 5px 0 0;
cursor: pointer;
}
.meaning {
font-size: 0.542em;
text-transform: uppercase;
padding-top: 2px;
margin-right: 10px;
cursor: pointer;
}
.penbutton {
display: inline-block;
}
#black .circle {
background-color: #101010;
}
#shader .circle {
background: linear-gradient(#807b76, #dfd8d0);
}
#eraser .circle {
background-color: #e8dfd6;
}
#deshader .circle {
background: linear-gradient(#9e9a96, #e8dfd6);
}
<head>
<title>Etch-a-Sketch</title>
</head>
<body>
<div id="container">
<div class="left" style="margin-bottom:10px">
<span id="black" class="penbutton">
<span class="circle"></span>
<span class="meaning">Black</span>
</span>
<span id="shader" class="penbutton">
<span class="circle"></span>
<span class="meaning">Shader</span>
</span>
<span id="eraser" class="penbutton">
<span class="circle"></span>
<span class="meaning">Super Eraser</span>
</span>
<span id="deshader" class="penbutton">
<span class="circle"></span>
<span class="meaning">Light Eraser</span>
</span>
</div>
<div id="canvas" class="full">
<!--grid generates here-->
</div>
</div>
<!--container-->
</body>
<script src="assets/js/eas.js"></script>
Upvotes: 1
Reputation:
There's so many problems with your code, The main one is when you change the pen type (when you call the pen()
function) you add a new mouseover
event listener.
Let's walk through it
Your code starts by calling pen('shader')
making the shader
the default pen type
pen("shader");
let's look at the pen()
definition
function pen(selected) {
let cells = document.querySelectorAll('div.cell');
cells.forEach(cell => {
cell.addEventListener('mouseover', function(e){
...
})
});
}
What happens is the event listener takes the string shader
and closes on it (This is called a closure) for that instance now whenever you hover over a cell the pen type will be shader
Now when you select the black pen you call the function pen()
again adding a new set of event listeners.
This does not replace the old event listener with the new one, it stacks them like calling two functions one after the other.
Now when you hover over a cell the first event listener with the pen type shader
will fire and then the second one with the black pen will fire after it, you can see this in the console you see two messages being printed from both if statement conditions.
Now when you pick the shader
again you add a new set of event listeners with the shader pen type, what happens is the very first event listener will fire setting opacity to 0.1 then comes the black one removing the shade class then comes the third listener again checking if the cell has the class shade if(cell.classList.contains("shade"))
which is false then falls down to the last else statement.
There's a ton of ways to fix this, but seeing that you're a beginner i will give you simple solutions
First make the pen type a global variable and on each button click you change it
Second add only one event listener to all cels at the start of your code and have them check the global variable
Demo
let penType = 'shader';
makeGrid(4); // generate grid
document.querySelector('#black').addEventListener("click", e => penType = "black");
document.querySelector('#shader').addEventListener("click", e => penType = "shader");
// generate grid
function makeGrid(dimension) {
const canvas = document.querySelector('#canvas');
const canvasWidth = document.getElementById("canvas").offsetWidth;
const cellWidth = canvasWidth / dimension;
for (let x = 1; x <= dimension * dimension; x++) {
const makeCell = document.createElement('div');
makeCell.classList.add('cell');
canvas.appendChild(makeCell);
};
canvas.style.gridTemplateRows = `repeat(${dimension}, ${cellWidth}px [row-start]`;
canvas.style.gridTemplateColumns = `repeat(${dimension}, ${cellWidth}px [column-start]`;
// after all cells have been generated add the event listener
// this is not the most optimize version of this but it gets the job done
let cells = document.querySelectorAll('div.cell');
cells.forEach(cell => {
cell.addEventListener('mouseover', function(e) {
console.log(penType)
if (penType == "black") { // turns cell black
cell.classList.remove('shade');
cell.style.backgroundColor = '#101010';
cell.style.opacity = '1';
console.log('Make cell black')
} else if (penType == "shader") { // turns cell 0.1
let opacity = cell.style.opacity;
if (cell.classList.contains("shade")) {
cell.style.opacity = (Number(opacity) + 0.1);
console.log('If there is shade, increase opacity.')
} else {
cell.classList.add('shade');
cell.setAttribute('style', 'opacity:0.1');
cell.style.backgroundColor = '#101010';
console.log('Else, add shade class.')
}
}
})
});
}
body {
margin: 0;
background-color: #514c53;
color: #fff;
font-size: 18pt;
}
/* DIV STYLING */
#container {
margin: 20px auto;
max-width: 400px;
display: flex;
flex-wrap: wrap;
}
.full {
width: 100%;
}
.left {
width: 75%;
margin-top: 15px;
}
.right {
width: 25%;
text-align: right;
}
#canvas {
display: grid;
flex-wrap: wrap;
grid-template-rows: repeat(16, 30px [row-start]);
grid-template-columns: repeat(16, 30px [col-start]);
background: #e8dfd6;
}
.cell {
background: #e8dfd6;
}
/* CELL SHADING */
.black {
background-color: #101010;
}
.shade {
background-color: #101010;
width: 100%;
height: 100%;
}
/* ELEMENT STYLING */
button {
font-weight: 700;
margin: 10px 0;
background-color: #b9967d;
color: #fff;
padding: 0px 15px;
text-shadow: 1px 1px #2e2e2e;
box-shadow: 3px 3px #2e2e2e;
border-radius: 5px;
border: 0px;
text-transform: uppercase;
height: 30px;
outline: none;
transition: 0.3s;
}
button:hover {
cursor: pointer;
background-color: #aa8062;
box-shadow: 0px -3px #2e2e2e;
}
button:active {
background-color: #997963;
}
.circle {
float: left;
height: 15px;
width: 15px;
border: 1px solid #fff;
border-radius: 50%;
margin: 1px 5px 0 0;
}
.circle:hover {
cursor: pointer;
/*transition: 0.3s;
border: 2px solid #fff;*/
}
.meaning {
float: left;
font-size: .542em;
text-transform: uppercase;
padding-top: 2px;
margin-right: 10px;
}
#black {
background-color: #101010;
}
#shader {
background-image: linear-gradient(#807b76, #dfd8d0);
}
<div id="container">
<div class="left" style="margin-bottom:10px">
<div class="circle" id="black"></div>
<div class="meaning">Black</div>
<div class="circle" id="shader"></div>
<div class="meaning">Shader</div>
</div>
<div id="canvas" class="full">
<!--grid generates here-->
</div>
</div>
<!--container-->
One tiny problem with your logic, when go to the black pen you don't have to remove the shade class because then when you come back to the shade pen and you hover over a black cell it sets opacity back to 0.1
Upvotes: 2