Пишем простой редактор аватарок на JavaScript
2026-02-21 16:44 Diff

#Руководства

  • 18 июн 2020
  • 0

В этой статье мы расскажем, как написать простой редактор для аватарок, чтобы облегчить жизнь пользователям.

 vlada_maestro / shutterstock

Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.

Если на сайте можно загрузить аватарку, то хорошо бы добавить минимальный редактор, который позволит обрезать изображение. Реализовать это относительно просто, и все стороны остаются в выигрыше: пользователю удобнее делать всё на сайте, не уходя с него.

Редактор берёт уже сохранённую фотографию и выводит её на страницу. Далее пользователь выбирает нужный ему фрагмент с помощью мыши или вводит значения в форме.

После сохранения сайт отправляет запрос PHP-скрипту, который обрезает фотографию и помещает её в отдельный файл. В итоге пользователь видит ссылку с результатом работы редактора.

Здесь мы покажем только важные фрагменты приложения, а полный исходный код вы найдёте в репозитории на GitHub.

Для начала нужно сверстать саму страницу в HTML:

<div class="wrapper"> <main class="main"> <div class="main__content"> <div class="avatar"> <img id="image" class="image"> <canvas id="canvas" class="canvas"> Your browser does not support JS or HTML5! </canvas> </div> <p> <input type="number" name="widthBox" id="widthBox" value="100" min="100" title="Width"> × <input type="number" name="heightBox" id="heightBox" value="100" min="100" title="Height"> </p> <p> <label>Top: <input type="number" name="topBox" id="topBox" value="0" min="0" title="Top"> </label><br><br> <label>Left: <input type="number" name="leftBox" id="leftBox" value="0" min="0" title="Left"></label> </p> <p> <button class="button" id="saveBtn">Save</button> </p> <p> <a href="images/newphoto.jpg" target="_blank" class="a a_hidden" id="newImg">Open new photo</a> </p> </div> </main> </div>

Сразу же добавляем стили:

body, html { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; font-size: 16px; font-family: helvetica, arial; background: #f6f6f6; color: #111; } .wrapper { width: 100%; height: 100%; display: table; } .main { display: table-cell; vertical-align: middle; } .main__content { padding: 5px; text-align: center; } .image { display: block; margin: 15px auto; border: 5px dashed #ddd; background: #fff; border-radius: 15px; max-width: 60%; max-height: 600px; } .canvas { display: block; max-width: 60%; max-height: 600px; position: absolute; border: 0; border-radius: 15px; cursor: move; } .input { display: inline-block; vertical-align: middle; margin: 5px; } .button { display: inline-block; vertical-align: middle; margin: 5px; background: #6694f6; border: 0; border-radius: 15px; padding: 10px 25px; color: #fff; cursor: pointer; box-shadow: 0 0 10px rgba(0,0,0,0.5); font-size: 16px; } .button:hover { background: #8acef1; } .button_disabled { background: #555; cursor: not-allowed; } .a, .a:visited { color: #6694f6; } .a:hover { text-decoration: none; } .a_hidden { opacity: 0; }

Получается достаточно минималистичная форма:

Изображение выводится с помощью тега img, а поверх него находится элемент canvas. На данном этапе позиция холста не указана — она прописывается скриптом и зависит от позиции изображения.

Дальше берём какое-нибудь изображение, чтобы его редактировать. Я воспользовался сайтом thispersondoesnotexist.com, который генерирует фотографию несуществующего человека.

Только мелкие детали выдают, что это не настоящая фотография

Для начала получим из DOM нужные нам объекты:

//Холст и его контекст const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); //Поля ввода const widthBox = document.getElementById("widthBox"); const heightBox = document.getElementById("heightBox"); const topBox = document.getElementById("topBox"); const leftBox = document.getElementById("leftBox"); //Кнопка сохранения const saveBtn = document.getElementById("saveBtn"); //Ссылка на новое изображение const newImg = document.getElementById("newImg");

Выполним инициализацию — загрузим изображение, наложим на него холст, заполним поля и так далее:

const image = document.getElementById("image"); image.addEventListener("load", function () { Init(); }); image.src = "images/photo.jpg"; window.addEventListener("resize", function () { Init(); }); function Init() { canvas.width = image.width; canvas.height = image.height; canvas.setAttribute("style", "top: " + (image.offsetTop + 5) + "px; left: " + (image.offsetLeft + 5) + "px;"); leftBox.setAttribute("max", image.width - 100); topBox.setAttribute("max", image.height - 100); widthBox.setAttribute("max", image.width); heightBox.setAttribute("max", image.height); DrawSelection(); //Эта функция будет рассмотрена чуть позже }

Чтобы проверить, работает ли функция нормально, я уже добавил рамку на холст:

Давайте рассмотрим код, который отвечает за выделение:

var selection = { mDown: false, x: 0, y: 0, top: 50, left: 50, width: 100, height: 100 }; function DrawSelection() { ctx.fillStyle = "rgba(0, 0, 0, 0.7)"; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.clearRect(selection.left, selection.top, selection.width, selection.height); ctx.strokeStyle = "#fff"; ctx.beginPath(); ctx.moveTo(selection.left, 0); ctx.lineTo(selection.left, canvas.height); ctx.moveTo(selection.left + selection.width, 0); ctx.lineTo(selection.left + selection.width, canvas.height); ctx.moveTo(0, selection.top); ctx.lineTo(canvas.width, selection.top); ctx.moveTo(0, selection.top + selection.height); ctx.lineTo(canvas.width, selection.top + selection.height); ctx.stroke(); }

Объект selection хранит данные о том, зажата ли кнопка мыши, какие координаты курсора на холсте, разрешение и позиция выделенной области. Функция DrawSelection () отрисовывает рамку:

Остаётся написать код, который двигает эту область:

function MouseDown(e) { //Говорим, что кнопка была зажата selection.mDown = true; } function MouseMove(e) { if(selection.mDown) //Проверяем, зажата ли кнопка { //Получаем координаты курсора на холсте selection.x = e.clientX - canvas.offsetLeft; selection.y = e.clientY - canvas.offsetTop; //Меняем позицию выделенного фрагмента selection.left = selection.x - selection.width / 2; selection.top = selection.y - selection.height / 2; //Проверяем, не выходит ли фрагмент за границы холста CheckSelection(); //Ввод новых значений в поля, отрисовка рамки Update(); } } function MouseUp(e) { //Отпускаем кнопку selection.mDown = false; }

Функции MouseDown (), MouseMove () и MouseUp () вызываются при действиях мыши над холстом: зажатии кнопки мыши, передвижении курсора и отпускании кнопки соответственно. Как работают CheckSelection () и Update (), можно увидеть в полном коде в репозитории.

Давайте посмотрим, как это работает:

Заключительный этап — пишем функцию для отправки запроса PHP-скрипту, который будет обрезать изображение:

function Save() { var xhr = new XMLHttpRequest(); var params = "width=" + widthBox.value + "&height=" + heightBox.value + "&top=" + topBox.value + "&left=" + leftBox.value + "&cw=" + canvas.width + "&ch=" + canvas.height; xhr.open("GET", "editor.php?" + params, true); xhr.onload = function () { if (xhr.status != 200) { console.log(xhr.status + ": " + xhr.statusText); } else { console.log(xhr.responseText); if (xhr.responseText == "ok") { newImg.className = "a"; } else { alert("Ошибка!"); } } }; xhr.send(); }

Эта функция вызывается при нажатии на кнопку Save. Если изображение успешно обрезано, то появится ссылка на него — для этого с объекта newImg будет удалён класс a_hidden.

PHP-скрипт получает разрешение и позицию нового фрагмента, а также разрешение самого холста. Затем он загружает старое изображение, вырезает из него нужный нам фрагмент и сохраняет в файл newphoto.jpg.

<?php $filename = "images/photo.jpg"; if(isset($_GET['width']) && isset($_GET['height']) && isset($_GET['left']) && isset($_GET['top']) && isset($_GET['cw']) && isset($_GET['ch'])) { $width = $_GET['width']; $height = $_GET['height']; $top = $_GET['top']; $left = $_GET['left']; $cw = $_GET['cw']; $ch = $_GET['ch']; //Получаем размеры старого изображения list($oldWidth, $oldHeight) = getimagesize($filename); //Вычисляем новые размеры и позицию фрагмента //Для этого сначала разделим значение, например, ширину на ширину холста - и получим новую ширину в процентах //Затем этот процент нужно умножить на ширину оригинальной фотографии - так мы получим новое значение $newWidth = ($width / $cw) * $oldWidth; $newHeight = ($height / $ch) * $oldHeight; $newLeft = ($left / $cw) * $oldWidth; $newTop = ($top / $ch) * $oldHeight; //Создаём изображение с новыми размерами $output = imagecreatetruecolor($newWidth, $newHeight); $source = imagecreatefromjpeg($filename); imagecopyresized($output, $source, 0, 0, $newLeft, $newTop, $newWidth, $newHeight, $newWidth, $newHeight); //Сохранение нового изображения $result = imagejpeg($output, "images/newphoto.jpg"); if($result) { echo "ok"; } else { echo "string"; "fail"; } } else { echo "Error!"; } ?>

В итоге мы получаем новое изображение, на котором будет выделенный фрагмент. Его разрешение зависит от размера оригинала, а не от размера холста.

Вы можете сделать так, чтобы разрешения совпадали, — для этого измените функцию imagecopyresized ():

imagecopyresized($output, $source, 0, 0, $newLeft, $newTop, $newWidth, $newHeight, $width, $height);

Вы можете добавить возможность менять разрешение выделенной области с помощью мыши — для этого в функции MouseMove () проверим, в какой области находится курсор, когда пользователь двигает мышь. Если курсор рядом с углом рамки, то меняем размер области, в другом случае — позицию.

В полях формы логичнее выводить значения относительно оригинального изображения, а не холста, — это будет работать, но потребует дополнительных вычислений.

Также вы можете придумать что-нибудь самостоятельно, если достаточно хорошо владеете JavaScript, HTML и CSS.

Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше