5
\$\begingroup\$

Currently I'm engaged with implementing a Drag and Drop GUI. I've discovered that there a not many resources (tutorials, etc.) available. So I wrote this:

function Cursor(cssSelector, rightLimit, bottomLimit) {
    var element = document.querySelector('#square');
    var styles = window.getComputedStyle(square);
    var x = 0;
    var y = 0;
    var fromLeft = 0;
    var fromTop = 0;
    var pushed = false;
    var limits = {
        top: 0,
        right: rightLimit,
        bottom: bottomLimit,
        left: 0
    }

    // Uses the offsetX and the offsetY of the 
    //  mousedown event.
    this.setCoordinates = function(left, top) {
      if (!fromLeft && !fromTop) {
        fromLeft = left;
        fromTop = top;
      } 
    }

    this.togglePushed = function() {
        pushed ? pushed = false : pushed = true;
    }

    this.getPushed = function() {
        return pushed;
    }

    // Uses the offsetX and the offsetY of the 
    //   mousemove event.
    this.moveCursor = function(offsetX, offsetY) {
      // How much have the x and the y coordinate
      //   changed since the mousedown event?
      var tmpX = offsetX - fromLeft;
      var tmpY = offsetY - fromTop;

      if ((x + tmpX) <= limits.right && (x + tmpX) >= limits.left &&
          (y + tmpY) >= limits.top && (y + tmpY) <= limits.bottom) {
        // If the values are valid then store them ...
        x += tmpX;
        y += tmpY;
        // ... and use them to move the element.
        element.style.left = x + 'px';
        element.style.top = y + 'px';
      }
    }  
  }

  var cursor = new Cursor('#square', 550, 450);

  square.addEventListener('mousedown', function(ev) {
    cursor.togglePushed();
    cursor.setCoordinates(ev.offsetX, ev.offsetY);
  });

  document.body.addEventListener('mouseup', function(ev) {
    cursor.togglePushed();
  });

  square.addEventListener('mousemove', function(ev) {
    if (cursor.getPushed()) { 
      cursor.moveCursor(ev.offsetX, ev.offsetY);
    }
  });
body {
  background-color: #eefafa;
}

#wrap {
  width: 600px;
  margin: 50px auto;
}

#panel {
  height: 500px;
  position: relative;
  background-color: rgba(150, 150, 150, 0.2);
  border-radius: 3px;
}

#square {
  width: 50px;
  height: 50px;
  background-color: orangered;
  border: 1px solid teal;
  border-radius: 3px;
  position: absolute;
  top: 0px;
  left: 0px;
}

.instruct {
  font-size: 125%;
  font-weight: bold;
  font-family: helvetica;
}
<div id="wrap">
  <p class="instruct">
      Click the square. Keep the mouse-button pushed and
        move the pointer slowly.
  </p>

  <div id="panel">
    <div id="square"></div>
  </div>
</div> 

There's also a demo on CodePen.

\$\endgroup\$
1
  • \$\begingroup\$ pushed ? pushed = false : pushed = true; can be simplified: pushed = !pushed; \$\endgroup\$
    – oliverpool
    Commented Dec 13, 2015 at 13:43

2 Answers 2

6
\$\begingroup\$

Events

Instead of adding 3 (permanent) EventListener, you could just keep the first one: square.addEventListener('mousedown'... and when this is triggered, add the 2 other events mouseup and mousemove on the document (it will also enalbe the user to move quickly)

The mouseup event should then simply remove the 2 event listeners (you will need to keep a trace of the EventListeners).

You can then get rid off the pushed variable.

The square.addEventListener could be moved inside the constructor.

moveCursor

In the method moveCursor instead of moving only if the new position is completely in the area, you could separately check the X and Y coordinates (It will then be easier to move near the edges).

Naming

I don't find the name Cursor to be very expressive... maybe Draggable is a better name?

\$\endgroup\$
1
  • \$\begingroup\$ "Draggable" is indeed a better name. Honestly: I had no good idea how to name it. Thanks a lot. \$\endgroup\$
    – x32
    Commented Dec 14, 2015 at 7:07
3
\$\begingroup\$

One big thing I think you could do to improve this would be to separate out the css and javascript hooks in the HTML.

As they currently stand neither are re-usable without the other. If you want to create another draggable square you will automatically get the styles from the square selector. Or if you want to remove the square styles altogether, the element will not respond to the draggability added by javascript.

Here are a couple of ideas for html elements with attributes that define style and functionality more clearly:

<div class='draggable-element square'></div>

<div class='square' data-draggable='true'></div>

Both ways are good, with data attributes probably shading it because of how clearly they define functionality (draggable-element could after-all be a style class). You can now switch these on and off in the HTML for either style or function reasons without worrying about side effects will happen elsewhere.

Another interesting thing to consider here is how clear it makes your HTML for other developers :-)

Another small point: IDs can't be re-used. I'd suggest removing them all and using classes instead for css. That way you can decorate whatever you like with the square styles.

Incidentally, you currently call Cursor with the parameter #square then hard-code it in the function. The parameter is better and should be used in the function.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.