Calculating absolute position in %

Posted on

Problem

I’m making a website for a friend (just for fun) where we have to move a lot of svg images in absolute positions. To make it easier I wrote some code to calculate the % position of each element so we can do it faster.

I’m wondering what you think about it and what can I do to improve it. And also any improvements to my style of coding are welcome.

<html>

<head>
    <!-- Just some style to differentiate the object -->
    <style>
        .draggable{
            width: 10px;
            height: 10px;
            background: red;
        }
    </style>
</head>

<body>
    <!-- The item that we will be moving around -->
    <div class="draggable"></div>
    <script>

    class Mover{
        constructor(htmlObject,parent, position){
            // Store the object to move
            this.htmlObject = htmlObject;
            // The parent of the object
            this.parent = parent;
            // The type of position : 'relative'/'absolute'
            this.position = position;
            // Modify the elements and start the listeners
            this.init();
        }

        init(){
            // Make the item draggable
            this.htmlObject.draggable = 'true';
            // Set its position type to the user input
            this.htmlObject.style.position = this.position;
            // Modify its zIndex to make sure that you can always see where it is.
            this.htmlObject.style.zIndex = '9999';
            // Add some border just make it easier to find the item.
            this.htmlObject.style.border = '1px solid black';
            // Add the listeners for the drop and drag events to the parent
            this.parent.ondrop = (event)=>{ this.drop(event) };
            this.parent.ondragover = (event)=>{ this.allowDrop(event) };
        }

        drop(e){
            e.preventDefault();
            let pos = this.calculatePos(e.x, e.y);
            // Set the object position equal to the event x and y pos in %
            this.htmlObject.style.top = pos.h+'%';
            this.htmlObject.style.left = pos.w+'%';
        }

        allowDrop(e){
            // Allow to drop.
            e.preventDefault();
        }

        calculatePos(w,h){
            // Get the parent Width and Height
            let mx = this.parent.offsetWidth;
            let mh = this.parent.offsetHeight;
            // -- Just testing stuff --
            // let mx = innerWidth;
            // let mh = innerHeight;
            // ------------------------
            // Calculate the % based on the parent size.
            return {w : (w/mx) * 100, h : (h/mh) * 100}
        }

    }

    // Initialize the movable item.
    const draggableElement = new Mover(document.querySelector('.dragable'),document.querySelector('body'),'absolute')   
    </script>
</body>

Solution

About Constructor

It is considered a bad practice for a constructor to have side effects for multiple reasons. You can browse numerous resources online on this subject. Here are a few whys:

  • The reader/maintainer of the code does not expect a constructor to have any side effects;
  • Such constructor is hard or impossible to test;
  • It violates a whole bunch of principles, including Principle of least astonishment, SRP.

So, yeah instead of

constructor(htmlObject,parent, position){
  // ...
  this.init();
}

you should rather create an instance and invoke the init() on it; like this

const draggableElement = new Mover(
  document.querySelector('.dragable'),
  document.querySelector('body'),
  'absolute',
);
draggableElement.init();

If you are not planning to add the function that disables “moving” of an element, you may even not need a local variable holding the reference to a Mover:

new Mover(
  document.querySelector('.dragable'),
  document.querySelector('body'),
  'absolute',
)
.init();

Naming Things

Naming is important. Method init() is not informative. You may want to rename it to something like enableElementDragging(). A symmetric counter part is now too easy too name – disableElementDragging().


Nano variable names are evil. What is e? An error? An exception? An event? To figure that out I need to look at a place where the function is being invoked. That’s mental burden.

Same applies to pos, w, and h… If you are in VS Code or any other smart editor, it’s a matter of hitting F2 and renaming it.

Names mx and mh are particularly unfortunate since they are meant to be symmetric to/consistent with w and h. Thus, mx should be mw! Also, I could not decipher what the m prefix means in both of these variables.

Just spell things out: position, width, height, parentWidth, parentHeight, calculatePosition().

Leave a Reply

Your email address will not be published. Required fields are marked *