TheRogerLAB Codes

Create a resizable and draggable component

  March, 2022

Introduction

In this component we will be using UI object component template to create a resizable and draggable component. It is useful for differents scenarios and applications. Actually a variant of this was used on ERD DESIGNER and QUERY DESIGNER:

Step 1: Define UI object template

var component = {

  items: [],

  cmp: function (id) {
    return this.items[id];
  },

  component: function (id) {
    this.config = {
      draggable: true,
      resizable: true,
      constraint: true,
      ondragEl: function (el) { },
      onresizeEl: function (el) { }
    },

      this.settings = {},
      this.slots = {},
      this.matchSettings = function () {
        var keys = [];
        for (var k in this.settings) {
          if (k in this.config)
            this.config[k] = this.settings[k];
        }
      },

      //FUNCTION FOR DRAGGING
      this.dragElement = function (elmnt, cmp) {
        //To be defined on STEP 3 
      },

      //FUNCTION FOR RESIZING
      this.makeResizableDiv = function (div) {
        //To be defined on STEP 4
      },


      this.show = function (parent) {
        this.matchSettings();
        var e = `
        <div class='resizable' id=`+ id + `>  
          <div class='innerContainer'></div>  
          <div class='resizers'>
            <div class='resizer left'></div>
            <div class='resizer right'></div>
            <div class='resizer bottom'></div>
            <div class='resizer top'></div>
            <div class='resizer top-left'></div>    
            <div class='resizer top-right'></div>
            <div class='resizer bottom-left'></div>
            <div class='resizer bottom-right'></div>    
          </div>   
        </div>
      `;

        if (parent == undefined) {
          return e;
        } else {
          var p = document.querySelector(parent);
          p.insertAdjacentHTML('beforeend', e);
        }

        if (this.config.draggable == true)
          this.dragElement(document.getElementById(id), this);
        if (this.config.resizable == true)
          this.makeResizableDiv('#' + id, this);
      }
  },

  create: function (id) {
    this.items[id] = new this.component(id);
    return this.items[id];
  }
};

Set this.config with these properties:

Step 2: Define CSS classes for the component


#root{
 width:100%;
 height:50vh;
 border:1px solid #4286f4;
 position:relative;
 overflow:hidden; 
 background-color:black;
}


/*These belongs to the component*/

.resizable {  
  width: 100px;
  height: 100px;
  position: absolute;  
  cursor:all-scroll;
}
.resizable .resizers{
  width: 100%;
  height: 100%;
  border: 2px solid #4286f4;  
  box-sizing: border-box;
}
.resizable .resizers .resizer{
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: lime; 
  position: absolute;
}
.resizable .resizers .resizer.left {
  left: -5px;
  top: calc(50% - 5px/1);
  cursor: e-resize; /*resizer cursor*/
  display:none;
}
.resizable .resizers .resizer.right {
  right: -5px;
  top: calc(50% - 5px/1);
  cursor: e-resize;
  display:none;
}
.resizable .resizers .resizer.top-left {
  left: -5px;
  top: -5px;
  cursor: nwse-resize; /*resizer cursor*/
  display:none;
}
.resizable .resizers .resizer.top {
  left: calc(50% - 5px/1);
  top: -5px;
  cursor: n-resize; /*resizer cursor*/
  display:none;
}
.resizable .resizers .resizer.top-right {
  right: -5px;
  top: -5px;
  cursor: nesw-resize;
  display:none;
}
.resizable .resizers .resizer.bottom-left {
  left: -5px;
  bottom: -5px;
  cursor: nesw-resize;
  display:none;
}
.resizable .resizers .resizer.bottom {
  left: calc(50% - 5px/1);
  bottom: -5px;
  cursor: n-resize;
  display:none;
}
.resizable .resizers .resizer.bottom-right {
  right: -5px;
  bottom: -5px;
  cursor: nwse-resize;
  display:none;
}
.resizable .resizers .resizer.draggable {
  right: 10px;
  top: -5px;
  cursor: nesw-resize;
  display:none;
}

Step 3: Define this.dragElement

    //FUNCTION FOR DRAGGING
    this.dragElement = function(elmnt,cmp) {
      var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
      elmnt.onmousedown = dragMouseDown;
      
      var funcAtDrag = this.config.ondragEl;
     
      
      function dragMouseDown(e) {
        e = e || window.event;
        e.preventDefault();
        // get the mouse cursor position at startup:
        var rect = elmnt.parentNode.getBoundingClientRect();        
        var padX = e.clientX - rect.left;
        var padY = e.clientY - rect.top;       
        pos3 = padX;
        pos4 = padY;
        document.onmouseup = closeDragElement;
        // call a function whenever the cursor moves:
        document.onmousemove = elementDrag;       
      }

      function elementDrag(e) {
       e = e || window.event;
       e.preventDefault();
       // calculate the new cursor position:
       var rect = elmnt.parentNode.getBoundingClientRect();        
       var padX = e.clientX - rect.left;
       var padY = e.clientY - rect.top;
        
       pos1 = pos3 - padX;
       pos2 = pos4 - padY;
       pos3 = padX;
       pos4 = padY;        
        
       
       if(cmp.config.constraint == true){
         var wPad = getComputedStyle(elmnt.parentNodenull).getPropertyValue('width');
         var wPad = parseFloat(wPad.substring(0, wPad.length - 2));
         
         var hPad = getComputedStyle(elmnt.parentNodenull).getPropertyValue('height');
         var hPad = parseFloat(hPad.substring(0, hPad.length - 2));
         
         var xElDer = (elmnt.offsetLeft - pos1) + elmnt.offsetWidth;
         var yElDer = (elmnt.offsetTop - pos2) + elmnt.offsetHeight;     
         
         
         if(elmnt.offsetTop - pos2 > 0  && yElDer < hPad){
           elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
         }
         if(elmnt.offsetLeft - pos1 > 0 && xElDer < wPad){
           elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
         }         
         
       }else{
          elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
          elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";         
       }
       
       //If a function was defined to be fired when dragging
       funcAtDrag(elmnt);
       
      }

      function closeDragElement() {     
        document.onmouseup = null;
        document.onmousemove = null;
      }
    },

Inside we will see several functions to call when onmousedown, onmousemove and onmouseup

Step 4: Define this.makeResizableDiv

    //FUNCTION FOR RESIZING
    this.makeResizableDiv = function(div) {
      const element = document.querySelector(div);
      const resizers = document.querySelectorAll(div + ' .resizer')
      const minimum_size = 40;
      
      let original_width = 0;
      let original_height = 0;
      let original_x = 0;
      let original_y = 0;
      let original_mouse_x = 0;
      let original_mouse_y = 0;
      var funcAtResize = this.config.onresizeEl;
      

      element.addEventListener('mousedown'function(e) {
        e.preventDefault();
        e.stopPropagation();
        
        //disabling others
        var res = document.querySelectorAll('[class~="resizer"]');
        for(var i=0;i<res.length;i++){    
          res[i].style.display = 'none';   
        } 
        var res = this.querySelector('.resizers');
          for(var i=0;i<res.children.length;i++){        
            res.children[i].style.display = 'block';          
          } 
      });
      for (let i = 0;i < resizers.length; i++) {
        const currentResizer = resizers[i];
        currentResizer.addEventListener('mousedown'function(e) {
          e.preventDefault();
          e.stopPropagation();

          original_width = parseFloat(getComputedStyle(element, null).getPropertyValue('width').replace('px'''));
          original_height = parseFloat(getComputedStyle(element, null).getPropertyValue('height').replace('px'''));
          
          original_x = element.offsetLeft ;
          original_y = element.offsetTop;         
          
          
          var rect = element.parentNode.getBoundingClientRect();        
          var padX = e.pageX - rect.left;
          var padY = e.pageY - rect.top;
          
          original_mouse_x = padX;
          original_mouse_y = padY;
          window.addEventListener('mousemove', resize)
          window.addEventListener('mouseup', stopResize)
        });

        function resize(e) {
          e.preventDefault();
          e.stopPropagation();
          var rect = element.parentNode.getBoundingClientRect();        
          var padX = e.pageX - rect.left;
          var padY = e.pageY - rect.top;
          
          var wPad = getComputedStyle(element.parentNodenull).getPropertyValue('width');
         var wPad = parseFloat(wPad.substring(0, wPad.length - 2));
         
         var hPad = getComputedStyle(element.parentNodenull).getPropertyValue('height');
         var hPad = parseFloat(hPad.substring(0, hPad.length - 2));
          
         var xHijo = getComputedStyle(element, null).getPropertyValue('left');
         var xHijo = parseFloat(xHijo.substring(0, xHijo.length - 2));
          
         var yHijo = getComputedStyle(element, null).getPropertyValue('top');
         var yHijo = parseFloat(yHijo.substring(0, yHijo.length - 2));
          
          
          var yElAct = original_y + (padY - original_mouse_y);
          var xElAct = original_x + (padX - original_mouse_x);
          
          var xElDer = xElAct + element.offsetWidth;
          var yElDer = yElAct + element.offsetHeight
          
          if (currentResizer.classList.contains('top')) {        
            const height = original_height - (padY - original_mouse_y);        
            if (height > minimum_size && yElAct > 0) {
              element.style.height = height + 'px';
              element.style.top = original_y + (padY - original_mouse_y) + 'px';
            }
          }else
          if (currentResizer.classList.contains('right')) {        
            const width = original_width + (padX - original_mouse_x);
            var lim = wPad - xHijo; 
            if (width > minimum_size && width < lim ) {
              element.style.width = width + 'px';
            }        
          }else
          if (currentResizer.classList.contains('bottom')) {         
            const height = original_height + (padY - original_mouse_y); 
            var lim = hPad - yHijo;            
            if (height > minimum_size && height < lim ) {
              element.style.height = height + 'px'
            }      
          }else
          if (currentResizer.classList.contains('left')){
            const width = original_width - (padX - original_mouse_x);        
            if (width > minimum_size && xElAct > 0) {
              element.style.width = width + 'px'
              element.style.left = original_x + (padX - original_mouse_x) + 'px';
            }        
          }else
          if (currentResizer.classList.contains('bottom-right')) {
            const width = original_width + (padX - original_mouse_x);
            const height = original_height + (padY - original_mouse_y);
            var lim = hPad - yHijo;  
            var limr = wPad - xHijo;
            if (width > minimum_size && width < limr) {
              element.style.width = width + 'px'
            }
            if (height > minimum_size && height < lim ) {
              element.style.height = height + 'px'
            }
          }else
          if (currentResizer.classList.contains('bottom-left')) {
            const height = original_height + (padY - original_mouse_y);
            const width = original_width - (padX - original_mouse_x);
            var lim = hPad - yHijo; 
            if (height > minimum_size && height < lim ) {
              element.style.height = height + 'px'
            }
            if (width > minimum_size && xElAct > 0) {
              element.style.width = width + 'px'
              element.style.left = original_x + (padX - original_mouse_x) + 'px'
            }
          }else
          if (currentResizer.classList.contains('top-right')) {
            const width = original_width + (padX - original_mouse_x);
            const height = original_height - (padY - original_mouse_y);
            var lim = wPad - xHijo; 
            if (width > minimum_size && width < lim ) {
              element.style.width = width + 'px'
            }
            if (height > minimum_size && yElAct > 0) {
              element.style.height = height + 'px'
              element.style.top = original_y + (padY - original_mouse_y) + 'px'
            }
          }else
          if (currentResizer.classList.contains('top-left')){
            const width = original_width - (padX - original_mouse_x);
            const height = original_height - (padY - original_mouse_y);
            
            if (width > minimum_size && xElAct > 0) {
              element.style.width = width + 'px'
              element.style.left = original_x + (padX - original_mouse_x) + 'px'
            }
            if (height > minimum_size && yElAct > 0 ) {
              element.style.height = height + 'px'
              element.style.top = original_y + (padY - original_mouse_y) + 'px'
            }
          }
          
          //If a function was defined to be fired when resizing
          funcAtResize(element);
        }

        function stopResize(e) {
          e.preventDefault();
          e.stopPropagation();
          window.removeEventListener('mousemove', resize)
        }
      }
    },

Inside we will see several functions to call when user clicks on a corner dot and resize.

Step 5: Create a HTML element to be used as parent

<div id="root"></div>

Step 6: Example of usage

  var el = component.create("myElement");
  el.settings = {
    ondragEl: function(el){ 
      console.log("I am dragging");      
    },
    onresizeEl: function(el){    
      console.log("I am resizing");        
    }
  };
  el.show('#root');

Check a working demo for this code:

SEE DEMO RATE THIS ARTICLE
TheRogerLAB Codes
Powered by TheRogerLAB Sandbox

info@therogerlab.com