JS recortar imagen antes de subir archivo al servidor sin jquery
Cropper.js para recortar imagen con js
El ejemplo original lo encontré en Youtube pero estaba hecho con jquery y bootstrap 4.
Decidí pasarlo a boot5 porque se desacopla de jquery lo cual me parece una muy buena opción ya que soy más de aprovechar las bondades del "vanilla js".
Lo primero que necesitamos es descargarnos al menos estos dos archivos del repositorio ofcial de cropper.js. Puedes elegir los min si prefieres.
-
cropper.js
Este trae el objeto Cropper
-
cropper.css
Evita que la imagen salga en medio de la pantalla fuera del canvas para el recorte
Yo lo tengo así:
El código
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8">
<title>Cropper.js</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.1/dist/umd/popper.min.js" integrity="sha384-SR1sx49pcuLnqZUnnPwx6FCym0wLsk5JZuNx2bPPENzswTNFaQU1RDvt3wT4gWFG" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/js/bootstrap.min.js" integrity="sha384-j0CNLUeiqtyaRmlzUHCPZ+Gy5fQu0dQ6eZ/xAww941Ai1SxSY+0EQqNXNE6DZiVc" crossorigin="anonymous"></script>
<link href="/js/cropper-js/cropper.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="container">
<form class="form">
<div class="mb-3">
<label for="file-upload" class="form-label">Upload Images</label><br/>
<input type="file" id="file-upload" class="image">
</div>
</form>
<!-- en este img se mostrará el archivo despues de haberlo subido-->
<img src="#" id="img-uploaded" style="visibility: hidden;" class="img-fluid" />
<!-- en este span se visualizará la url del archivo-->
<span id="span-uploaded"></span>
</div>
<div class="modal fade" id="div-modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Crop image</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="img-container">
<div class="row">
<div class="col-md-8">
<!-- en este img se visualizará todo el archivo seleccionado-->
<img id="img-original" class="img-fluid">
</div>
<div class="col-md-4">
<!-- en este div se mostrará la zona seleccionada, lo que quedará despues de hacer click en el boton crop-->
<div id="div-preview" class="preview img-fluid"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="btn-crop">Crop</button>
</div>
</div>
</div>
</div>
<style type="text/css">
img {
display: block;
max-width: 100%;
}
.preview {
overflow: hidden;
width: 160px;
height: 160px;
margin: 10px;
border: 1px solid #0B5ED7;
}
</style>
<script src="/js/cropper-js/cropper.js"></script>
<script type="module">
//este código lo pongo más abajo
</script>
</body>
</html>
JS
//$ es una convención de js que indica que esa variable es un elemento html, se puede declarar sin $
//file es input de selección
const $file = document.getElementById("file-upload")
//es elemento img dentro del modal donde se montará la imagen seleccionada
const $image = document.getElementById("img-original")
const $modal = document.getElementById("div-modal")
//si deseamos interactuar con el modal usando los metodos nativos de bootstrap5
//debemos construirlo pasando el elemento. En nuestro caso .show() y .hide()
const objmodal = new bootstrap.Modal($modal, {
//que el modal no interactue con el teclado
keyboard: false
})
//escuchamos el change del input-file
$file.addEventListener("change", function (e) {
const load_image = function (url){
$image.src = url
objmodal.show()
}
const files = e.target.files
if(files && files.length>0) {
const objfile = files[0]
//el objeto file tiene las propiedades: name, size, type, lastmodified, lastmodifiedate
//para poder visualizar el archivo de imagen lo debemos pasar a una url
//el objeto URL está en fase experimental así que si no existe usaria FileReader
if (URL){
//crea una url del estilo: blob:http://localhost:1024/129e832d-2545-471f-8e70-20355d8e33eb
const url = URL.createObjectURL(objfile)
load_image(url)
}
else if (FileReader) {
const reader = new FileReader()
reader.onload = function (e) {
load_image(reader.result)
}
reader.readAsDataURL(objfile)
}
}
})//file.on-change
const $btncrop = document.getElementById("btn-crop")
//configuramos el click del boton crop
$btncrop.addEventListener("click", function (){
//obtenemos la zona seleccionada
const canvas = cropper.getCroppedCanvas()
canvas.toBlob(function (blob){
//el objeto blob (binary larege object) tiene las propiedades: size y type
const reader = new FileReader()
//se pasa el binario base64
reader.readAsDataURL(blob)
reader.onloadend = function (){
const base64data = reader.result
//base64data es un string del tipo: data:image/png;base64,iVBORw0KGgoAAAA....
console.log("base64data", base64data)
//en mi caso estoy trabajando con php en el back pero puede ser cualquier url
const url = "/index.php?f=crop_first&nohome=1"
fetch(url, {
method: "POST",
headers: {
//si la respuesta del servidor no es un json saltará una excepción en js
"Accept": "application/json",
//le indica al servidor que se le enviará un json
"Content-Type": "application/json"
},
body: JSON.stringify({
image: base64data
})
})
.then(response => response.json())
.then(function (result){
$file.value = "" //resetea el elemento input-file (file-upload)
objmodal.hide() //escondo el modal
//result es algo como: {message:"image uploaded successfully.", file:"upload/uuid.png"}
alert(result.message)
//este es el img que está debajo del elemnto input-file
const $img = document.getElementById("img-uploaded")
$img.src = "/"+result.file
$img.style.visibility = "visible"
const $span = document.getElementById("span-uploaded")
$span.innerText=$img.src
})
}//reader.on-loaded
})//canvas.toblob
})//btncrop.on-click
//el objeto cropper que habrá que crearlo y destruirlo.
//Crearlo al mostrar el modal y destruirlo al cerrarlo
let cropper = null
$modal.addEventListener("shown.bs.modal", function (){
console.log("modal.on-show")
//crea el marco de selección sobre el objeto $image
cropper = new Cropper($image, {
//donde se mostrará la parte seleccionada
preview: document.getElementById("div-preview"),
//3: indica que no se podrá seleccionar fuera de los límites
viewMode: 3,
//NaN libre elección, 1 cuadrado, proporción del lado horizontal con respecto al vertical
aspectRatio: 1.5,
})
})//modal.on-shown
$modal.addEventListener("hidden.bs.modal", function (){
console.log("modal.on-hide")
cropper.destroy()
cropper = null
})//modal.on-hidden
Imágenes
Código bonus del backend. PHP
<?php
//cuando el dato se envía como json, este no llega en la variable $_POST
//sino a través de php://input
if($json = file_get_contents("php://input"))
{
//pasamos de json a array
$post = json_decode($json, true);
//en la key image llega un string de este estilo:
// data:image/png;base64,iVBORw0KGgoAAAA....
$parts = explode(";base64,", $post["image"]);
//despues de decodificarlo vuelve a ser "blob" (mirar el codigo js que lo convierte de blob a base64)
//$strblob guardaría algo como
// �PNG IHDR�� IDATx^���gv�U�˹_�42�@�Yaf�I�,{m�Ɩ���8h����H�h5Z�v5��+k�Y�$+L�H+�f
$strblob = base64_decode($parts[1]);
$uuid = uniqid();
$pathfile = "upload/$uuid.png";
file_put_contents($pathfile, $strblob);
echo json_encode([
"message" => "image uploaded successfully.",
"file" => $pathfile
]);
exit;
}
Si deseas ver el código fuente en github lo puedes hacer pinchando aquí
Autor: Eduardo A. F.
Publicado: 09-04-2021 23:26
Actualizado: 09-04-2021 23:34