Tabla de contenido
Una vez más aquí. Mientras termino de recuperar el aliento (por 2ª vez) de esto del COVID, hoy vengo a este blog a dejar algunos códigos con los que se puede crear un editor de imágenes utilizando un poco de HTML, de CSS y Vanilla JavaScript. Con este editor de imágenes podremos seleccionar nuestras propias imágenes, y editar algunos parámetros básicos de esta, utilizando unos controles simples.
Con el programa resultante de estos códigos, los usuarios pueden aplicar algunos filtros sobre su imagen. Estos filtros son; escala de grises, saturación, inversión de colores o ajustar el brillo de la imagen. Además, los usuarios también podrán rotar o voltear las imágenes y guardar sus imágenes una vez terminada la edición.
Si se está familiarizado con el lenguaje JavaScript, los códigos y la lógica de este proyecto no deberían resultar difíciles de entender. Si quieres ver este proyecto antes de meterte en materia, este programa se puede ver funcionando en la siguiente dirección.
Editor de imágenes básico
El proyecto para crear un editor de imágenes utilizando HTML, CSS y JavaScript, requiere tan solo de cuatro archivos. Los tres primeros se deben crear como archivos: HTML, CSS y JavaScript. Una vez que creemos estos archivos, simplemente será necesario pegar los códigos que vamos a ver a continuación dentro del archivo que corresponda. El cuarto archivo necesario serás una imagen necesaria para utilizarla como imagen por defecto. Esta se debe incluir en el archivo HTML que vamos a ver a continuación.
Código HTML
Lo primero que vamos a hacer es crear un archivo HTML con el nombre index.html. Dentro de este vamos a pegar el siguiente código en su archivo HTML. Quiero recalcar que será necesario buscar una imagen por defecto, que en este ejemplo se llama «image-placeholder.jpeg» y colocar la ruta al archivo en el lugar que se indica comentado en el siguiente código.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Editor de imágenes con JavaScript</title> <link rel="stylesheet" href="style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" /> <link rel="stylesheet" href="https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css"> </head> <body> <div class="container disable"> <h2>Editor de imágenes sencillo</h2> <div class="wrapper"> <div class="editor-panel"> <div class="filter"> <label class="title">Filtros</label> <div class="options"> <button id="brightness" class="active">Brillo</button> <button id="saturation">Saturación</button> <button id="inversion">Inversión</button> <button id="grayscale">Escala de grises</button> </div> <div class="slider"> <div class="filter-info"> <p class="name">Brillo</p> <p class="value">100%</p> </div> <input type="range" value="100" min="0" max="200"> </div> </div> <div class="rotate"> <label class="title">Rotar & Voltear</label> <div class="options"> <button id="horizontal"><i class='bx bx-reflect-vertical'></i></button> <button id="vertical"><i class='bx bx-reflect-horizontal'></i></button> <button id="left"><i class="fa-solid fa-rotate-left"></i></button> <button id="right"><i class="fa-solid fa-rotate-right"></i></button> </div> </div> </div> <div class="preview-img"> <!-- AQUÍ SE DEBE COLOCAR LA IMAGEN POR DEFECTO, QUE CADA CUAL TENDRÁ QUE BUSCARSE LA SUYA PROPIA --> <img src="image-placeholder.jpeg" alt="imagen previa"> </div> </div> <div class="controls"> <button class="reset-filter">Restablecer Filtros</button> <div class="row"> <input type="file" class="file-input" accept="image/*" hidden> <button class="choose-img">Seleccionar Imagen</button> <button class="save-img">Guardar Imagen</button> </div> </div> </div> <script src="script.js"></script> </body> </html>
Código CSS
En segundo archivo a crear será el CSS. A este le vamos a dar el nombre de style.css y a pegar el siguiente código en su interior. Además de las CSS habituales, en las reglas CSS que vamos a ver, aquí se incluyen las reglas necesarias para que se vea correctamente también en dispositivos móviles.
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Comic Sans MS', Helvetica, Arial, sans-serif; } body { display: flex; padding: 10px; min-height: 100vh; align-items: center; justify-content: center; background: #DEFBD0; } .container { width: 850px; padding: 30px 35px 35px; background: #fff; border-radius: 10px; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); border: 1px solid #000; } .container.disable .editor-panel, .container.disable .controls .reset-filter, .container.disable .controls .save-img { opacity: 0.5; pointer-events: none; } .container h2 { margin-top: -8px; font-size: 26px; text-align: center; font-weight: bold; } .container .wrapper { display: flex; margin: 20px 0; min-height: 335px; } .wrapper .editor-panel { padding: 15px 20px; width: 280px; border-radius: 5px; border: 1px solid #ccc; } .editor-panel .title { display: block; font-size: 16px; margin-bottom: 12px; } .editor-panel .options, .controls { display: flex; flex-wrap: wrap; justify-content: space-between; } .editor-panel button { outline: none; height: 40px; font-size: 14px; color: #6C757D; background: #fff; border-radius: 3px; margin-bottom: 8px; border: 1px solid #aaa; } .editor-panel .filter button { width: calc(100% / 2 - 4px); } .editor-panel button:hover { background: #f5f5f5; } .filter button.active { color: #fff; border-color: #5372F0; background: #5372F0; } .filter .slider { margin-top: 12px; } .filter .slider .filter-info { display: flex; color: #464646; font-size: 14px; justify-content: space-between; } .filter .slider input { width: 100%; height: 5px; accent-color: #5372F0; } .editor-panel .rotate { margin-top: 17px; } .editor-panel .rotate button { display: flex; align-items: center; justify-content: center; width: calc(100% / 4 - 3px); } .rotate .options button:nth-child(3), .rotate .options button:nth-child(4) { font-size: 18px; } .rotate .options button:active { color: #fff; background: #5372F0; border-color: #5372F0; } .wrapper .preview-img { flex-grow: 1; display: flex; overflow: hidden; margin-left: 20px; border-radius: 5px; align-items: center; justify-content: center; } .preview-img img { max-width: 490px; max-height: 335px; width: 100%; height: 100%; object-fit: contain; } .controls button { padding: 11px 20px; font-size: 14px; border-radius: 3px; outline: none; color: #fff; cursor: pointer; background: none; transition: all 0.3s ease; text-transform: uppercase; } .controls .reset-filter { color: #6C757D; border: 1px solid #6C757D; } .controls .reset-filter:hover { color: #fff; background: #6C757D; } .controls .choose-img { background: #6C757D; border: 1px solid #6C757D; } .controls .choose-img:hover { color:#000 } .controls .save-img { margin-left: 5px; background: #9a9dac; border: 1px solid #5372F0; } .controls .save-img:hover { border: 1px solid #ff2727; background: #5372F0; color: #000; } @media screen and (max-width: 760px) { .container { padding: 25px; } .container .wrapper { flex-wrap: wrap-reverse; } .wrapper .editor-panel { width: 100%; } .wrapper .preview-img { width: 100%; margin: 0 0 15px; } } @media screen and (max-width: 500px) { .controls button { width: 100%; margin-bottom: 10px; } .controls .row { width: 100%; } .controls .row .save-img { margin-left: 0px; } }
Código JavaScript
Por último, vamos a crear un archivo JavaScript con el nombre script.js y a pegar el siguiente código en su interior.
const fileInput = document.querySelector(".file-input"), filterOptions = document.querySelectorAll(".filter button"), filterName = document.querySelector(".filter-info .name"), filterValue = document.querySelector(".filter-info .value"), filterSlider = document.querySelector(".slider input"), rotateOptions = document.querySelectorAll(".rotate button"), previewImg = document.querySelector(".preview-img img"), resetFilterBtn = document.querySelector(".reset-filter"), chooseImgBtn = document.querySelector(".choose-img"), saveImgBtn = document.querySelector(".save-img"); let brightness = "100", saturation = "100", inversion = "0", grayscale = "0"; let rotate = 0, flipHorizontal = 1, flipVertical = 1; // Cargar Imágenes const loadImage = () => { let file = fileInput.files[0]; if(!file) return; previewImg.src = URL.createObjectURL(file); previewImg.addEventListener("load", () => { resetFilterBtn.click(); document.querySelector(".container").classList.remove("disable"); }); } // Aplicar Filtros const applyFilter = () => { previewImg.style.transform = `rotate(${rotate}deg) scale(${flipHorizontal}, ${flipVertical})`; previewImg.style.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`; } filterOptions.forEach(option => { option.addEventListener("click", () => { document.querySelector(".active").classList.remove("active"); option.classList.add("active"); filterName.innerText = option.innerText; if(option.id === "brightness") { filterSlider.max = "200"; filterSlider.value = brightness; filterValue.innerText = `${brightness}%`; } else if(option.id === "saturation") { filterSlider.max = "200"; filterSlider.value = saturation; filterValue.innerText = `${saturation}%` } else if(option.id === "inversion") { filterSlider.max = "100"; filterSlider.value = inversion; filterValue.innerText = `${inversion}%`; } else { filterSlider.max = "100"; filterSlider.value = grayscale; filterValue.innerText = `${grayscale}%`; } }); }); // Actualizar Filtros const updateFilter = () => { filterValue.innerText = `${filterSlider.value}%`; const selectedFilter = document.querySelector(".filter .active"); if(selectedFilter.id === "brightness") { brightness = filterSlider.value; } else if(selectedFilter.id === "saturation") { saturation = filterSlider.value; } else if(selectedFilter.id === "inversion") { inversion = filterSlider.value; } else { grayscale = filterSlider.value; } applyFilter(); } rotateOptions.forEach(option => { option.addEventListener("click", () => { if(option.id === "left") { rotate -= 90; } else if(option.id === "right") { rotate += 90; } else if(option.id === "horizontal") { flipHorizontal = flipHorizontal === 1 ? -1 : 1; } else { flipVertical = flipVertical === 1 ? -1 : 1; } applyFilter(); }); }); // Resetear Filtros const resetFilter = () => { brightness = "100"; saturation = "100"; inversion = "0"; grayscale = "0"; rotate = 0; flipHorizontal = 1; flipVertical = 1; filterOptions[0].click(); applyFilter(); } // Guardar imágenes const saveImage = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); canvas.width = previewImg.naturalWidth; canvas.height = previewImg.naturalHeight; ctx.filter = `brightness(${brightness}%) saturate(${saturation}%) invert(${inversion}%) grayscale(${grayscale}%)`; ctx.translate(canvas.width / 2, canvas.height / 2); if(rotate !== 0) { ctx.rotate(rotate * Math.PI / 180); } ctx.scale(flipHorizontal, flipVertical); ctx.drawImage(previewImg, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); const link = document.createElement("a"); link.download = "image.jpg"; link.href = canvas.toDataURL(); link.click(); } filterSlider.addEventListener("input", updateFilter); resetFilterBtn.addEventListener("click", resetFilter); saveImgBtn.addEventListener("click", saveImage); fileInput.addEventListener("change", loadImage); chooseImgBtn.addEventListener("click", () => fileInput.click());
Los cuatro archivos necesarios para crear este proyecto, por defecto se han guardado en dentro de la misma carpeta, por lo que si alguien necesita crear otra estructura, debe tener esto en cuenta.
12 Comentarios
Buenos Dias existe una forma que el código que esta agregando y de verdad muchas por compartir. Puede agregar opciones como zoom, texto, lapiz para raya la imagen
Hola. Te diré que si, que se pueden añadir más efectos con JavaScript, pero hay que desarrollar las funciones necesarias en el archivo .js. En estos momentos no tengo mucho tiempo, pero cuando tenga un rato libre publicaré una actualización del código. Salu2 y gracias por comentar.
Buen dia, acondicione parte del codigo en un proyecto que tengo y no me funciona. me puede colaborar y decirme cuanto costaria ?
Gracias
Hola. ¿Cuando costaría el qué? Yo no cobro nada por los códigos que publico en esta web. Si no te funcionó parte del código, igual es que no lo has acondicionado correctamente. Exactamente ¿qué es lo que ha fallado? Salu2
Buen dia. El codigo funciona perfecto. Lo que hice fue; que en vez de buscar o seleccionar una imagen fue tomar una imagen del servidor directamente mostrarla y realizar por ejemplo los giros. No me funciono. Me indicaria como hacerlo ?
Gracias. Un abrazo.
Hola. Según entendí, tú lo que que quieres es utilizar una URL de una imagen tomada de un servidor,¿no?. Bueno, si es esto, debes modificar algunas cosas.
Se puede añadir un campo de entrada de texto donde el usuario ingresar la URL de la imagen. Luego, se puede modificar la función loadImage() para cargar la imagen desde la URL que añadimos en lugar de un archivo local.
En el HTML, habría que añadir un nuevo campo de entrada de texto para que el usuario pueda escribir la URL de la imagen:
div class=»input-group»>
input type=»text» class=»image-url» placeholder=»Escribe la URL de la imagen» >
button class=»load-image-btn»>Load Image
/div>
(a este código le faltan los < del input, del div y del button, por que si la pongo la página muestra el botón en lugar del código)
En el JavaScript, modificamos la función loadImage() para que cargue la imagen desde la URL añadida en lugar de un archivo local:
const loadImage = () => {
const imageUrl = document.querySelector(".image-url").value;
if (!imageUrl) return;
previewImg.src = imageUrl;
previewImg.addEventListener("load", () => {
resetFilterBtn.click();
document.querySelector(".container").classList.remove("disable");
});
};
En el JavaScript, también habría que añadir un controlador de eventos para el botón de carga de imagen, que llamará a la función loadImage():
const loadImageBtn = document.querySelector(".load-image-btn");
loadImageBtn.addEventListener("click", loadImage);
Con estas modificaciones, el usuario podrá ingresar una URL de imagen en el campo de entrada de texto y cargar la imagen haciendo clic en el botón «Load Image». Luego, se aplicarán los mismos filtros y transformaciones que en el código original.
Todo esto lo he escrito ahora según me vino, no lo he podido probar. Si esto no te funciona, avísame y cuando tenga un rato te hago un ejemplo completo y me aseguro de que funcione correctamente. Salu2.
Perfecto. Le confieso que tenia la idea de que esa es la manera pero no supe como implementarlo. Me pondre a trabajar y le contare.
Muchas gracias.
Como te digo, no pude probarlo. Pero la idea viene siendo esa. Espero que lo consigas. Si necesitas algo más, no dudes en dejar otro comentario. Salu2 y gracias por tus comentarios.
Buen dia … definitivamente no pude lograrlo … como le comente por el momento tome el codigo que inicialmente necesito, en esta oportunidad el de girar la imagen. Lo hice asi …
En el php …
El contenedor de la imagen … traida de la db :
IMAGEN ACTUAL ….
<img class="imagen-endb" src="» width=»210″ height=»250″>
y el contenedor para rotar la imagen :
En el mismo php …
const imagen_endb= document.querySelector(«.imagen-endb scr»).value,
rotateOptions = document.querySelectorAll(«.rotate button»);
imagen_actual.src = imagen_endb;
rotateOptions.forEach(option => {
option.addEventListener(«click», () => {
if(option.id === «left») {
rotate -= 90;
} else if(option.id === «right») {
rotate += 90;
}
});
});
Que estoy haciendo mal o que me falta ?
Gracias por su colaboracion y su tiempo.
Hola. Como poner aquí el código puede dar problemas, ya que mucho se interpreta, te envié un correo electrónico con el código que pedías. Espero que te sirva. Salu2.
muchas gracias
Muchas gracias a ti por el comentario. Salu2.