Search code examples
javascriptmouseeventmousemovemousedown

JavaScript - Div Elements Appending Twice on One Click


//Global variables
let count = 1;
let canvas = document.querySelector('#canvas');
let canvasContainer = document.querySelector('.canvas-container');
let designBtn = document.querySelector('#design-btn');
let editBtn = document.querySelector('#edit-btn');
let isDesign = false;
let isEdit = false;
let circleArray = [];
let offset = [];


//Click event listener for design button
designBtn.addEventListener('click', (event)=>{
  isDesign = true;
  isEdit = false;

  designBtn.style.backgroundColor = '#BA4A00'
  designBtn.style.color = '#17202A'

  editBtn.style.backgroundColor = null;
  editBtn.style.color = null

  //Invoke being_design function
  begin_design()
})

//Click event listener for edit button
editBtn.addEventListener('click', (event)=>{
  isDesign = false;
  isEdit = true;

  editBtn.style.backgroundColor = '#BA4A00'
  editBtn.style.color = '#17202A'

  designBtn.style.backgroundColor = null;
  designBtn.style.color = null

  //Invoke edit_design function
  edit_design()
})



//Function creates new element and then appends it to the parent div
function create_circle_element(x, y){
  let circle = document.createElement('div')

  const circleHeight = 40;
  const circleWidth = 40;

  circle.style.position = 'absolute';
  circle.style.backgroundColor = 'orange';
  circle.style.height = `${circleHeight}px`;
  circle.style.width = `${circleWidth}px`;
  circle.style.borderRadius = '50%'
  circle.style.textAlign = 'center';
  circle.style.lineHeight = `${circleHeight}px`;
  circle.style.cursor = 'pointer';
  circle.style.left = `${(x - (canvas.offsetLeft + canvasContainer.offsetLeft - window.scrollX)) - (circleWidth/2)}px`;
  circle.style.top = `${(y -(canvas.offsetTop + canvasContainer.offsetTop - window.scrollY)) - (circleHeight/2)}px`

  circle.textContent = `${count}`;
  canvas.append(circle)
  circleArray.push(circle)
  count++
}


//Function responsible for adding circles to the canvas
//Function is invoked when design button is clicked
function begin_design(){
    let mousePosition;
    canvas.addEventListener('mousedown', (event)=>{
    if(isDesign){
      mousePosition = {
        x: event.clientX,
        y: event.clientY
      }
        create_circle_element(mousePosition.x, mousePosition.y);
      }
    })
}


//Function responsible for editing the circles on the canvas i.e moving them around on mousedown/mousemove event
//Function is invoked when edit button is clicked
function edit_design(){
  if(isEdit){
  let mouseDown = false;
  let offset = [];
  let circleClickedOn = [];

//Set mouseDown to false
  canvas.addEventListener('mouseup', ()=>{
    mouseDown = false
  })

//Loop through the newly created circles and attached 'mousedown' event to each
  circleArray.forEach((circleElement)=>{
    circleElement.addEventListener('mousedown', (event)=>{

      mouseDown = true;
      offset = [
        circleElement.offsetLeft - event.clientX,
        circleElement.offsetTop - event.clientY
      ]
      circleClickedOn = [circleElement]
    })
  })

//Move circles around
  canvas.addEventListener('mousemove', (event)=>{
    if(mouseDown){
     let mousePosition;

     mousePosition = {
       x: event.clientX,
       y: event.clientY
     }

     circleClickedOn[0].style.left = `${offset[0] + mousePosition.x}px`
     circleClickedOn[0].style.top = `${offset[1] + mousePosition.y}px`
    }
  })
}
}
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body{
  position: relative;
  width: 100%;
  min-height: 100vh;
}


.sidebar{
  position: fixed;
  top: 0;
  left: 0;
  background-color: #17202A;
  width: 50px;
  min-height: 100vh;
}

.sidebar .sidebar-top{
  position: relative;
  height: 35px;
}
.sidebar .sidebar-top #toggle-btn{
  position: absolute;
  display: flex;
  height: 35px;
  width: 100%;
  justify-content: center;
  align-items: center;
  font-size: 18px;
  cursor: pointer;
  color: #808B96
}

.sidebar .sidebar-center{
  position: relative;
  width: 100%;
  margin-top: 15px;
}
.sidebar .sidebar-center ul li{
  position: relative;
  height: 35px;
  margin-bottom: 5px;
  list-style: none;
}
.sidebar .sidebar-center ul li a{
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 35px;
  text-decoration: none
}

.sidebar .sidebar-center ul li a{
  font-size: 18px;
  color: #808B96
}
.sidebar .sidebar-center ul li a:hover{
  background-color: #2e4053
}


.canvas-container{
  position: absolute;
  top: 0;
  width: calc(100% - 50px);
  min-height: 100vh;
  left: 50px;
  padding: 20px;
  background-color: #2E4053
}

.canvas-container #canvas{
  position: relative;
  width: 100%;
  min-height: 100vh;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <title>testing</title>
  <link rel="stylesheet" href="style.css">
  <!-- Boxicons CDN Link -->
  <link href='https://unpkg.com/[email protected]/css/boxicons.min.css' rel='stylesheet'>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
  <body>
    <div class="sidebar">
      <div class="sidebar-top">
        <i class='bx bx-menu' id="toggle-btn"></i>
      </div>
      <div class="sidebar-center">
        <ul class="nav-list">
          <li>
            <a href="#" id="design-btn">
                <i class='bx bx-pyramid'></i>
            </a>
          </li>

          <li>
            <a href="#" id="edit-btn">
                <i class='bx bxs-edit-alt'></i>
            </a>
          </li>
        </ul>

      </div>
    </div>
    <div class="canvas-container">
      <div id="canvas"></div>
    <script src="script.js"></script>
  </body>
</html>

Objective - When user clicks design button, the circles are added to the canvas on mousedown event. When user clicks the edit button, they are able to move the circles around. When the design button is clicked again, user is able to continue adding circles and incrementing the number from where it was left off.

Error to resolve - When design is first clicked, I am able to successfully add circles to the canvas. If I click on edit button and move the circles around and then click on design button right after, it appends two circles on on click. Why is it appending two circles? This only happens after I click on edit.

***JAVASCRIPT***
//Global variables
let count = 1;
let canvas = document.querySelector('#canvas');
let canvasContainer = document.querySelector('.canvas-container')
let designBtn = document.querySelector('#design-btn');
let editBtn = document.querySelector('#edit-btn');
let isDesign = false;
let isEdit = false;
let circleArray = [];
let offset = []


//Click event listener for design button
designBtn.addEventListener('click', (event)=>{
  isDesign = true;
  isEdit = false;

  designBtn.style.backgroundColor = '#BA4A00'
  designBtn.style.color = '#17202A'

  editBtn.style.backgroundColor = null;
  editBtn.style.color = null

  //Invoke being_design function
  begin_design()
})

//Click event listener for edit button
editBtn.addEventListener('click', (event)=>{
  isDesign = false;
  isEdit = true;

  editBtn.style.backgroundColor = '#BA4A00'
  editBtn.style.color = '#17202A'

  designBtn.style.backgroundColor = null;
  designBtn.style.color = null

  //Invoke edit_design function
  edit_design()
})



//Function creates new element and then appends it to the parent div
function create_circle_element(x, y){
  let circle = document.createElement('div')

  const circleHeight = 40;
  const circleWidth = 40;

  circle.style.position = 'absolute';
  circle.style.backgroundColor = 'orange';
  circle.style.height = `${circleHeight}px`;
  circle.style.width = `${circleWidth}px`;
  circle.style.borderRadius = '50%'
  circle.style.textAlign = 'center';
  circle.style.lineHeight = `${circleHeight}px`;
  circle.style.cursor = 'pointer';
  circle.style.left = `${(x - (canvas.offsetLeft + canvasContainer.offsetLeft - window.scrollX)) - (circleWidth/2)}px`;
  circle.style.top = `${(y -(canvas.offsetTop + canvasContainer.offsetTop - window.scrollY)) - (circleHeight/2)}px`

  circle.textContent = `${count}`;
  canvas.append(circle)
  circleArray.push(circle)
  count++
}


//Function responsible for adding circles to the canvas
//Function is invoked when design button is clicked
function begin_design(){
    let mousePosition;
    canvas.addEventListener('mousedown', (event)=>{
    if(isDesign){
      mousePosition = {
        x: event.clientX,
        y: event.clientY
      }
        create_circle_element(mousePosition.x, mousePosition.y)
      }
    })
}


