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

Пишем простой редактор аватарок на JavaScript

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

 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;
}

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

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

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

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

Пишем 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(); //Эта функция будет рассмотрена чуть позже
}

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

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

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-скрипт

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.

Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Изучите основы Python на практике. Бесплатно ➞
Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе.
Смотреть программу
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована