Move and resize square boxes with Vanilla JSAnimations with vanilla JSSimple login form with vanilla HTML...

Do ranged attacks with improvised weapons get the bonus from the archery fighting style?

Changing Color of error messages

What does "^L" mean in c?

Do I need to be arrogant to get ahead?

awkward or wrong?

What is the term when voters “dishonestly” choose something that they do not want to choose?

Bash - pair each line of file

While on vacation my taxi took a longer route, possibly to scam me out of money. How can I deal with this?

Is it insecure to send a password in a `curl` command?

Why is indicated airspeed rather than ground speed used during the takeoff roll?

Regex aa* = a*a?

What can I do if I am asked to learn different programming languages very frequently?

Violin - Can double stops be played when the strings are not next to each other?

Print a physical multiplication table

Hausdorff dimension of the boundary of fibres of Lipschitz maps

Do US professors/group leaders only get a salary, but no group budget?

Worshiping one God at a time?

How to get the n-th line after a grepped one?

Assassination attempts against Kosh

Do I need to consider instance restrictions when showing a language is in P?

Deletion of copy-ctor & copy-assignment - public, private or protected?

How to generate binary array whose elements with values 1 are randomly drawn

How can my new character not be a role-playing handicap to the party?

Using Leaflet inside Bootstrap container?



Move and resize square boxes with Vanilla JS


Animations with vanilla JSSimple login form with vanilla HTML markupWeb page with draggable boxes in a gridGrabbing elements with a “Move Up” buttonGoogle Maps JavaScript API v3: Sorting Markers with Check BoxesLooping over text boxes and editing valuesAbstraction of google analytics with click events and forms submission handlingPopup classes hierarchy designAdd and remove classes on resizeResize and compress images with imagick













3












$begingroup$


Feature requirements.




  • I can move a square box.

  • I can resize a square box by stretching four edges.

  • Many square boxes can exist.


I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class. But I think that using module pattern is better in this case.



Module Pattern




  • Pros


    • Can manage only focused element. Don't need to care about unfocused elements.

    • Can control event handling in it.

    • Can hide private variables and methods.



  • Cons


    • Maybe need many changes by changing features.

    • Test issue.




Class




  • Pros


    • Can manage and maintain properties of each square box.

    • Can be more clean code.



  • Cons


    • Should manage many instances.

    • Should add additional information to the DOM for finding each instance.




So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?



https://jsfiddle.net/docf1t40/2/






  <!DOCTYPE html>
