adamgy
adamgy

Reputation: 5663

Why is my mousedown event triggered on a mouseup event?

I've been working on the etch-a-sketch project of the Odin project, and I came across some weird behavior when implementing the mousedown and mouseup event listeners.

I've created a 50x50 grid of divs in a container div. The container div listens on mousedown events, upon which it calls the startDrawing function, filling the boxes that the user hovers over. It also listens on mousedown events, so that when the mouse is released, the stopDrawing function is called and the filling of boxes stops.

All of this works pretty much fine, but sometimes when I start drawing a line with the mouse left button held down, the box div becomes "grabbed". After this, while dragging the mouse with the left button still down, the boxes are not filled when hovering over them. Then when I release the mouse it starts drawing. It's as if the behavior is toggled after the accidental "grabbing", but on the next mousedown it starts acting normally again.

This is probably harder to explain than to see it for yourself, so below is my code as well as a link to a corresponding codepen.

I've tried googling to find out how I can remove this "grabbing" behavior, but I haven't really found anything, probably because I don't even know what keywords to search for.

Can somebody explain what's happening and provide some info on how I can fix this?

Etch-a-Sketch Codepen

const GRID_SIZE = 50;
for(let i = 0; i < GRID_SIZE * GRID_SIZE; i++){
    const container = document.getElementById('container');
    let div = document.createElement('div');
    div.classList.add('box');
    container.appendChild(div);
}

function fillBox(e){
    this.classList.add('filled');
}

function clearGrid(){
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.classList.remove('filled'));
}

function startDrawing(){
    // console.log("start drawing");
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.addEventListener('mouseover', fillBox));
}

function stopDrawing(){
    // console.log("stop drawing");
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.removeEventListener('mouseover', fillBox));
}

const container = document.querySelector('#container');
container.addEventListener('mousedown', startDrawing);
container.addEventListener('mouseup', stopDrawing);

const button = document.querySelector('#clear-grid-btn');
button.onclick = clearGrid;
#container{
    width: 500px;
    display: grid;
    grid-template-columns: repeat(50, 10px);
    grid-template-rows: repeat(50, 10px);
    border: solid;
    border-color: black;
    margin:auto;
}

.box{
    width: 10px;
    height: 10px;
}

.box:hover{
    background-color: blue;
}

.filled{
    background-color: blue;
}

#clear-grid-btn{
    display:block;
    margin:auto;
    margin-top: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="etch-a-sketch.css">
</head>
<body>
    <div id="container"></div>
    <button id="clear-grid-btn">Clear grid</button>
</body>
<script src="etch-a-sketch.js"></script>
</html>

Upvotes: 3

Views: 1672

Answers (1)

Kaiido
Kaiido

Reputation: 137123

What happens here is that the default behavior of a mousedown + mousemove is to initiate a grab.
Certainly, at some point, the browser will select some content in the page and start grabbing it.

The solution to avoid this is to tell the browser that your code does handle the event and should thus not perform its usual behavior. You can do so by calling the Event::preventDefault() method:

const GRID_SIZE = 50;
for(let i = 0; i < GRID_SIZE * GRID_SIZE; i++){
    const container = document.getElementById('container');
    let div = document.createElement('div');
    div.classList.add('box');
    container.appendChild(div);
}

function fillBox(evt){
    evt.preventDefault(); // tell the browser we handle that event
    this.classList.add('filled');
}

function clearGrid(){
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.classList.remove('filled'));
}

function startDrawing(evt){
    evt.preventDefault(); // tell the browser we handle that event
    // console.log("start drawing");
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.addEventListener('mouseover', fillBox));
}

function stopDrawing(evt){
    evt.preventDefault(); // tell the browser we handle that event
    // console.log("stop drawing");
    const boxes = document.querySelectorAll('.box');
    boxes.forEach(box => box.removeEventListener('mouseover', fillBox));
}

const container = document.querySelector('#container');
container.addEventListener('mousedown', startDrawing);
container.addEventListener('mouseup', stopDrawing);

const button = document.querySelector('#clear-grid-btn');
button.onclick = clearGrid;
#container{
    width: 500px;
    display: grid;
    grid-template-columns: repeat(50, 10px);
    grid-template-rows: repeat(50, 10px);
    border: solid;
    border-color: black;
    margin:auto;
}

.box{
    width: 10px;
    height: 10px;
}

.box:hover{
    background-color: blue;
}

.filled{
    background-color: blue;
}

#clear-grid-btn{
    display:block;
    margin:auto;
    margin-top: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="etch-a-sketch.css">
</head>
<body>
    <div id="container"></div>
    <button id="clear-grid-btn">Clear grid</button>
</body>
<script src="etch-a-sketch.js"></script>
</html>

Upvotes: 4

Related Questions