//Function responsible for editing the circles on the canvas i.e moving them around on mousedown/mousemove event
//Function is invoked when edit button is clicked
function edit_design(){
  if(isEdit){
  let mouseDown = false;
  let offset = [];
  let circleClickedOn = [];

//Set mouseDown to false
  canvas.addEventListener('mouseup', ()=>{
    mouseDown = false
  })

//Loop through the newly created circles and attached 'mousedown' event to each
  circleArray.forEach((circleElement)=>{
    circleElement.addEventListener('mousedown', (event)=>{

      mouseDown = true;
      offset = [
        circleElement.offsetLeft - event.clientX,
        circleElement.offsetTop - event.clientY
      ]
      circleClickedOn = [circleElement]
    })
  })

//Move circles around
  canvas.addEventListener('mousemove', (event)=>{
    if(mouseDown){
     let mousePosition;

     mousePosition = {
       x: event.clientX,
       y: event.clientY
     }

     circleClickedOn[0].style.left = `${offset[0] + mousePosition.x}px`
     circleClickedOn[0].style.top = `${offset[1] + mousePosition.y}px`
    }
  })
}
}
***HTML***
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <title>Testing</title>
  <link rel="stylesheet" href="style.css">
  <!-- Boxicons CDN Link -->
  <link href='https://unpkg.com/[email protected]/css/boxicons.min.css' rel='stylesheet'>
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
  <body>
    <div class="sidebar">
      <div class="sidebar-top">
        <i class='bx bx-menu' id="toggle-btn"></i>
      </div>
      <div class="sidebar-center">
        <ul class="nav-list">
          <li>
            <a href="#" id="design-btn">
                <i class='bx bx-pyramid'></i>
            </a>
          </li>

          <li>
            <a href="#" id="edit-btn">
                <i class='bx bxs-edit-alt'></i>
            </a>
          </li>
        </ul>

      </div>
    </div>
    <div class="canvas-container">
      <div id="canvas"></div>
    <script src="script.js"></script>
  </body>
</html>
***CSS***
*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body{
  position: relative;
  width: 100%;
  min-height: 100vh;
}


.sidebar{
  position: fixed;
  top: 0;
  left: 0;
  background-color: #17202A;
  width: 50px;
  min-height: 100vh;
}

.sidebar .sidebar-top{
  position: relative;
  height: 35px;
}
.sidebar .sidebar-top #toggle-btn{
  position: absolute;
  display: flex;
  height: 35px;
  width: 100%;
  justify-content: center;
  align-items: center;
  font-size: 18px;
  cursor: pointer;
  color: #808B96
}

.sidebar .sidebar-center{
  position: relative;
  width: 100%;
  margin-top: 15px;
}
.sidebar .sidebar-center ul li{
  position: relative;
  height: 35px;
  margin-bottom: 5px;
  list-style: none;
}
.sidebar .sidebar-center ul li a{
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 35px;
  text-decoration: none
}

.sidebar .sidebar-center ul li a{
  font-size: 18px;
  color: #808B96
}
.sidebar .sidebar-center ul li a:hover{
  background-color: #2e4053
}


.canvas-container{
  position: absolute;
  top: 0;
  width: calc(100% - 50px);
  min-height: 100vh;
  left: 50px;
  padding: 20px;
  background-color: #2E4053
}

.canvas-container #canvas{
  position: relative;
  width: 100%;
  min-height: 100vh;
}


Solution

    1. The issue is you're registering the mouse-down event handler in design mode every time user switches to the design mode.
    2. However you never unregister the mouse-down event handler upon switching to edit mode.
    3. This results in multiple event handler to be registered for canvas click event(mouse-down).
    4. So whenever you switch back to the design mode and click upon the canvas, it fires click handler for previously registered listener as well as the newly registered listener.
    5. Check the updated snippet

    Comment lines marked with // Solution :

    //Global variables
    let count = 1;
    let canvas = document.querySelector('#canvas');
    let canvasContainer = document.querySelector('.canvas-container');
    let designBtn = document.querySelector('#design-btn');
    let editBtn = document.querySelector('#edit-btn');
    let isDesign = false;
    let isEdit = false;
    let circleArray = [];
    let offset = [];
    
    
    //Click event listener for design button
    designBtn.addEventListener('click', (event)=>{
      isDesign = true;
      isEdit = false;
    
      designBtn.style.backgroundColor = '#BA4A00'
      designBtn.style.color = '#17202A'
    
      editBtn.style.backgroundColor = null;
      editBtn.style.color = null
    
      //Invoke being_design function
      begin_design()
    })
    
    //Click event listener for edit button
    editBtn.addEventListener('click', (event)=>{
      isDesign = false;
      isEdit = true;
    
      editBtn.style.backgroundColor = '#BA4A00'
      editBtn.style.color = '#17202A'
    
      designBtn.style.backgroundColor = null;
      designBtn.style.color = null
    
      //Invoke edit_design function
      edit_design()
    })
    
    
    
    //Function creates new element and then appends it to the parent div
    function create_circle_element(x, y){
      let circle = document.createElement('div')
    
      const circleHeight = 40;
      const circleWidth = 40;
    
      circle.style.position = 'absolute';
      circle.style.backgroundColor = 'orange';
      circle.style.height = `${circleHeight}px`;
      circle.style.width = `${circleWidth}px`;
      circle.style.borderRadius = '50%'
      circle.style.textAlign = 'center';
      circle.style.lineHeight = `${circleHeight}px`;
      circle.style.cursor = 'pointer';
      circle.style.left = `${(x - (canvas.offsetLeft + canvasContainer.offsetLeft - window.scrollX)) - (circleWidth/2)}px`;
      circle.style.top = `${(y -(canvas.offsetTop + canvasContainer.offsetTop - window.scrollY)) - (circleHeight/2)}px`
    
      circle.textContent = `${count}`;
      canvas.append(circle)
      circleArray.push(circle)
      count++
    }
    
    // Solution : Define a click handler
    var designClickHandler = (event)=>{
        if(isDesign){
          mousePosition = {
            x: event.clientX,
            y: event.clientY
          }
            create_circle_element(mousePosition.x, mousePosition.y);
          }
        }
    
    //Function responsible for adding circles to the canvas
    //Function is invoked when design button is clicked
    function begin_design(){
        let mousePosition;
        
        // Solution : Register click handler
        canvas.addEventListener('mousedown', designClickHandler)
    }
    
    
    //Function responsible for editing the circles on the canvas i.e moving them around on mousedown/mousemove event
    //Function is invoked when edit button is clicked
    function edit_design(){
      if(isEdit){
      
      // Solution : unregister click handler
      canvas.removeEventListener('mousedown', designClickHandler)
    
      
      let mouseDown = false;
      let offset = [];
      let circleClickedOn = [];
    
    //Set mouseDown to false
      canvas.addEventListener('mouseup', ()=>{
        mouseDown = false
      })
    
    //Loop through the newly created circles and attached 'mousedown' event to each
      circleArray.forEach((circleElement)=>{
        circleElement.addEventListener('mousedown', (event)=>{
    
          mouseDown = true;
          offset = [
            circleElement.offsetLeft - event.clientX,
            circleElement.offsetTop - event.clientY
          ]
          circleClickedOn = [circleElement]
        })
      })
    
    //Move circles around
      canvas.addEventListener('mousemove', (event)=>{
        if(mouseDown){
         let mousePosition;
    
         mousePosition = {
           x: event.clientX,
           y: event.clientY
         }
    
         circleClickedOn[0].style.left = `${offset[0] + mousePosition.x}px`
         circleClickedOn[0].style.top = `${offset[1] + mousePosition.y}px`
        }
      })
    }
    }
    *{
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body{
      position: relative;
      width: 100%;
      min-height: 100vh;
      user-select: none;
    }
    
    
    .sidebar{
      position: fixed;
      top: 0;
      left: 0;
      background-color: #17202A;
      width: 50px;
      min-height: 100vh;
    }
    
    .sidebar .sidebar-top{
      position: relative;
      height: 35px;
    }
    .sidebar .sidebar-top #toggle-btn{
      position: absolute;
      display: flex;
      height: 35px;
      width: 100%;
      justify-content: center;
      align-items: center;
      font-size: 18px;
      cursor: pointer;
      color: #808B96
    }
    
    .sidebar .sidebar-center{
      position: relative;
      width: 100%;
      margin-top: 15px;
    }
    .sidebar .sidebar-center ul li{
      position: relative;
      height: 35px;
      margin-bottom: 5px;
      list-style: none;
    }
    .sidebar .sidebar-center ul li a{
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 35px;
      text-decoration: none
    }
    
    .sidebar .sidebar-center ul li a{
      font-size: 18px;
      color: #808B96
    }
    .sidebar .sidebar-center ul li a:hover{
      background-color: #2e4053
    }
    
    
    .canvas-container{
      position: absolute;
      top: 0;
      width: calc(100% - 50px);
      min-height: 100vh;
      left: 50px;
      padding: 20px;
      background-color: #2E4053
    }
    
    .canvas-container #canvas{
      position: relative;
      width: 100%;
      min-height: 100vh;
    }
    <!DOCTYPE html>
    <html lang="en" dir="ltr">
    <head>
      <meta charset="utf-8">
      <title>hiveport</title>
      <link rel="stylesheet" href="style.css">
      <!-- Boxicons CDN Link -->
      <link href='https://unpkg.com/[email protected]/css/boxicons.min.css' rel='stylesheet'>
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
      <body>
        <div class="sidebar">
          <div class="sidebar-top">
            <i class='bx bx-menu' id="toggle-btn"></i>
          </div>
          <div class="sidebar-center">
            <ul class="nav-list">
              <li>
                <a href="#" id="design-btn">
                    <i class='bx bx-pyramid'></i>
                </a>
              </li>
    
              <li>
                <a href="#" id="edit-btn">
                    <i class='bx bxs-edit-alt'></i>
                </a>
              </li>
            </ul>
    
          </div>
        </div>
        <div class="canvas-container">
          <div id="canvas"></div>
        <script src="script.js"></script>
      </body>
    </html>