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 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, 0, 0,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, 0, 0, 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 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:
- shadowRoot: Is the shadowDOM of the component. It contains the styles of the component and its html definition.
- Conditions to apply certain functions to the component.
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(255, 255, 255, .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(0, 0, 0, 0.16) 0px 2px 5px 0px, rgba(0, 0, 0, 0.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: