Пишем простой редактор аватарок на JavaScript
В этой статье мы расскажем, как написать простой редактор для аватарок, чтобы облегчить жизнь пользователям.
![](https://248006.selcdn.ru/main/iblock/e78/e78336ab495d5622d39f7c3c7c966b5f/8b2c735fc22b39b83d4b7d95f4d97e4e.png)
![](https://248006.selcdn.ru/main/iblock/e78/e78336ab495d5622d39f7c3c7c966b5f/8b2c735fc22b39b83d4b7d95f4d97e4e.png)
vlada_maestro / shutterstock
Если на сайте можно загрузить аватарку, то хорошо бы добавить минимальный редактор, который позволит обрезать изображение. Реализовать это относительно просто, и все стороны остаются в выигрыше: пользователю удобнее делать всё на сайте, не уходя с него.
Как работает редактор
Редактор берёт уже сохранённую фотографию и выводит её на страницу. Далее пользователь выбирает нужный ему фрагмент с помощью мыши или вводит значения в форме.
После сохранения сайт отправляет запрос 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;
}
Получается достаточно минималистичная форма:
![](https://248006.selcdn.ru/main/upload/setka_images/17165517062020_e3ea06ecc4efe66fd609360c227a5daace25eda6.png)
Изображение выводится с помощью тега img, а поверх него находится элемент canvas. На данном этапе позиция холста не указана — она прописывается скриптом и зависит от позиции изображения.
Дальше берём какое-нибудь изображение, чтобы его редактировать. Я воспользовался сайтом thispersondoesnotexist.com, который генерирует фотографию несуществующего человека.
![](https://248006.selcdn.ru/main/upload/setka_images/17150517062020_db52642fc67f6c7c46657360f234a883af322464.png)
Пишем JS-скрипт
Для начала получим из 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(); //Эта функция будет рассмотрена чуть позже
}
Чтобы проверить, работает ли функция нормально, я уже добавил рамку на холст:
![](https://248006.selcdn.ru/main/upload/setka_images/17195817062020_6a4e9b3ae3023faad72ace61e6264ce47ed78056.png)
Давайте рассмотрим код, который отвечает за выделение:
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 () отрисовывает рамку:
![](https://248006.selcdn.ru/main/upload/setka_images/17215717062020_d58f50d1222620cd1cfe95da3a91221bd0d26e65.png)
Остаётся написать код, который двигает эту область:
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 (), можно увидеть в полном коде в репозитории.
Давайте посмотрим, как это работает:
![](/upload/setka_images/17150517062020_b0db57166028d10b861a6ceedc3e58d5dd8b10d0.gif)
Заключительный этап — пишем функцию для отправки запроса 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-скрипт
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!";
}
?>
В итоге мы получаем новое изображение, на котором будет выделенный фрагмент. Его разрешение зависит от размера оригинала, а не от размера холста.
![](https://248006.selcdn.ru/main/upload/setka_images/17150417062020_27e9aa5bdf801f94f7728fe14d1ac08405e5a691.png)
Вы можете сделать так, чтобы разрешения совпадали, — для этого измените функцию imagecopyresized ():
imagecopyresized($output, $source, 0, 0, $newLeft, $newTop, $newWidth, $newHeight, $width, $height);
Как улучшить редактор аватарок
Вы можете добавить возможность менять разрешение выделенной области с помощью мыши — для этого в функции MouseMove () проверим, в какой области находится курсор, когда пользователь двигает мышь. Если курсор рядом с углом рамки, то меняем размер области, в другом случае — позицию.
В полях формы логичнее выводить значения относительно оригинального изображения, а не холста, — это будет работать, но потребует дополнительных вычислений.
Также вы можете придумать что-нибудь самостоятельно, если достаточно хорошо владеете JavaScript, HTML и CSS.