javascriptcssdrag-and-drop

Draggable div is sticked to the right side of screen until max-width is reached while dragging


I'm making Chrome extension. I'm trying to make popup to be draggable and to be able to move it around screen. I'm able to move it but it's stuck to the right side of screen until it reaches max-width. What am I doing wrong?

Here's simplified code.

const popup = document.getElementById("popup");
const dragBtn = document.getElementById("drag-btn");

let offsetX, offsetY, isDragging = false;

dragBtn.addEventListener("mousedown", (e) => {
    isDragging = true;
    offsetX = e.clientX - popup.offsetLeft;
    offsetY = e.clientY - popup.offsetTop;
});

document.addEventListener("mousemove", (e) => {
    if (isDragging) {
        popup.style.left = `${e.clientX - offsetX}px`;
        popup.style.top = `${e.clientY - offsetY}px`;
    }
});

document.addEventListener("mouseup", (e) => {
    isDragging = false;
});
#popup {
    position: fixed;
    top: 20px;
    right: 20px;
    min-width: 350px;
    max-width: 500px;
    background: var(--dark-bg, #222);
    color: var(--dark-text, #fff);
    border: 1px solid #333;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
    padding: 15px;
    border-radius: 8px;
    z-index: 10000;
}

#popup-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}

#popup-title {
    font-size: 14px;
    font-weight: bold;
}

button {
    border: none;
    cursor: pointer;
    border-radius: 4px;
    font-size: 16px;
    width: 26px;
    height: 26px;
    text-align: center;
    padding: 0;
}

#drag-btn {
    background: #326042;
    color: white;
    cursor: grab;
}

#theme-btn {
    background: #ef713b;
    color: white;
}

#close-btn {
    background: #ff5252;
    color: white;
}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Song Lyrics Finder</title>
    <link rel="stylesheet" href="src/style.css">
</head>
<body>
    <div id="popup">
        <div id="popup-header">
            <p id="popup-title">Song Lyrics Finder (from Genius.com)</p>
            <div style="display: flex; align-items: center; gap: 5px;">
                <button id="drag-btn">✥</button>
                <button id="theme-btn">&#9681;</button>
                <button id="close-btn">&#11199;</button>
            </div>
        </div>
    </div>
</body>
</html>


Solution

  • Because the element has a right value, and you're changing the left value, you are stretching the element until it reaches maximum size.

    I would change the position using transform instead. In addition to not tempering with the width of the element, the performance is usually better (see in this SO answer):

    const popup = document.getElementById("popup");
    const dragBtn = document.getElementById("drag-btn");
    
    let offsetX, offsetY, isDragging = false;
    
    dragBtn.addEventListener("mousedown", (e) => {
      isDragging = true;
      
      // get previous translate values
      const [tranX, tranY] = (popup.style.translate || '0 0').split(' ').map(v => parseInt(v, 10));
      
      offsetX = e.clientX - tranX;
      offsetY = e.clientY - tranY;
    });
    
    document.addEventListener("mousemove", (e) => {
      if (!isDragging) return;
    
      popup.style.translate = `${e.clientX - offsetX}px ${e.clientY - offsetY}px`;
    });
    
    document.addEventListener("mouseup", (e) => {
      isDragging = false;
    });
    #popup {
      position: fixed;
      top: 20px;
      right: 20px;
      min-width: 350px;
      max-width: 500px;
      background: var(--dark-bg, #222);
      color: var(--dark-text, #fff);
      border: 1px solid #333;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
      padding: 15px;
      border-radius: 8px;
      z-index: 10000;
      transform-style: preserve-3d;
    }
    
    #popup-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    
    #popup-title {
      font-size: 14px;
      font-weight: bold;
    }
    
    button {
      border: none;
      cursor: pointer;
      border-radius: 4px;
      font-size: 16px;
      width: 26px;
      height: 26px;
      text-align: center;
      padding: 0;
    }
    
    #drag-btn {
      background: #326042;
      color: white;
      cursor: grab;
    }
    
    #theme-btn {
      background: #ef713b;
      color: white;
    }
    
    #close-btn {
      background: #ff5252;
      color: white;
    }
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Song Lyrics Finder</title>
      <link rel="stylesheet" href="src/style.css">
    </head>
    
    <body>
      <div id="popup">
        <div id="popup-header">
          <p id="popup-title">Song Lyrics Finder (from Genius.com)</p>
          <div style="display: flex; align-items: center; gap: 5px;">
            <button id="drag-btn">✥</button>
            <button id="theme-btn">&#9681;</button>
            <button id="close-btn">&#11199;</button>
          </div>
        </div>
      </div>
    </body>
    
    </html>

    Another option is to reset right to auto and set the current left on mouse down event, because we're going to update the left from now on:

    const popup = document.getElementById("popup");
    const dragBtn = document.getElementById("drag-btn");
    
    let offsetX, offsetY, isDragging = false;
    
    dragBtn.addEventListener("mousedown", (e) => {
      isDragging = true;
      
      const { left } = popup.getBoundingClientRect();
      
      // set right to auto so it won't interfere
      popup.style.right = 'auto';
      
      // set left
      popup.style.left = `${left}px`;
    
      offsetX = e.clientX - popup.offsetLeft;
      offsetY = e.clientY - popup.offsetTop;
    });
    
    document.addEventListener("mousemove", (e) => {
      if (!isDragging) return;
      
      popup.style.left = `${e.clientX - offsetX}px`;
      popup.style.top = `${e.clientY - offsetY}px`;
    });
    
    document.addEventListener("mouseup", (e) => {
      isDragging = false;
    });
    #popup {
      position: fixed;
      top: 20px;
      right: 20px;
      min-width: 350px;
      max-width: 500px;
      background: var(--dark-bg, #222);
      color: var(--dark-text, #fff);
      border: 1px solid #333;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.4);
      padding: 15px;
      border-radius: 8px;
      z-index: 10000;
    }
    
    #popup-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    
    #popup-title {
      font-size: 14px;
      font-weight: bold;
    }
    
    button {
      border: none;
      cursor: pointer;
      border-radius: 4px;
      font-size: 16px;
      width: 26px;
      height: 26px;
      text-align: center;
      padding: 0;
    }
    
    #drag-btn {
      background: #326042;
      color: white;
      cursor: grab;
    }
    
    #theme-btn {
      background: #ef713b;
      color: white;
    }
    
    #close-btn {
      background: #ff5252;
      color: white;
    }
    <div id="popup">
      <div id="popup-header">
        <p id="popup-title">Song Lyrics Finder (from Genius.com)</p>
        <div style="display: flex; align-items: center; gap: 5px;">
          <button id="drag-btn">✥</button>
          <button id="theme-btn">&#9681;</button>
          <button id="close-btn">&#11199;</button>
        </div>
      </div>
    </div>