TheRogerLAB Codes

Image cropper web component

  June, 2021

Introduction

This component consists in an image cropper useful for crop user's profile photos or other uses. Let's start by creating the structure of our web component. Create a file cropperLab.js:

class cropperLab extends HTMLElement {

  //Here will be all the functionality.

}
window.customElements.define('cropper-lab', cropperLab);

Step 1: Add a constructor and variables

class cropperLab extends HTMLElement {
  constructor () {
    super();
    this.oldSrc = '';
  }
}
window.customElements.define('cropper-lab', cropperLab);

In this case we create one variable:

Step 2: Adding getters

class cropperLab extends HTMLElement {
  //Step 1

  get width() {
    return this.hasAttribute('width');
  }
  get height() {
    return this.hasAttribute('height');
  }
  get rounded() {
    return this.hasAttribute('rounded');
  }
}
window.customElements.define('cropper-lab', cropperLab);

Step 3: Add a function to process the selected image

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  loadPic(e){
    var reader = new FileReader();
    this.resetAll();
    reader.readAsDataURL(e.target.files[0]);
    reader.cmp = this;
    reader.onload = function(event) {
      var shdRoot = event.target.cmp.shadowRoot;
      shdRoot.querySelector(".resize-image").setAttribute('src',event.target.result);
      event.target.cmp.oldSrc = event.target.result;
      shdRoot.querySelector(".resize-image").cmp = shdRoot;
      shdRoot.querySelector(".resize-image").onload = function(e){
        var shdRoot = e.target.cmp;
        shdRoot.querySelector('.slidecontainer').style.display = 'block';
        shdRoot.querySelector('.crop').style.display = 'initial';
        var widthTotal = shdRoot.querySelector(".resize-image").offsetWidth;
        shdRoot.querySelector(".resize-container").style.width = widthTotal + 'px';
        shdRoot.querySelector(".resize-image").style.width = widthTotal + 'px';
        shdRoot.querySelector("#myRange").max = widthTotal + widthTotal;
        shdRoot.querySelector("#myRange").value = widthTotal;
        shdRoot.querySelector("#myRange").min = widthTotal - widthTotal;
      }
    }
  } 

window.customElements.define('cropper-lab', cropperLab);

Step 4: Add a function to drag the loaded image and change its position

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  dragElement(elmnt) {
    var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
    elmnt.onmousedown = dragMouseDown;
    elmnt.ontouchstart = dragMouseDown;

    function dragMouseDown(e) {
      e.preventDefault();
      // get the mouse cursor position at startup:
      pos3 = e.clientX || e.targetTouches[0].pageX;
      pos4 = e.clientY || e.targetTouches[0].pageY;
      document.onmouseup = closeDragElement;
      document.ontouchend = closeDragElement;
      // call a function whenever the cursor moves:
      document.onmousemove = elementDrag;
      document.ontouchmove = elementDrag;
    }
    function elementDrag(e) {
      e = e || window.event;
      // calculate the new cursor position:
      pos1 = pos3 - (e.clientX || e.targetTouches[0].pageX);
      pos2 = pos4 - (e.clientY || e.targetTouches[0].pageY);
      pos3 = (e.clientX || e.targetTouches[0].pageX);
      pos4 = (e.clientY || e.targetTouches[0].pageY);
      // set the element's new position:
      elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
      elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
    }
    function closeDragElement() {
      // stop moving when mouse button is released:
      document.onmouseup = '';
      document.ontouchend = '';
      document.onmousemove = '';
      document.ontouchmove = '';
    }
  }
}
window.customElements.define('cropper-lab', cropperLab);

Step 5: Add a function to crop the image

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  //Step 4

  crop(){
    this.shadowRoot.querySelector('.crop').style.display = 'none';
    this.shadowRoot.querySelector('.reset').style.display = 'initial';
    this.shadowRoot.querySelector('.save').style.display = 'initial';
    this.shadowRoot.querySelector('.slidecontainer').style.display = 'none';
    var image = this.shadowRoot.querySelector('.resize-image');

    var resize_canvas = document.createElement('canvas');
    resize_canvas.width = image.offsetWidth;
    resize_canvas.height = image.offsetHeight;
    resize_canvas.getContext('2d').drawImage(image, 00,image.offsetWidth, image.offsetHeight);

    image.setAttribute('src',resize_canvas.toDataURL("image/png",1));

    var imageContainer = this.shadowRoot.querySelector('.resize-container');
    var centerContainer = this.shadowRoot.querySelector('.center');
    var left = centerContainer.offsetLeft - imageContainer.offsetLeft;
    var top = centerContainer.offsetTop - imageContainer.offsetTop;
    var width = centerContainer.offsetWidth;
    var height = centerContainer.offsetHeight;
    var newTop = centerContainer.offsetTop;
    var newLeft = centerContainer.offsetLeft;

    var crop_canvas = document.createElement('canvas'); 
    crop_canvas.width = width;
    crop_canvas.height = height;
    crop_canvas.getContext('2d').drawImage(resize_canvas, left, top, width, height, 00, width, height);

    var imageC = this.shadowRoot.querySelector('.imageCropped');
    imageC.src = crop_canvas.toDataURL("image/png",1);
    this.shadowRoot.querySelector('.resize-image').setAttribute('src','');
  }
}
window.customElements.define('cropper-lab', cropperLab);

Step 6: Add functions to slide, get cropped image, resetAll and reset

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  //Step 4

  //Step 5

  slide(w){
    this.shadowRoot.querySelector(".resize-container").style.width = (w)+'px';
    this.shadowRoot.querySelector(".resize-image").style.width = (w)+'px';
  }
  getCropped(){
    return this.shadowRoot.querySelector(".imageCropped").getAttribute('src');
  }
  resetAll(){
    this.shadowRoot.querySelector(".reset").style.display = 'none';
    this.shadowRoot.querySelector(".save").style.display = 'none';
    this.shadowRoot.querySelector(".crop").style.display = 'none';
    this.shadowRoot.querySelector(".slidecontainer").style.display = 'none';
    this.shadowRoot.querySelector(".resize-container").removeAttribute('style');
    this.shadowRoot.querySelector(".resize-image").setAttribute('src','');
    this.shadowRoot.querySelector(".imageCropped").setAttribute('src','');
    this.shadowRoot.querySelector(".resize-image").style.width = '100%';
    this.shadowRoot.querySelector("#myRange").max = 10;
    this.shadowRoot.querySelector("#myRange").value = 5;
    this.shadowRoot.querySelector("#myRange").min = 0;
  }
  reset(){
    this.resetAll();
    this.shadowRoot.querySelector(".resize-image").setAttribute('src',this.oldSrc);
  }
}
window.customElements.define('cropper-lab', cropperLab);

Step 7: Add a function connectedCallback

It fires when the component is created

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  //Step 4

  //Step 5

  //Step 6

  connectedCallback () {

    //Here will be the functionality.

  }
}
window.customElements.define('cropper-lab', cropperLab);

Inside connectedCallback function we will create two things:

Filling the connectedCallback function:

class cropperLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  //Step 4

  //Step 5

  connectedCallback () {
    let shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
      <style>
        .slidecontainer {
          width: 100%;
          display:none;
          z-index: 1;
          position: relative;
          margin-top:8px;
        }
        .slider {
          -webkit-appearance: none;
          width: 100%;
          height: 15px;
          border-radius: 5px;
          background: #d3d3d3;
          outline: none;
          opacity: 0.9;
          -webkit-transition: .2s;
          transition: opacity .2s;
        }
        .slider:hover {
          opacity: 1;
        }
        .slider::-webkit-slider-thumb {
          -webkit-appearance: none;
          appearance: none;
          width: 25px;
          height: 25px;
          border-radius: 50%;
          background: #2196F3;
          cursor: pointer;
        }
        .slider::-moz-range-thumb {
          width: 25px;
          height: 25px;
          border-radius: 50%;
          background: #2196F3;
          cursor: pointer;
          border:none;
        }
        .resize-container {
          position: relative;
          display: inline-block;
          cursor: move;
          margin: 0 auto;
        }
        .resize-container img {
          display: block;
        }
        .resize-container:hover img,
        .resize-container:active img {
          outline: 2px dashed gray;
        }
        .parent{
          width:99%;
          height:99%;
          overflow: hidden;
          position:absolute;
          top:0px;
          left:0px;
        }
        .center{
          position: absolute;
          width: 150px;
          height: 150px;
          top: calc(50% - 150px/2);
          left: calc(50% - 150px/2);
          z-index: 2;
          background: rgba(255255255.3);
          border: 2px solid #cecece;
        }
        .imageCropped{
          position: relative;
          left: -2px;
          top: -2px;
        }
        .uploader{
          z-index: 1;
          position: relative;
          display:none;
        }
        .lb_uploader{
          z-index: 1;
          position: relative;
          cursor:pointer;
        }
        .crop, .reset, .save{
          display:none;
        }
        .btn{
          border: none;
          display: inline-block;
          padding: 8px 16px;
          vertical-align: middle;
          overflow: hidden;
          text-decoration: none;
          color: inherit;
          background-color: inherit;
          text-align: center;
          cursor: pointer;
          white-space: nowrap;
          color: white;
          background-color: #2196F3;
          border-radius: 32px;
          margin-top:5px;
          margin-left:5px;
          z-index:1;
          position: relative;
          font-size: 15px;
          font-family: 'Changa', sans-serif;
          box-shadow: rgba(0000.16) 0px 2px 5px 0px, rgba(0000.12) 0px 2px 10px 0px;
        }
      </style>
      <div>
        <label class='lb_uploader' for='uploader'>
          <slot name='select'>
             <div class='btn'><slot name='selectText'>Select</slot></div>
          </slot>
        </label>
        <label class='reset'>
          <slot name='reset'>
            <div class='btn'><slot name='resetText'>Reset</slot></div>
          </slot>
        </label>
        <label class='crop'>
          <slot name='crop'>
            <div class='btn'><slot name='cropText'>Crop</slot></div>
          </slot>
        </label>
        <label class='save'>
          <slot name='save'>
            <div class='btn'><slot name='saveText'>Save</slot></div>
          </slot>
        </label>
        <input type="file" class="uploader" id='uploader'/>
        <div class="slidecontainer">
          <input type="range" class="slider" id="myRange">
        </div>
        <div class='parent'>
          <div class="resize-container">
            <img class="resize-image" src="" style='width:100%'>
          </div>
          <div class='center'><img class="imageCropped"></div>
        </div>
      </div>
      `;
      shadowRoot.querySelector('.uploader').addEventListener('change', e => {
        this.loadPic(e);
      });
      shadowRoot.querySelector('#myRange').addEventListener('input', e => {
        this.slide(e.target.value);
      });
      shadowRoot.querySelector('.crop').addEventListener('click', e => {
        this.crop();
      });
      shadowRoot.querySelector('.reset').addEventListener('click', e => {
        this.reset();
      });
      if(this.width){ 
        shadowRoot.querySelector('.center').style.width = this.getAttribute('width');
        shadowRoot.querySelector('.center').style.left = 'calc(50% - '+this.getAttribute('width')+'/2)';
      }
      if(this.height){
        shadowRoot.querySelector('.center').style.height = this.getAttribute('height');
        shadowRoot.querySelector('.center').style.top = 'calc(50% - '+this.getAttribute('height')+'/2)';
      }
      if(this.rounded){
        shadowRoot.querySelector('.center').style.borderRadius = '200px';
        shadowRoot.querySelector('.imageCropped').style.borderRadius = '200px';
      }
      this.dragElement(shadowRoot.querySelector(".resize-container"));
  }
}
window.customElements.define('cropper-lab', cropperLab);

To send cropped image to the server

1) Set your component HTML

<cropper-lab id='mycrop' width='150px' height='150px'>
  <div slot='selectText'>Select image</div>
  <div slot='cropText'>Crop image</div>
  <div slot='resetText'>Reset</div>
  <div slot='saveText' onclick='saveImage();'>Save it</div>
</cropper-lab>

2) Define function saveImage JS

  function saveImage(){
    var img = document.querySelector('#mycrop').getCropped();
    //sending to the server
    var fd = new FormData();
    fd.append("image", img);
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      //everything is ok
      if (xhr.readyState==4 && xhr.status==200){
        var response = JSON.parse(xhr.responseText);
        if(response.success == true){
          //if it was correct clear all or redirect to a page
          document.querySelector('#mycrop').resetAll();
        }
      }
    }
    xhr.open("POST""upload.php");
    xhr.send(fd);
  }

3) Get the data PHP

  //if image was sent
  if($_POST['image']!=''){
    $nameString = '';
    $target_dir = "upload/";
    $image = str_replace('data:image/png;base64,''', $_POST['image']);
    $image = str_replace(' ''+', $image);
    $data = base64_decode($image); 
    $unique = uniqid();
    $file = $target_dir.$unique.'.png';
    $success = file_put_contents($file, $data);
    if($success != false)
      echo '{"success": true}';
    else
      echo '{"success": false}';
  } else {
    echo '{"success": false}';
  }

Check a working demo for this component:

SEE DEMO RATE THIS ARTICLE
4.5
27
TheRogerLAB Codes
Powered by TheRogerLAB Sandbox

info@therogerlab.com