<html>
<head>
<title>Practice</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.plate {
position: absolute;
width: 100%;
height: 100%;
background: rgb(240, 240, 240);
}
.edge {
position: absolute;
width: 12px;
height: 12px;
border-radius: 6px;
background: rgb(211, 211, 211);
}
.edge.lt {
top: -6px;
left: -6px;
cursor: nwse-resize;
}
.edge.rt {
top: -6px;
right: -6px;
cursor: nesw-resize;
}
.edge.lb {
bottom: -6px;
left: -6px;
cursor: nesw-resize;
}
.edge.rb {
bottom: -6px;
right: -6px;
cursor: nwse-resize;
}
</style>
</head>
<body>
<script type="text/javascript">
const Square = (function() {
let userAction;
let focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};

function focusElement(dom, width, height, sx, sy, tx, ty) {
focusedElement.DOM = dom;
focusedElement.width = width;
focusedElement.height = height;
focusedElement.screenX = sx;
focusedElement.screenY = sy;
focusedElement.translateX = tx;
focusedElement.translateY = ty;
}

function blurElement() {
focusedElement = {
DOM: null,
width: 0,
height: 0,
screenX: 0,
screenY: 0,
translateX: 0,
translateY: 0,
};
}

function getMovement(sx, sy) {
return {
x: sx - focusedElement.screenX,
y: sy - focusedElement.screenY
};
}

function move(sx, sy) {
const movement = getMovement(sx, sy);
const tx = focusedElement.translateX + movement.x;
const ty = focusedElement.translateY + movement.y;
focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
}

function resize(sx, sy) {
const movement = getMovement(sx, sy);
let tx = focusedElement.translateX;
let ty = focusedElement.translateY;
let width = focusedElement.width;
let height = focusedElement.height;

switch (userAction) {
case 'RESIZE-LT':
width = focusedElement.width - movement.x;
height = focusedElement.height - movement.y;
tx = focusedElement.translateX + movement.x;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-RT':
width = focusedElement.width + movement.x;
height = focusedElement.height - movement.y;
ty = focusedElement.translateY + movement.y;
break;
case 'RESIZE-LB':
width = focusedElement.width - movement.x;
height = focusedElement.height + movement.y;
tx = focusedElement.translateX + movement.x;
break;
case 'RESIZE-RB':
width = focusedElement.width + movement.x;
height = focusedElement.height + movement.y;
break;
}

width = Math.max(50, width);
height = Math.max(50, height);

focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
focusedElement.DOM.style.width = `${width}px`;
focusedElement.DOM.style.height = `${height}px`;
}

function onMouseDown(e) {
if (e.target && e.target.dataset && e.target.dataset.userAction) {
let tx = 0;
let ty = 0;
const transform = e.target.parentNode.style.transform;
const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
if (matchTranslate) {
tx = parseInt(matchTranslate[1]);
ty = parseInt(matchTranslate[2]);
}

focusElement(
e.target.parentNode,
parseInt(e.target.parentNode.style.width),
parseInt(e.target.parentNode.style.height),
e.screenX,
e.screenY,
tx,
ty
);

userAction = e.target.dataset.userAction;
}
}

function onMouseUp(e) {
blurElement();

userAction = null;
}

function onMouseMove(e) {
switch (userAction) {
case 'MOVE':
move(e.screenX, e.screenY);
break;
case 'RESIZE-LT':
case 'RESIZE-RT':
case 'RESIZE-LB':
case 'RESIZE-RB':
resize(e.screenX, e.screenY);
break;
}
}

return {
create: function(x, y, width, height) {
const div = document.createElement('div');
div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
<span data-user-action="RESIZE-LT" class="edge lt"></span>
<span data-user-action="RESIZE-RT" class="edge rt"></span>
<span data-user-action="RESIZE-LB" class="edge lb"></span>
<span data-user-action="RESIZE-RB" class="edge rb"></span>`;
document.body.appendChild(div);
},
onMouseDownListener: onMouseDown,
onMouseUpListener: onMouseUp,
onMouseMoveListener: onMouseMove,
}
})();

document.addEventListener('DOMContentLoaded', function() {
Square.create(300, 300, 100, 200);
Square.create(200, 100, 80, 80);
document.addEventListener('mousedown', Square.onMouseDownListener);
document.addEventListener('mouseup', Square.onMouseUpListener);
document.addEventListener('mousemove', Square.onMouseMoveListener);
});
</script>
</body>
</html>












share|improve this question











$endgroup$

















    3












    $begingroup$


    Feature requirements.




    • I can move a square box.

    • I can resize a square box by stretching four edges.

    • Many square boxes can exist.


    I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class. But I think that using module pattern is better in this case.



    Module Pattern




    • Pros


      • Can manage only focused element. Don't need to care about unfocused elements.

      • Can control event handling in it.

      • Can hide private variables and methods.



    • Cons


      • Maybe need many changes by changing features.

      • Test issue.




    Class




    • Pros


      • Can manage and maintain properties of each square box.

      • Can be more clean code.



    • Cons


      • Should manage many instances.

      • Should add additional information to the DOM for finding each instance.




    So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?



    https://jsfiddle.net/docf1t40/2/






      <!DOCTYPE html>
    <html>
    <head>
    <title>Practice</title>
    <style type="text/css">
    * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }
    .plate {
    position: absolute;
    width: 100%;
    height: 100%;
    background: rgb(240, 240, 240);
    }
    .edge {
    position: absolute;
    width: 12px;
    height: 12px;
    border-radius: 6px;
    background: rgb(211, 211, 211);
    }
    .edge.lt {
    top: -6px;
    left: -6px;
    cursor: nwse-resize;
    }
    .edge.rt {
    top: -6px;
    right: -6px;
    cursor: nesw-resize;
    }
    .edge.lb {
    bottom: -6px;
    left: -6px;
    cursor: nesw-resize;
    }
    .edge.rb {
    bottom: -6px;
    right: -6px;
    cursor: nwse-resize;
    }
    </style>
    </head>
    <body>
    <script type="text/javascript">
    const Square = (function() {
    let userAction;
    let focusedElement = {
    DOM: null,
    width: 0,
    height: 0,
    screenX: 0,
    screenY: 0,
    translateX: 0,
    translateY: 0,
    };

    function focusElement(dom, width, height, sx, sy, tx, ty) {
    focusedElement.DOM = dom;
    focusedElement.width = width;
    focusedElement.height = height;
    focusedElement.screenX = sx;
    focusedElement.screenY = sy;
    focusedElement.translateX = tx;
    focusedElement.translateY = ty;
    }

    function blurElement() {
    focusedElement = {
    DOM: null,
    width: 0,
    height: 0,
    screenX: 0,
    screenY: 0,
    translateX: 0,
    translateY: 0,
    };
    }

    function getMovement(sx, sy) {
    return {
    x: sx - focusedElement.screenX,
    y: sy - focusedElement.screenY
    };
    }

    function move(sx, sy) {
    const movement = getMovement(sx, sy);
    const tx = focusedElement.translateX + movement.x;
    const ty = focusedElement.translateY + movement.y;
    focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
    }

    function resize(sx, sy) {
    const movement = getMovement(sx, sy);
    let tx = focusedElement.translateX;
    let ty = focusedElement.translateY;
    let width = focusedElement.width;
    let height = focusedElement.height;

    switch (userAction) {
    case 'RESIZE-LT':
    width = focusedElement.width - movement.x;
    height = focusedElement.height - movement.y;
    tx = focusedElement.translateX + movement.x;
    ty = focusedElement.translateY + movement.y;
    break;
    case 'RESIZE-RT':
    width = focusedElement.width + movement.x;
    height = focusedElement.height - movement.y;
    ty = focusedElement.translateY + movement.y;
    break;
    case 'RESIZE-LB':
    width = focusedElement.width - movement.x;
    height = focusedElement.height + movement.y;
    tx = focusedElement.translateX + movement.x;
    break;
    case 'RESIZE-RB':
    width = focusedElement.width + movement.x;
    height = focusedElement.height + movement.y;
    break;
    }

    width = Math.max(50, width);
    height = Math.max(50, height);

    focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
    focusedElement.DOM.style.width = `${width}px`;
    focusedElement.DOM.style.height = `${height}px`;
    }

    function onMouseDown(e) {
    if (e.target && e.target.dataset && e.target.dataset.userAction) {
    let tx = 0;
    let ty = 0;
    const transform = e.target.parentNode.style.transform;
    const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
    if (matchTranslate) {
    tx = parseInt(matchTranslate[1]);
    ty = parseInt(matchTranslate[2]);
    }

    focusElement(
    e.target.parentNode,
    parseInt(e.target.parentNode.style.width),
    parseInt(e.target.parentNode.style.height),
    e.screenX,
    e.screenY,
    tx,
    ty
    );

    userAction = e.target.dataset.userAction;
    }
    }

    function onMouseUp(e) {
    blurElement();

    userAction = null;
    }

    function onMouseMove(e) {
    switch (userAction) {
    case 'MOVE':
    move(e.screenX, e.screenY);
    break;
    case 'RESIZE-LT':
    case 'RESIZE-RT':
    case 'RESIZE-LB':
    case 'RESIZE-RB':
    resize(e.screenX, e.screenY);
    break;
    }
    }

    return {
    create: function(x, y, width, height) {
    const div = document.createElement('div');
    div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
    div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
    <span data-user-action="RESIZE-LT" class="edge lt"></span>
    <span data-user-action="RESIZE-RT" class="edge rt"></span>
    <span data-user-action="RESIZE-LB" class="edge lb"></span>
    <span data-user-action="RESIZE-RB" class="edge rb"></span>`;
    document.body.appendChild(div);
    },
    onMouseDownListener: onMouseDown,
    onMouseUpListener: onMouseUp,
    onMouseMoveListener: onMouseMove,
    }
    })();

    document.addEventListener('DOMContentLoaded', function() {
    Square.create(300, 300, 100, 200);
    Square.create(200, 100, 80, 80);
    document.addEventListener('mousedown', Square.onMouseDownListener);
    document.addEventListener('mouseup', Square.onMouseUpListener);
    document.addEventListener('mousemove', Square.onMouseMoveListener);
    });
    </script>
    </body>
    </html>












    share|improve this question











    $endgroup$















      3












      3








      3


      1



      $begingroup$


      Feature requirements.




      • I can move a square box.

      • I can resize a square box by stretching four edges.

      • Many square boxes can exist.


      I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class. But I think that using module pattern is better in this case.



      Module Pattern




      • Pros


        • Can manage only focused element. Don't need to care about unfocused elements.

        • Can control event handling in it.

        • Can hide private variables and methods.



      • Cons


        • Maybe need many changes by changing features.

        • Test issue.




      Class




      • Pros


        • Can manage and maintain properties of each square box.

        • Can be more clean code.



      • Cons


        • Should manage many instances.

        • Should add additional information to the DOM for finding each instance.




      So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?



      https://jsfiddle.net/docf1t40/2/






        <!DOCTYPE html>
      <html>
      <head>
      <title>Practice</title>
      <style type="text/css">
      * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      }
      .plate {
      position: absolute;
      width: 100%;
      height: 100%;
      background: rgb(240, 240, 240);
      }
      .edge {
      position: absolute;
      width: 12px;
      height: 12px;
      border-radius: 6px;
      background: rgb(211, 211, 211);
      }
      .edge.lt {
      top: -6px;
      left: -6px;
      cursor: nwse-resize;
      }
      .edge.rt {
      top: -6px;
      right: -6px;
      cursor: nesw-resize;
      }
      .edge.lb {
      bottom: -6px;
      left: -6px;
      cursor: nesw-resize;
      }
      .edge.rb {
      bottom: -6px;
      right: -6px;
      cursor: nwse-resize;
      }
      </style>
      </head>
      <body>
      <script type="text/javascript">
      const Square = (function() {
      let userAction;
      let focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };

      function focusElement(dom, width, height, sx, sy, tx, ty) {
      focusedElement.DOM = dom;
      focusedElement.width = width;
      focusedElement.height = height;
      focusedElement.screenX = sx;
      focusedElement.screenY = sy;
      focusedElement.translateX = tx;
      focusedElement.translateY = ty;
      }

      function blurElement() {
      focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };
      }

      function getMovement(sx, sy) {
      return {
      x: sx - focusedElement.screenX,
      y: sy - focusedElement.screenY
      };
      }

      function move(sx, sy) {
      const movement = getMovement(sx, sy);
      const tx = focusedElement.translateX + movement.x;
      const ty = focusedElement.translateY + movement.y;
      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      }

      function resize(sx, sy) {
      const movement = getMovement(sx, sy);
      let tx = focusedElement.translateX;
      let ty = focusedElement.translateY;
      let width = focusedElement.width;
      let height = focusedElement.height;

      switch (userAction) {
      case 'RESIZE-LT':
      width = focusedElement.width - movement.x;
      height = focusedElement.height - movement.y;
      tx = focusedElement.translateX + movement.x;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-RT':
      width = focusedElement.width + movement.x;
      height = focusedElement.height - movement.y;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-LB':
      width = focusedElement.width - movement.x;
      height = focusedElement.height + movement.y;
      tx = focusedElement.translateX + movement.x;
      break;
      case 'RESIZE-RB':
      width = focusedElement.width + movement.x;
      height = focusedElement.height + movement.y;
      break;
      }

      width = Math.max(50, width);
      height = Math.max(50, height);

      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      focusedElement.DOM.style.width = `${width}px`;
      focusedElement.DOM.style.height = `${height}px`;
      }

      function onMouseDown(e) {
      if (e.target && e.target.dataset && e.target.dataset.userAction) {
      let tx = 0;
      let ty = 0;
      const transform = e.target.parentNode.style.transform;
      const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
      if (matchTranslate) {
      tx = parseInt(matchTranslate[1]);
      ty = parseInt(matchTranslate[2]);
      }

      focusElement(
      e.target.parentNode,
      parseInt(e.target.parentNode.style.width),
      parseInt(e.target.parentNode.style.height),
      e.screenX,
      e.screenY,
      tx,
      ty
      );

      userAction = e.target.dataset.userAction;
      }
      }

      function onMouseUp(e) {
      blurElement();

      userAction = null;
      }

      function onMouseMove(e) {
      switch (userAction) {
      case 'MOVE':
      move(e.screenX, e.screenY);
      break;
      case 'RESIZE-LT':
      case 'RESIZE-RT':
      case 'RESIZE-LB':
      case 'RESIZE-RB':
      resize(e.screenX, e.screenY);
      break;
      }
      }

      return {
      create: function(x, y, width, height) {
      const div = document.createElement('div');
      div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
      div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
      <span data-user-action="RESIZE-LT" class="edge lt"></span>
      <span data-user-action="RESIZE-RT" class="edge rt"></span>
      <span data-user-action="RESIZE-LB" class="edge lb"></span>
      <span data-user-action="RESIZE-RB" class="edge rb"></span>`;
      document.body.appendChild(div);
      },
      onMouseDownListener: onMouseDown,
      onMouseUpListener: onMouseUp,
      onMouseMoveListener: onMouseMove,
      }
      })();

      document.addEventListener('DOMContentLoaded', function() {
      Square.create(300, 300, 100, 200);
      Square.create(200, 100, 80, 80);
      document.addEventListener('mousedown', Square.onMouseDownListener);
      document.addEventListener('mouseup', Square.onMouseUpListener);
      document.addEventListener('mousemove', Square.onMouseMoveListener);
      });
      </script>
      </body>
      </html>












      share|improve this question











      $endgroup$




      Feature requirements.




      • I can move a square box.

      • I can resize a square box by stretching four edges.

      • Many square boxes can exist.


      I've implemented moving and resizing square boxes by module pattern. Of course, I thought making a square box as a Class. But I think that using module pattern is better in this case.



      Module Pattern




      • Pros


        • Can manage only focused element. Don't need to care about unfocused elements.

        • Can control event handling in it.

        • Can hide private variables and methods.



      • Cons


        • Maybe need many changes by changing features.

        • Test issue.




      Class




      • Pros


        • Can manage and maintain properties of each square box.

        • Can be more clean code.



      • Cons


        • Should manage many instances.

        • Should add additional information to the DOM for finding each instance.




      So, I implemented as module pattern. It looks like not good for test and can be more clear code. What should I do more for clean code or more testable?



      https://jsfiddle.net/docf1t40/2/






        <!DOCTYPE html>
      <html>
      <head>
      <title>Practice</title>
      <style type="text/css">
      * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      }
      .plate {
      position: absolute;
      width: 100%;
      height: 100%;
      background: rgb(240, 240, 240);
      }
      .edge {
      position: absolute;
      width: 12px;
      height: 12px;
      border-radius: 6px;
      background: rgb(211, 211, 211);
      }
      .edge.lt {
      top: -6px;
      left: -6px;
      cursor: nwse-resize;
      }
      .edge.rt {
      top: -6px;
      right: -6px;
      cursor: nesw-resize;
      }
      .edge.lb {
      bottom: -6px;
      left: -6px;
      cursor: nesw-resize;
      }
      .edge.rb {
      bottom: -6px;
      right: -6px;
      cursor: nwse-resize;
      }
      </style>
      </head>
      <body>
      <script type="text/javascript">
      const Square = (function() {
      let userAction;
      let focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };

      function focusElement(dom, width, height, sx, sy, tx, ty) {
      focusedElement.DOM = dom;
      focusedElement.width = width;
      focusedElement.height = height;
      focusedElement.screenX = sx;
      focusedElement.screenY = sy;
      focusedElement.translateX = tx;
      focusedElement.translateY = ty;
      }

      function blurElement() {
      focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };
      }

      function getMovement(sx, sy) {
      return {
      x: sx - focusedElement.screenX,
      y: sy - focusedElement.screenY
      };
      }

      function move(sx, sy) {
      const movement = getMovement(sx, sy);
      const tx = focusedElement.translateX + movement.x;
      const ty = focusedElement.translateY + movement.y;
      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      }

      function resize(sx, sy) {
      const movement = getMovement(sx, sy);
      let tx = focusedElement.translateX;
      let ty = focusedElement.translateY;
      let width = focusedElement.width;
      let height = focusedElement.height;

      switch (userAction) {
      case 'RESIZE-LT':
      width = focusedElement.width - movement.x;
      height = focusedElement.height - movement.y;
      tx = focusedElement.translateX + movement.x;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-RT':
      width = focusedElement.width + movement.x;
      height = focusedElement.height - movement.y;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-LB':
      width = focusedElement.width - movement.x;
      height = focusedElement.height + movement.y;
      tx = focusedElement.translateX + movement.x;
      break;
      case 'RESIZE-RB':
      width = focusedElement.width + movement.x;
      height = focusedElement.height + movement.y;
      break;
      }

      width = Math.max(50, width);
      height = Math.max(50, height);

      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      focusedElement.DOM.style.width = `${width}px`;
      focusedElement.DOM.style.height = `${height}px`;
      }

      function onMouseDown(e) {
      if (e.target && e.target.dataset && e.target.dataset.userAction) {
      let tx = 0;
      let ty = 0;
      const transform = e.target.parentNode.style.transform;
      const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
      if (matchTranslate) {
      tx = parseInt(matchTranslate[1]);
      ty = parseInt(matchTranslate[2]);
      }

      focusElement(
      e.target.parentNode,
      parseInt(e.target.parentNode.style.width),
      parseInt(e.target.parentNode.style.height),
      e.screenX,
      e.screenY,
      tx,
      ty
      );

      userAction = e.target.dataset.userAction;
      }
      }

      function onMouseUp(e) {
      blurElement();

      userAction = null;
      }

      function onMouseMove(e) {
      switch (userAction) {
      case 'MOVE':
      move(e.screenX, e.screenY);
      break;
      case 'RESIZE-LT':
      case 'RESIZE-RT':
      case 'RESIZE-LB':
      case 'RESIZE-RB':
      resize(e.screenX, e.screenY);
      break;
      }
      }

      return {
      create: function(x, y, width, height) {
      const div = document.createElement('div');
      div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
      div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
      <span data-user-action="RESIZE-LT" class="edge lt"></span>
      <span data-user-action="RESIZE-RT" class="edge rt"></span>
      <span data-user-action="RESIZE-LB" class="edge lb"></span>
      <span data-user-action="RESIZE-RB" class="edge rb"></span>`;
      document.body.appendChild(div);
      },
      onMouseDownListener: onMouseDown,
      onMouseUpListener: onMouseUp,
      onMouseMoveListener: onMouseMove,
      }
      })();

      document.addEventListener('DOMContentLoaded', function() {
      Square.create(300, 300, 100, 200);
      Square.create(200, 100, 80, 80);
      document.addEventListener('mousedown', Square.onMouseDownListener);
      document.addEventListener('mouseup', Square.onMouseUpListener);
      document.addEventListener('mousemove', Square.onMouseMoveListener);
      });
      </script>
      </body>
      </html>








        <!DOCTYPE html>
      <html>
      <head>
      <title>Practice</title>
      <style type="text/css">
      * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      }
      .plate {
      position: absolute;
      width: 100%;
      height: 100%;
      background: rgb(240, 240, 240);
      }
      .edge {
      position: absolute;
      width: 12px;
      height: 12px;
      border-radius: 6px;
      background: rgb(211, 211, 211);
      }
      .edge.lt {
      top: -6px;
      left: -6px;
      cursor: nwse-resize;
      }
      .edge.rt {
      top: -6px;
      right: -6px;
      cursor: nesw-resize;
      }
      .edge.lb {
      bottom: -6px;
      left: -6px;
      cursor: nesw-resize;
      }
      .edge.rb {
      bottom: -6px;
      right: -6px;
      cursor: nwse-resize;
      }
      </style>
      </head>
      <body>
      <script type="text/javascript">
      const Square = (function() {
      let userAction;
      let focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };

      function focusElement(dom, width, height, sx, sy, tx, ty) {
      focusedElement.DOM = dom;
      focusedElement.width = width;
      focusedElement.height = height;
      focusedElement.screenX = sx;
      focusedElement.screenY = sy;
      focusedElement.translateX = tx;
      focusedElement.translateY = ty;
      }

      function blurElement() {
      focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };
      }

      function getMovement(sx, sy) {
      return {
      x: sx - focusedElement.screenX,
      y: sy - focusedElement.screenY
      };
      }

      function move(sx, sy) {
      const movement = getMovement(sx, sy);
      const tx = focusedElement.translateX + movement.x;
      const ty = focusedElement.translateY + movement.y;
      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      }

      function resize(sx, sy) {
      const movement = getMovement(sx, sy);
      let tx = focusedElement.translateX;
      let ty = focusedElement.translateY;
      let width = focusedElement.width;
      let height = focusedElement.height;

      switch (userAction) {
      case 'RESIZE-LT':
      width = focusedElement.width - movement.x;
      height = focusedElement.height - movement.y;
      tx = focusedElement.translateX + movement.x;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-RT':
      width = focusedElement.width + movement.x;
      height = focusedElement.height - movement.y;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-LB':
      width = focusedElement.width - movement.x;
      height = focusedElement.height + movement.y;
      tx = focusedElement.translateX + movement.x;
      break;
      case 'RESIZE-RB':
      width = focusedElement.width + movement.x;
      height = focusedElement.height + movement.y;
      break;
      }

      width = Math.max(50, width);
      height = Math.max(50, height);

      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      focusedElement.DOM.style.width = `${width}px`;
      focusedElement.DOM.style.height = `${height}px`;
      }

      function onMouseDown(e) {
      if (e.target && e.target.dataset && e.target.dataset.userAction) {
      let tx = 0;
      let ty = 0;
      const transform = e.target.parentNode.style.transform;
      const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
      if (matchTranslate) {
      tx = parseInt(matchTranslate[1]);
      ty = parseInt(matchTranslate[2]);
      }

      focusElement(
      e.target.parentNode,
      parseInt(e.target.parentNode.style.width),
      parseInt(e.target.parentNode.style.height),
      e.screenX,
      e.screenY,
      tx,
      ty
      );

      userAction = e.target.dataset.userAction;
      }
      }

      function onMouseUp(e) {
      blurElement();

      userAction = null;
      }

      function onMouseMove(e) {
      switch (userAction) {
      case 'MOVE':
      move(e.screenX, e.screenY);
      break;
      case 'RESIZE-LT':
      case 'RESIZE-RT':
      case 'RESIZE-LB':
      case 'RESIZE-RB':
      resize(e.screenX, e.screenY);
      break;
      }
      }

      return {
      create: function(x, y, width, height) {
      const div = document.createElement('div');
      div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
      div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
      <span data-user-action="RESIZE-LT" class="edge lt"></span>
      <span data-user-action="RESIZE-RT" class="edge rt"></span>
      <span data-user-action="RESIZE-LB" class="edge lb"></span>
      <span data-user-action="RESIZE-RB" class="edge rb"></span>`;
      document.body.appendChild(div);
      },
      onMouseDownListener: onMouseDown,
      onMouseUpListener: onMouseUp,
      onMouseMoveListener: onMouseMove,
      }
      })();

      document.addEventListener('DOMContentLoaded', function() {
      Square.create(300, 300, 100, 200);
      Square.create(200, 100, 80, 80);
      document.addEventListener('mousedown', Square.onMouseDownListener);
      document.addEventListener('mouseup', Square.onMouseUpListener);
      document.addEventListener('mousemove', Square.onMouseMoveListener);
      });
      </script>
      </body>
      </html>





        <!DOCTYPE html>
      <html>
      <head>
      <title>Practice</title>
      <style type="text/css">
      * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      }
      .plate {
      position: absolute;
      width: 100%;
      height: 100%;
      background: rgb(240, 240, 240);
      }
      .edge {
      position: absolute;
      width: 12px;
      height: 12px;
      border-radius: 6px;
      background: rgb(211, 211, 211);
      }
      .edge.lt {
      top: -6px;
      left: -6px;
      cursor: nwse-resize;
      }
      .edge.rt {
      top: -6px;
      right: -6px;
      cursor: nesw-resize;
      }
      .edge.lb {
      bottom: -6px;
      left: -6px;
      cursor: nesw-resize;
      }
      .edge.rb {
      bottom: -6px;
      right: -6px;
      cursor: nwse-resize;
      }
      </style>
      </head>
      <body>
      <script type="text/javascript">
      const Square = (function() {
      let userAction;
      let focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };

      function focusElement(dom, width, height, sx, sy, tx, ty) {
      focusedElement.DOM = dom;
      focusedElement.width = width;
      focusedElement.height = height;
      focusedElement.screenX = sx;
      focusedElement.screenY = sy;
      focusedElement.translateX = tx;
      focusedElement.translateY = ty;
      }

      function blurElement() {
      focusedElement = {
      DOM: null,
      width: 0,
      height: 0,
      screenX: 0,
      screenY: 0,
      translateX: 0,
      translateY: 0,
      };
      }

      function getMovement(sx, sy) {
      return {
      x: sx - focusedElement.screenX,
      y: sy - focusedElement.screenY
      };
      }

      function move(sx, sy) {
      const movement = getMovement(sx, sy);
      const tx = focusedElement.translateX + movement.x;
      const ty = focusedElement.translateY + movement.y;
      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      }

      function resize(sx, sy) {
      const movement = getMovement(sx, sy);
      let tx = focusedElement.translateX;
      let ty = focusedElement.translateY;
      let width = focusedElement.width;
      let height = focusedElement.height;

      switch (userAction) {
      case 'RESIZE-LT':
      width = focusedElement.width - movement.x;
      height = focusedElement.height - movement.y;
      tx = focusedElement.translateX + movement.x;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-RT':
      width = focusedElement.width + movement.x;
      height = focusedElement.height - movement.y;
      ty = focusedElement.translateY + movement.y;
      break;
      case 'RESIZE-LB':
      width = focusedElement.width - movement.x;
      height = focusedElement.height + movement.y;
      tx = focusedElement.translateX + movement.x;
      break;
      case 'RESIZE-RB':
      width = focusedElement.width + movement.x;
      height = focusedElement.height + movement.y;
      break;
      }

      width = Math.max(50, width);
      height = Math.max(50, height);

      focusedElement.DOM.style.transform = `translate(${tx}px, ${ty}px)`;
      focusedElement.DOM.style.width = `${width}px`;
      focusedElement.DOM.style.height = `${height}px`;
      }

      function onMouseDown(e) {
      if (e.target && e.target.dataset && e.target.dataset.userAction) {
      let tx = 0;
      let ty = 0;
      const transform = e.target.parentNode.style.transform;
      const matchTranslate = transform.match(/translate((-?d+.?d*)px ?, ?(-?d+.?d*)px)/);
      if (matchTranslate) {
      tx = parseInt(matchTranslate[1]);
      ty = parseInt(matchTranslate[2]);
      }

      focusElement(
      e.target.parentNode,
      parseInt(e.target.parentNode.style.width),
      parseInt(e.target.parentNode.style.height),
      e.screenX,
      e.screenY,
      tx,
      ty
      );

      userAction = e.target.dataset.userAction;
      }
      }

      function onMouseUp(e) {
      blurElement();

      userAction = null;
      }

      function onMouseMove(e) {
      switch (userAction) {
      case 'MOVE':
      move(e.screenX, e.screenY);
      break;
      case 'RESIZE-LT':
      case 'RESIZE-RT':
      case 'RESIZE-LB':
      case 'RESIZE-RB':
      resize(e.screenX, e.screenY);
      break;
      }
      }

      return {
      create: function(x, y, width, height) {
      const div = document.createElement('div');
      div.setAttribute('style', `position:absolute; width:${width}px; height:${height}px; transform: translate(${x}px, ${y}px)`);
      div.innerHTML = `<div data-user-action="MOVE" class="plate"></div>
      <span data-user-action="RESIZE-LT" class="edge lt"></span>
      <span data-user-action="RESIZE-RT" class="edge rt"></span>
      <span data-user-action="RESIZE-LB" class="edge lb"></span>
      <span data-user-action="RESIZE-RB" class="edge rb"></span>`;
      document.body.appendChild(div);
      },
      onMouseDownListener: onMouseDown,
      onMouseUpListener: onMouseUp,
      onMouseMoveListener: onMouseMove,
      }
      })();

      document.addEventListener('DOMContentLoaded', function() {
      Square.create(300, 300, 100, 200);
      Square.create(200, 100, 80, 80);
      document.addEventListener('mousedown', Square.onMouseDownListener);
      document.addEventListener('mouseup', Square.onMouseUpListener);
      document.addEventListener('mousemove', Square.onMouseMoveListener);
      });
      </script>
      </body>
      </html>






      javascript html dom revealing-module-pattern






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Mar 14 at 2:01









      200_success

      130k17153419




      130k17153419










      asked Mar 14 at 1:47









      gentlejogentlejo

      1234




      1234






















          1 Answer
          1






          active

          oldest

          votes


















          4












          $begingroup$


          class syntax



          In JS class is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.



          The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.



          Modules



          In modern JS module, modules, modular, etc... refers to code that is import/export able.



          Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class syntax. The same can be achieved with the IIS style and class syntax.



          Objects



          Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))() It provides the best encapsulation mechanism for single instance objects (such as your example)



          A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} } best suited to long lived multi instance objects.



          For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}}; However the encapsulation model is not as strong.



          Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).



          There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.



          Reviewing your code




          • The mouse event provides delta mouse position in MouseEvent.movementX and MouseEvent.movementY so you don't need to function getMovement


          • You should not expose the mouse events for Square. Have Square.create add the events if needed.


          • You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)


          • It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example /* DOM helpers */)


          • Rather than inspecting the DOM element to get its position and size, assign your focusElement to each size-able element as you create it. Use a Map to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside the Square object


          • You update the DOM elements position in two places. Use a single function to update the position (and size) of the element.


          • To reduce code complexity use the MouseEvent.type property to determine the event type and handle all similar mouse events in one function.


          • Mouse events are out of sync with the display refresh. For the cleanest updates you should be using requestAnimationFrame to update elements. requestAnimationFrame ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.



          Example



          Uses same IIF style to encapsulate the object (renamed Squares as it can represent many)



          Removed the 3 exposed onMouse... functions.



          The aim was to reduce source size and improve efficiency.



          Creates a boxDesc for each box mapped to the element, rather than updating the one focusElement object.



          I did not include the use of requestAnimationFrame






             const Squares = (() => {
          /* Privates */
          var addMouseEvents = true, focus;
          const boxMap = new Map();
          const corners = { // bit fields
          none: 0, // 0b0000
          top: 1, // 0b0001
          bottom: 2, // 0b0010
          left: 4, // 0b0100
          right: 8, // 0b1000
          };
          const MIN_SIZE = 50;
          const HANDLE_SIZE = 8; // adds 2 px

          /* DOM helpers */
          const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
          const elStyle = (el, style) => (Object.assign(el.style, style), el);
          const elData = (el, data) => (Object.assign(el.dataset, data), el);
          const append = (par, ...sibs) => {
          for(const sib of sibs) { par.appendChild(sib) }
          return par;
          };

          const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
          function update(box) {
          box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
          box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
          const right = innerWidth - (box.width + HANDLE_SIZE);
          const bot = innerHeight - (box.height + HANDLE_SIZE);
          box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
          box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
          elStyle(box.DOM, {
          transform: `translate(${box.x}px, ${box.y}px)`,
          width: box.width + "px",
          height: box.height + "px",
          });
          return box;
          }
          function move(box, dx, dy) {
          const bot = innerHeight - HANDLE_SIZE;
          const right = innerWidth - HANDLE_SIZE;
          if (box.action === corners.none) {
          box.x += dx;
          box.y += dy;
          } else {
          if ((box.action & corners.bottom) === corners.bottom) {
          box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
          } else {
          if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
          if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
          box.y += dy;
          box.height -= dy;
          }
          if ((box.action & corners.right) === corners.right) {
          box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
          } else {
          if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
          if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
          box.x += dx;
          box.width -= dx;
          }
          }
          update(box);
          }
          function mouseEvent(e) {
          if(e.type === "mousedown") {
          if (e.target.dataset && e.target.dataset.userAction) {
          focus = boxMap.get(e.target.parentNode);
          focus.action = Number(e.target.dataset.userAction);
          }
          }else if(e.type === "mouseup") { focus = undefined }
          else {
          if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
          if (focus) { move(focus, e.movementX, e.movementY) }
          }
          }
          function mouseEvents() {
          document.addEventListener('mousedown', mouseEvent);
          document.addEventListener('mouseup', mouseEvent);
          document.addEventListener('mousemove', mouseEvent);
          addMouseEvents = false;
          }
          return {
          create(x, y, width, height) {
          const box = append(
          elStyle(tag("div"), { position: "absolute" }),
          elData(tag("div", {className : "plate"}), {userAction : corners.none}),
          elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
          elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
          elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
          elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
          );
          boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
          append(document.body, box);
          if (addMouseEvents) { mouseEvents() }
          }
          };

          })();

          document.addEventListener('DOMContentLoaded', function() {
          Squares.create(10, 100, 80, 80);
          Squares.create(110, 100, 80, 80);
          Squares.create(210, 100, 80, 80);
          Squares.create(310, 100, 80, 80);
          Squares.create(410, 100, 80, 80);
          });

          * {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
          user-select: none;
          -moz-user-select: none;
          }
          .plate {
          position: absolute;
          width: 100%;
          height: 100%;
          background: rgb(240, 240, 240);
          cursor: move;
          }
          .plate:hover {
          background: #DEE;
          }
          .edge {
          position: absolute;
          width: 12px;
          height: 12px;
          border-radius: 6px;
          background: rgb(211, 211, 211);
          }
          .edge:hover {
          background: #CDD;
          }
          .edge.lt {
          top: -6px;
          left: -6px;
          cursor: nwse-resize;
          }
          .edge.rt {
          top: -6px;
          right: -6px;
          cursor: nesw-resize;
          }
          .edge.lb {
          bottom: -6px;
          left: -6px;
          cursor: nesw-resize;
          }
          .edge.rb {
          bottom: -6px;
          right: -6px;
          cursor: nwse-resize;
          }








          share|improve this answer











          $endgroup$













            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215388%2fmove-and-resize-square-boxes-with-vanilla-js%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            4












            $begingroup$


            class syntax



            In JS class is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.



            The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.



            Modules



            In modern JS module, modules, modular, etc... refers to code that is import/export able.



            Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class syntax. The same can be achieved with the IIS style and class syntax.



            Objects



            Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))() It provides the best encapsulation mechanism for single instance objects (such as your example)



            A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} } best suited to long lived multi instance objects.



            For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}}; However the encapsulation model is not as strong.



            Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).



            There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.



            Reviewing your code




            • The mouse event provides delta mouse position in MouseEvent.movementX and MouseEvent.movementY so you don't need to function getMovement


            • You should not expose the mouse events for Square. Have Square.create add the events if needed.


            • You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)


            • It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example /* DOM helpers */)


            • Rather than inspecting the DOM element to get its position and size, assign your focusElement to each size-able element as you create it. Use a Map to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside the Square object


            • You update the DOM elements position in two places. Use a single function to update the position (and size) of the element.


            • To reduce code complexity use the MouseEvent.type property to determine the event type and handle all similar mouse events in one function.


            • Mouse events are out of sync with the display refresh. For the cleanest updates you should be using requestAnimationFrame to update elements. requestAnimationFrame ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.



            Example



            Uses same IIF style to encapsulate the object (renamed Squares as it can represent many)



            Removed the 3 exposed onMouse... functions.



            The aim was to reduce source size and improve efficiency.



            Creates a boxDesc for each box mapped to the element, rather than updating the one focusElement object.



            I did not include the use of requestAnimationFrame






               const Squares = (() => {
            /* Privates */
            var addMouseEvents = true, focus;
            const boxMap = new Map();
            const corners = { // bit fields
            none: 0, // 0b0000
            top: 1, // 0b0001
            bottom: 2, // 0b0010
            left: 4, // 0b0100
            right: 8, // 0b1000
            };
            const MIN_SIZE = 50;
            const HANDLE_SIZE = 8; // adds 2 px

            /* DOM helpers */
            const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
            const elStyle = (el, style) => (Object.assign(el.style, style), el);
            const elData = (el, data) => (Object.assign(el.dataset, data), el);
            const append = (par, ...sibs) => {
            for(const sib of sibs) { par.appendChild(sib) }
            return par;
            };

            const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
            function update(box) {
            box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
            box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
            const right = innerWidth - (box.width + HANDLE_SIZE);
            const bot = innerHeight - (box.height + HANDLE_SIZE);
            box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
            box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
            elStyle(box.DOM, {
            transform: `translate(${box.x}px, ${box.y}px)`,
            width: box.width + "px",
            height: box.height + "px",
            });
            return box;
            }
            function move(box, dx, dy) {
            const bot = innerHeight - HANDLE_SIZE;
            const right = innerWidth - HANDLE_SIZE;
            if (box.action === corners.none) {
            box.x += dx;
            box.y += dy;
            } else {
            if ((box.action & corners.bottom) === corners.bottom) {
            box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
            } else {
            if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
            if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
            box.y += dy;
            box.height -= dy;
            }
            if ((box.action & corners.right) === corners.right) {
            box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
            } else {
            if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
            if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
            box.x += dx;
            box.width -= dx;
            }
            }
            update(box);
            }
            function mouseEvent(e) {
            if(e.type === "mousedown") {
            if (e.target.dataset && e.target.dataset.userAction) {
            focus = boxMap.get(e.target.parentNode);
            focus.action = Number(e.target.dataset.userAction);
            }
            }else if(e.type === "mouseup") { focus = undefined }
            else {
            if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
            if (focus) { move(focus, e.movementX, e.movementY) }
            }
            }
            function mouseEvents() {
            document.addEventListener('mousedown', mouseEvent);
            document.addEventListener('mouseup', mouseEvent);
            document.addEventListener('mousemove', mouseEvent);
            addMouseEvents = false;
            }
            return {
            create(x, y, width, height) {
            const box = append(
            elStyle(tag("div"), { position: "absolute" }),
            elData(tag("div", {className : "plate"}), {userAction : corners.none}),
            elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
            elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
            elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
            elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
            );
            boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
            append(document.body, box);
            if (addMouseEvents) { mouseEvents() }
            }
            };

            })();

            document.addEventListener('DOMContentLoaded', function() {
            Squares.create(10, 100, 80, 80);
            Squares.create(110, 100, 80, 80);
            Squares.create(210, 100, 80, 80);
            Squares.create(310, 100, 80, 80);
            Squares.create(410, 100, 80, 80);
            });

            * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none;
            -moz-user-select: none;
            }
            .plate {
            position: absolute;
            width: 100%;
            height: 100%;
            background: rgb(240, 240, 240);
            cursor: move;
            }
            .plate:hover {
            background: #DEE;
            }
            .edge {
            position: absolute;
            width: 12px;
            height: 12px;
            border-radius: 6px;
            background: rgb(211, 211, 211);
            }
            .edge:hover {
            background: #CDD;
            }
            .edge.lt {
            top: -6px;
            left: -6px;
            cursor: nwse-resize;
            }
            .edge.rt {
            top: -6px;
            right: -6px;
            cursor: nesw-resize;
            }
            .edge.lb {
            bottom: -6px;
            left: -6px;
            cursor: nesw-resize;
            }
            .edge.rb {
            bottom: -6px;
            right: -6px;
            cursor: nwse-resize;
            }








            share|improve this answer











            $endgroup$


















              4












              $begingroup$


              class syntax



              In JS class is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.



              The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.



              Modules



              In modern JS module, modules, modular, etc... refers to code that is import/export able.



              Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class syntax. The same can be achieved with the IIS style and class syntax.



              Objects



              Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))() It provides the best encapsulation mechanism for single instance objects (such as your example)



              A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} } best suited to long lived multi instance objects.



              For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}}; However the encapsulation model is not as strong.



              Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).



              There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.



              Reviewing your code




              • The mouse event provides delta mouse position in MouseEvent.movementX and MouseEvent.movementY so you don't need to function getMovement


              • You should not expose the mouse events for Square. Have Square.create add the events if needed.


              • You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)


              • It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example /* DOM helpers */)


              • Rather than inspecting the DOM element to get its position and size, assign your focusElement to each size-able element as you create it. Use a Map to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside the Square object


              • You update the DOM elements position in two places. Use a single function to update the position (and size) of the element.


              • To reduce code complexity use the MouseEvent.type property to determine the event type and handle all similar mouse events in one function.


              • Mouse events are out of sync with the display refresh. For the cleanest updates you should be using requestAnimationFrame to update elements. requestAnimationFrame ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.



              Example



              Uses same IIF style to encapsulate the object (renamed Squares as it can represent many)



              Removed the 3 exposed onMouse... functions.



              The aim was to reduce source size and improve efficiency.



              Creates a boxDesc for each box mapped to the element, rather than updating the one focusElement object.



              I did not include the use of requestAnimationFrame






                 const Squares = (() => {
              /* Privates */
              var addMouseEvents = true, focus;
              const boxMap = new Map();
              const corners = { // bit fields
              none: 0, // 0b0000
              top: 1, // 0b0001
              bottom: 2, // 0b0010
              left: 4, // 0b0100
              right: 8, // 0b1000
              };
              const MIN_SIZE = 50;
              const HANDLE_SIZE = 8; // adds 2 px

              /* DOM helpers */
              const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
              const elStyle = (el, style) => (Object.assign(el.style, style), el);
              const elData = (el, data) => (Object.assign(el.dataset, data), el);
              const append = (par, ...sibs) => {
              for(const sib of sibs) { par.appendChild(sib) }
              return par;
              };

              const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
              function update(box) {
              box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
              box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
              const right = innerWidth - (box.width + HANDLE_SIZE);
              const bot = innerHeight - (box.height + HANDLE_SIZE);
              box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
              box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
              elStyle(box.DOM, {
              transform: `translate(${box.x}px, ${box.y}px)`,
              width: box.width + "px",
              height: box.height + "px",
              });
              return box;
              }
              function move(box, dx, dy) {
              const bot = innerHeight - HANDLE_SIZE;
              const right = innerWidth - HANDLE_SIZE;
              if (box.action === corners.none) {
              box.x += dx;
              box.y += dy;
              } else {
              if ((box.action & corners.bottom) === corners.bottom) {
              box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
              } else {
              if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
              if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
              box.y += dy;
              box.height -= dy;
              }
              if ((box.action & corners.right) === corners.right) {
              box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
              } else {
              if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
              if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
              box.x += dx;
              box.width -= dx;
              }
              }
              update(box);
              }
              function mouseEvent(e) {
              if(e.type === "mousedown") {
              if (e.target.dataset && e.target.dataset.userAction) {
              focus = boxMap.get(e.target.parentNode);
              focus.action = Number(e.target.dataset.userAction);
              }
              }else if(e.type === "mouseup") { focus = undefined }
              else {
              if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
              if (focus) { move(focus, e.movementX, e.movementY) }
              }
              }
              function mouseEvents() {
              document.addEventListener('mousedown', mouseEvent);
              document.addEventListener('mouseup', mouseEvent);
              document.addEventListener('mousemove', mouseEvent);
              addMouseEvents = false;
              }
              return {
              create(x, y, width, height) {
              const box = append(
              elStyle(tag("div"), { position: "absolute" }),
              elData(tag("div", {className : "plate"}), {userAction : corners.none}),
              elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
              elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
              elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
              elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
              );
              boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
              append(document.body, box);
              if (addMouseEvents) { mouseEvents() }
              }
              };

              })();

              document.addEventListener('DOMContentLoaded', function() {
              Squares.create(10, 100, 80, 80);
              Squares.create(110, 100, 80, 80);
              Squares.create(210, 100, 80, 80);
              Squares.create(310, 100, 80, 80);
              Squares.create(410, 100, 80, 80);
              });

              * {
              margin: 0;
              padding: 0;
              box-sizing: border-box;
              user-select: none;
              -moz-user-select: none;
              }
              .plate {
              position: absolute;
              width: 100%;
              height: 100%;
              background: rgb(240, 240, 240);
              cursor: move;
              }
              .plate:hover {
              background: #DEE;
              }
              .edge {
              position: absolute;
              width: 12px;
              height: 12px;
              border-radius: 6px;
              background: rgb(211, 211, 211);
              }
              .edge:hover {
              background: #CDD;
              }
              .edge.lt {
              top: -6px;
              left: -6px;
              cursor: nwse-resize;
              }
              .edge.rt {
              top: -6px;
              right: -6px;
              cursor: nesw-resize;
              }
              .edge.lb {
              bottom: -6px;
              left: -6px;
              cursor: nesw-resize;
              }
              .edge.rb {
              bottom: -6px;
              right: -6px;
              cursor: nwse-resize;
              }








              share|improve this answer











              $endgroup$
















                4












                4








                4





                $begingroup$


                class syntax



                In JS class is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.



                The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.



                Modules



                In modern JS module, modules, modular, etc... refers to code that is import/export able.



                Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class syntax. The same can be achieved with the IIS style and class syntax.



                Objects



                Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))() It provides the best encapsulation mechanism for single instance objects (such as your example)



                A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} } best suited to long lived multi instance objects.



                For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}}; However the encapsulation model is not as strong.



                Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).



                There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.



                Reviewing your code




                • The mouse event provides delta mouse position in MouseEvent.movementX and MouseEvent.movementY so you don't need to function getMovement


                • You should not expose the mouse events for Square. Have Square.create add the events if needed.


                • You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)


                • It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example /* DOM helpers */)


                • Rather than inspecting the DOM element to get its position and size, assign your focusElement to each size-able element as you create it. Use a Map to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside the Square object


                • You update the DOM elements position in two places. Use a single function to update the position (and size) of the element.


                • To reduce code complexity use the MouseEvent.type property to determine the event type and handle all similar mouse events in one function.


                • Mouse events are out of sync with the display refresh. For the cleanest updates you should be using requestAnimationFrame to update elements. requestAnimationFrame ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.



                Example



                Uses same IIF style to encapsulate the object (renamed Squares as it can represent many)



                Removed the 3 exposed onMouse... functions.



                The aim was to reduce source size and improve efficiency.



                Creates a boxDesc for each box mapped to the element, rather than updating the one focusElement object.



                I did not include the use of requestAnimationFrame






                   const Squares = (() => {
                /* Privates */
                var addMouseEvents = true, focus;
                const boxMap = new Map();
                const corners = { // bit fields
                none: 0, // 0b0000
                top: 1, // 0b0001
                bottom: 2, // 0b0010
                left: 4, // 0b0100
                right: 8, // 0b1000
                };
                const MIN_SIZE = 50;
                const HANDLE_SIZE = 8; // adds 2 px

                /* DOM helpers */
                const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
                const elStyle = (el, style) => (Object.assign(el.style, style), el);
                const elData = (el, data) => (Object.assign(el.dataset, data), el);
                const append = (par, ...sibs) => {
                for(const sib of sibs) { par.appendChild(sib) }
                return par;
                };

                const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
                function update(box) {
                box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
                box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
                const right = innerWidth - (box.width + HANDLE_SIZE);
                const bot = innerHeight - (box.height + HANDLE_SIZE);
                box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
                box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
                elStyle(box.DOM, {
                transform: `translate(${box.x}px, ${box.y}px)`,
                width: box.width + "px",
                height: box.height + "px",
                });
                return box;
                }
                function move(box, dx, dy) {
                const bot = innerHeight - HANDLE_SIZE;
                const right = innerWidth - HANDLE_SIZE;
                if (box.action === corners.none) {
                box.x += dx;
                box.y += dy;
                } else {
                if ((box.action & corners.bottom) === corners.bottom) {
                box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
                } else {
                if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
                if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
                box.y += dy;
                box.height -= dy;
                }
                if ((box.action & corners.right) === corners.right) {
                box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
                } else {
                if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
                if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
                box.x += dx;
                box.width -= dx;
                }
                }
                update(box);
                }
                function mouseEvent(e) {
                if(e.type === "mousedown") {
                if (e.target.dataset && e.target.dataset.userAction) {
                focus = boxMap.get(e.target.parentNode);
                focus.action = Number(e.target.dataset.userAction);
                }
                }else if(e.type === "mouseup") { focus = undefined }
                else {
                if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
                if (focus) { move(focus, e.movementX, e.movementY) }
                }
                }
                function mouseEvents() {
                document.addEventListener('mousedown', mouseEvent);
                document.addEventListener('mouseup', mouseEvent);
                document.addEventListener('mousemove', mouseEvent);
                addMouseEvents = false;
                }
                return {
                create(x, y, width, height) {
                const box = append(
                elStyle(tag("div"), { position: "absolute" }),
                elData(tag("div", {className : "plate"}), {userAction : corners.none}),
                elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
                elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
                elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
                elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
                );
                boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
                append(document.body, box);
                if (addMouseEvents) { mouseEvents() }
                }
                };

                })();

                document.addEventListener('DOMContentLoaded', function() {
                Squares.create(10, 100, 80, 80);
                Squares.create(110, 100, 80, 80);
                Squares.create(210, 100, 80, 80);
                Squares.create(310, 100, 80, 80);
                Squares.create(410, 100, 80, 80);
                });

                * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                user-select: none;
                -moz-user-select: none;
                }
                .plate {
                position: absolute;
                width: 100%;
                height: 100%;
                background: rgb(240, 240, 240);
                cursor: move;
                }
                .plate:hover {
                background: #DEE;
                }
                .edge {
                position: absolute;
                width: 12px;
                height: 12px;
                border-radius: 6px;
                background: rgb(211, 211, 211);
                }
                .edge:hover {
                background: #CDD;
                }
                .edge.lt {
                top: -6px;
                left: -6px;
                cursor: nwse-resize;
                }
                .edge.rt {
                top: -6px;
                right: -6px;
                cursor: nesw-resize;
                }
                .edge.lb {
                bottom: -6px;
                left: -6px;
                cursor: nesw-resize;
                }
                .edge.rb {
                bottom: -6px;
                right: -6px;
                cursor: nwse-resize;
                }








                share|improve this answer











                $endgroup$




                class syntax



                In JS class is a syntax for creating objects. It is not an independent distinct entity. Class creates an object adding a constructor function and assigning functions and (optionally) properties to the associated prototype. It does not provide any features not available using standard syntax.



                The 'class` syntax does suffer from one serious flaw (that in my book makes it unusable). It lacks a mechanism to create private functions and properties. Objects created via the class syntax can not adequately encapsulate an objects state, an essential requirement of OO design.



                Modules



                In modern JS module, modules, modular, etc... refers to code that is import/export able.



                Modules provide a local and isolated scope (state) that does provide some encapsulation for single instance objects created with the class syntax. The same can be achieved with the IIS style and class syntax.



                Objects



                Your code is a IIF, or IIFE meaning Immediately Invoked Function Expression const obj=(() => ({foo: "bar", log() {console.log(this.foo)}}))() It provides the best encapsulation mechanism for single instance objects (such as your example)



                A variation is an object factory function obj() { return {foo: "bar", log() {console.log(this.foo)}} } best suited to long lived multi instance objects.



                For performance and many (100 to 1000+) instances of short lived objects, the prototype model is best suited. function Obj() { this.foo = "bar" }; Obj.prototype = { log() {console.log(this.foo)}}; However the encapsulation model is not as strong.



                Modern JS engines cache compiled code, thus you can define the prototype inside the function with only a minor memory penalty (prototyped properties and functions each need a reference per object) This lets you use closure to encapsulate state and still provide prototypal inheritance. However I would argue that in JS polymorphisum is the preferred method of extension for JS objects (though not everyone would agree).



                There are many more ways to define and instanciate objects. Which you use is defined by how the object is to be used, and what your preferred style is.



                Reviewing your code




                • The mouse event provides delta mouse position in MouseEvent.movementX and MouseEvent.movementY so you don't need to function getMovement


                • You should not expose the mouse events for Square. Have Square.create add the events if needed.


                • You can simplify the resize and move logic by treating the corners as part of a side (top, bottom, left, and right) then you move only a side, left or right, top or bottom. Using a bitfield to define corners you can then specify which sides to move for each corner. (see example)


                • It is best not to add markup to the page. Use the DOM API to manipulate the DOM. I find the API somewhat awkward so use some helper functions to improve readability and reduce code noise. (see example /* DOM helpers */)


                • Rather than inspecting the DOM element to get its position and size, assign your focusElement to each size-able element as you create it. Use a Map to make the association. You can then just get that object using the element as key on mouse down (see example) Note that I assume your example is self contained and that there is no manipulation of relevant elements outside the Square object


                • You update the DOM elements position in two places. Use a single function to update the position (and size) of the element.


                • To reduce code complexity use the MouseEvent.type property to determine the event type and handle all similar mouse events in one function.


                • Mouse events are out of sync with the display refresh. For the cleanest updates you should be using requestAnimationFrame to update elements. requestAnimationFrame ensures that backbuffers associated with elements that are rendered from within the callback are only presented during the displays vertical sync.



                Example



                Uses same IIF style to encapsulate the object (renamed Squares as it can represent many)



                Removed the 3 exposed onMouse... functions.



                The aim was to reduce source size and improve efficiency.



                Creates a boxDesc for each box mapped to the element, rather than updating the one focusElement object.



                I did not include the use of requestAnimationFrame






                   const Squares = (() => {
                /* Privates */
                var addMouseEvents = true, focus;
                const boxMap = new Map();
                const corners = { // bit fields
                none: 0, // 0b0000
                top: 1, // 0b0001
                bottom: 2, // 0b0010
                left: 4, // 0b0100
                right: 8, // 0b1000
                };
                const MIN_SIZE = 50;
                const HANDLE_SIZE = 8; // adds 2 px

                /* DOM helpers */
                const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
                const elStyle = (el, style) => (Object.assign(el.style, style), el);
                const elData = (el, data) => (Object.assign(el.dataset, data), el);
                const append = (par, ...sibs) => {
                for(const sib of sibs) { par.appendChild(sib) }
                return par;
                };

                const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
                function update(box) {
                box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
                box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
                const right = innerWidth - (box.width + HANDLE_SIZE);
                const bot = innerHeight - (box.height + HANDLE_SIZE);
                box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
                box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
                elStyle(box.DOM, {
                transform: `translate(${box.x}px, ${box.y}px)`,
                width: box.width + "px",
                height: box.height + "px",
                });
                return box;
                }
                function move(box, dx, dy) {
                const bot = innerHeight - HANDLE_SIZE;
                const right = innerWidth - HANDLE_SIZE;
                if (box.action === corners.none) {
                box.x += dx;
                box.y += dy;
                } else {
                if ((box.action & corners.bottom) === corners.bottom) {
                box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
                } else {
                if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
                if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
                box.y += dy;
                box.height -= dy;
                }
                if ((box.action & corners.right) === corners.right) {
                box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
                } else {
                if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
                if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
                box.x += dx;
                box.width -= dx;
                }
                }
                update(box);
                }
                function mouseEvent(e) {
                if(e.type === "mousedown") {
                if (e.target.dataset && e.target.dataset.userAction) {
                focus = boxMap.get(e.target.parentNode);
                focus.action = Number(e.target.dataset.userAction);
                }
                }else if(e.type === "mouseup") { focus = undefined }
                else {
                if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
                if (focus) { move(focus, e.movementX, e.movementY) }
                }
                }
                function mouseEvents() {
                document.addEventListener('mousedown', mouseEvent);
                document.addEventListener('mouseup', mouseEvent);
                document.addEventListener('mousemove', mouseEvent);
                addMouseEvents = false;
                }
                return {
                create(x, y, width, height) {
                const box = append(
                elStyle(tag("div"), { position: "absolute" }),
                elData(tag("div", {className : "plate"}), {userAction : corners.none}),
                elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
                elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
                elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
                elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
                );
                boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
                append(document.body, box);
                if (addMouseEvents) { mouseEvents() }
                }
                };

                })();

                document.addEventListener('DOMContentLoaded', function() {
                Squares.create(10, 100, 80, 80);
                Squares.create(110, 100, 80, 80);
                Squares.create(210, 100, 80, 80);
                Squares.create(310, 100, 80, 80);
                Squares.create(410, 100, 80, 80);
                });

                * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                user-select: none;
                -moz-user-select: none;
                }
                .plate {
                position: absolute;
                width: 100%;
                height: 100%;
                background: rgb(240, 240, 240);
                cursor: move;
                }
                .plate:hover {
                background: #DEE;
                }
                .edge {
                position: absolute;
                width: 12px;
                height: 12px;
                border-radius: 6px;
                background: rgb(211, 211, 211);
                }
                .edge:hover {
                background: #CDD;
                }
                .edge.lt {
                top: -6px;
                left: -6px;
                cursor: nwse-resize;
                }
                .edge.rt {
                top: -6px;
                right: -6px;
                cursor: nesw-resize;
                }
                .edge.lb {
                bottom: -6px;
                left: -6px;
                cursor: nesw-resize;
                }
                .edge.rb {
                bottom: -6px;
                right: -6px;
                cursor: nwse-resize;
                }








                   const Squares = (() => {
                /* Privates */
                var addMouseEvents = true, focus;
                const boxMap = new Map();
                const corners = { // bit fields
                none: 0, // 0b0000
                top: 1, // 0b0001
                bottom: 2, // 0b0010
                left: 4, // 0b0100
                right: 8, // 0b1000
                };
                const MIN_SIZE = 50;
                const HANDLE_SIZE = 8; // adds 2 px

                /* DOM helpers */
                const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
                const elStyle = (el, style) => (Object.assign(el.style, style), el);
                const elData = (el, data) => (Object.assign(el.dataset, data), el);
                const append = (par, ...sibs) => {
                for(const sib of sibs) { par.appendChild(sib) }
                return par;
                };

                const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
                function update(box) {
                box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
                box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
                const right = innerWidth - (box.width + HANDLE_SIZE);
                const bot = innerHeight - (box.height + HANDLE_SIZE);
                box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
                box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
                elStyle(box.DOM, {
                transform: `translate(${box.x}px, ${box.y}px)`,
                width: box.width + "px",
                height: box.height + "px",
                });
                return box;
                }
                function move(box, dx, dy) {
                const bot = innerHeight - HANDLE_SIZE;
                const right = innerWidth - HANDLE_SIZE;
                if (box.action === corners.none) {
                box.x += dx;
                box.y += dy;
                } else {
                if ((box.action & corners.bottom) === corners.bottom) {
                box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
                } else {
                if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
                if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
                box.y += dy;
                box.height -= dy;
                }
                if ((box.action & corners.right) === corners.right) {
                box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
                } else {
                if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
                if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
                box.x += dx;
                box.width -= dx;
                }
                }
                update(box);
                }
                function mouseEvent(e) {
                if(e.type === "mousedown") {
                if (e.target.dataset && e.target.dataset.userAction) {
                focus = boxMap.get(e.target.parentNode);
                focus.action = Number(e.target.dataset.userAction);
                }
                }else if(e.type === "mouseup") { focus = undefined }
                else {
                if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
                if (focus) { move(focus, e.movementX, e.movementY) }
                }
                }
                function mouseEvents() {
                document.addEventListener('mousedown', mouseEvent);
                document.addEventListener('mouseup', mouseEvent);
                document.addEventListener('mousemove', mouseEvent);
                addMouseEvents = false;
                }
                return {
                create(x, y, width, height) {
                const box = append(
                elStyle(tag("div"), { position: "absolute" }),
                elData(tag("div", {className : "plate"}), {userAction : corners.none}),
                elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
                elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
                elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
                elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
                );
                boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
                append(document.body, box);
                if (addMouseEvents) { mouseEvents() }
                }
                };

                })();

                document.addEventListener('DOMContentLoaded', function() {
                Squares.create(10, 100, 80, 80);
                Squares.create(110, 100, 80, 80);
                Squares.create(210, 100, 80, 80);
                Squares.create(310, 100, 80, 80);
                Squares.create(410, 100, 80, 80);
                });

                * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                user-select: none;
                -moz-user-select: none;
                }
                .plate {
                position: absolute;
                width: 100%;
                height: 100%;
                background: rgb(240, 240, 240);
                cursor: move;
                }
                .plate:hover {
                background: #DEE;
                }
                .edge {
                position: absolute;
                width: 12px;
                height: 12px;
                border-radius: 6px;
                background: rgb(211, 211, 211);
                }
                .edge:hover {
                background: #CDD;
                }
                .edge.lt {
                top: -6px;
                left: -6px;
                cursor: nwse-resize;
                }
                .edge.rt {
                top: -6px;
                right: -6px;
                cursor: nesw-resize;
                }
                .edge.lb {
                bottom: -6px;
                left: -6px;
                cursor: nesw-resize;
                }
                .edge.rb {
                bottom: -6px;
                right: -6px;
                cursor: nwse-resize;
                }





                   const Squares = (() => {
                /* Privates */
                var addMouseEvents = true, focus;
                const boxMap = new Map();
                const corners = { // bit fields
                none: 0, // 0b0000
                top: 1, // 0b0001
                bottom: 2, // 0b0010
                left: 4, // 0b0100
                right: 8, // 0b1000
                };
                const MIN_SIZE = 50;
                const HANDLE_SIZE = 8; // adds 2 px

                /* DOM helpers */
                const tag = (name, props ={}) => Object.assign(document.createElement(name), props);
                const elStyle = (el, style) => (Object.assign(el.style, style), el);
                const elData = (el, data) => (Object.assign(el.dataset, data), el);
                const append = (par, ...sibs) => {
                for(const sib of sibs) { par.appendChild(sib) }
                return par;
                };

                const boxDesc = (DOM, width, height, x, y) => ({ DOM, width, height, x, y });
                function update(box) {
                box.width = box.width < MIN_SIZE ? MIN_SIZE : box.width;
                box.height = box.height < MIN_SIZE ? MIN_SIZE : box.height;
                const right = innerWidth - (box.width + HANDLE_SIZE);
                const bot = innerHeight - (box.height + HANDLE_SIZE);
                box.x = box.x < HANDLE_SIZE ? HANDLE_SIZE : box.x > right ? right : box.x;
                box.y = box.y < HANDLE_SIZE ? HANDLE_SIZE : box.y > bot ? bot : box.y;
                elStyle(box.DOM, {
                transform: `translate(${box.x}px, ${box.y}px)`,
                width: box.width + "px",
                height: box.height + "px",
                });
                return box;
                }
                function move(box, dx, dy) {
                const bot = innerHeight - HANDLE_SIZE;
                const right = innerWidth - HANDLE_SIZE;
                if (box.action === corners.none) {
                box.x += dx;
                box.y += dy;
                } else {
                if ((box.action & corners.bottom) === corners.bottom) {
                box.height = box.y + box.height + dy > bot ? bot - box.y : box.height + dy;
                } else {
                if (box.height - dy < MIN_SIZE) { dy = box.height - MIN_SIZE }
                if (box.y + dy < HANDLE_SIZE) { dy = HANDLE_SIZE - box.y }
                box.y += dy;
                box.height -= dy;
                }
                if ((box.action & corners.right) === corners.right) {
                box.width = box.x + box.width + dx > right ? right - box.x : box.width + dx;
                } else {
                if (box.width - dx < MIN_SIZE) { dx = box.width - MIN_SIZE }
                if (box.x + dx < HANDLE_SIZE) { dx = HANDLE_SIZE - box.x }
                box.x += dx;
                box.width -= dx;
                }
                }
                update(box);
                }
                function mouseEvent(e) {
                if(e.type === "mousedown") {
                if (e.target.dataset && e.target.dataset.userAction) {
                focus = boxMap.get(e.target.parentNode);
                focus.action = Number(e.target.dataset.userAction);
                }
                }else if(e.type === "mouseup") { focus = undefined }
                else {
                if (e.buttons === 0) { focus = undefined } // to stop sticky button in snippet
                if (focus) { move(focus, e.movementX, e.movementY) }
                }
                }
                function mouseEvents() {
                document.addEventListener('mousedown', mouseEvent);
                document.addEventListener('mouseup', mouseEvent);
                document.addEventListener('mousemove', mouseEvent);
                addMouseEvents = false;
                }
                return {
                create(x, y, width, height) {
                const box = append(
                elStyle(tag("div"), { position: "absolute" }),
                elData(tag("div", {className : "plate"}), {userAction : corners.none}),
                elData(tag("span", {className : "edge lt"}), {userAction : corners.top + corners.left}),
                elData(tag("span", {className : "edge rt"}), {userAction : corners.top + corners.right}),
                elData(tag("span", {className : "edge lb"}), {userAction : corners.bottom + corners.left}),
                elData(tag("span", {className : "edge rb"}), {userAction : corners.bottom + corners.right})
                );
                boxMap.set(box, update(boxDesc(box, width, height, x, y))); // update() sizes and positions elements
                append(document.body, box);
                if (addMouseEvents) { mouseEvents() }
                }
                };

                })();

                document.addEventListener('DOMContentLoaded', function() {
                Squares.create(10, 100, 80, 80);
                Squares.create(110, 100, 80, 80);
                Squares.create(210, 100, 80, 80);
                Squares.create(310, 100, 80, 80);
                Squares.create(410, 100, 80, 80);
                });

                * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                user-select: none;
                -moz-user-select: none;
                }
                .plate {
                position: absolute;
                width: 100%;
                height: 100%;
                background: rgb(240, 240, 240);
                cursor: move;
                }
                .plate:hover {
                background: #DEE;
                }
                .edge {
                position: absolute;
                width: 12px;
                height: 12px;
                border-radius: 6px;
                background: rgb(211, 211, 211);
                }
                .edge:hover {
                background: #CDD;
                }
                .edge.lt {
                top: -6px;
                left: -6px;
                cursor: nwse-resize;
                }
                .edge.rt {
                top: -6px;
                right: -6px;
                cursor: nesw-resize;
                }
                .edge.lb {
                bottom: -6px;
                left: -6px;
                cursor: nesw-resize;
                }
                .edge.rb {
                bottom: -6px;
                right: -6px;
                cursor: nwse-resize;
                }






                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited 2 days ago

























                answered Mar 14 at 16:27









                Blindman67Blindman67

                8,5041521




                8,5041521






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215388%2fmove-and-resize-square-boxes-with-vanilla-js%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    is 'sed' thread safeWhat should someone know about using Python scripts in the shell?Nexenta bash script uses...

                    How do i solve the “ No module named 'mlxtend' ” issue on Jupyter?

                    Pilgersdorf Inhaltsverzeichnis Geografie | Geschichte | Bevölkerungsentwicklung | Politik | Kultur...