Добавление аватара, обрезка фото на мобильном устройстве и десктопе на JavaScript и PHP

в 15:12, , рубрики: javascript, javascript html5 php, php, метки:

Доброго времени суток! Разрабатывая сайт я подошел к тому, что мне необходимо добавить функцию добавления аватара для пользователей на десктопе и мобильных устройствах. Долго искал материалы, даже сначала решил добавить библиотеку с готовыми функциями, уже написанную кем-то (не помню как называлась статья, но она точно была на хабре и там были рассмотрены некоторые библиотеки). После тестирования этих подключенных библиотек я решил написать все на JavaScript и PHP (за исключением использования ajax для работы с php) при помощи HTML5. Для демонстрации я создал страницу для хабра: демо для habrahabr.

Начнем с общей части. Разметка.

Тут все просто. Внутри главного блока (в моем случае 400x400px) я создал блок с изображением, абсолютный блок для вычисления координат обрезки (200х200px) и так же абсолютный блок на всю ширину рабочей области, который я назвал тачпадом (400х400px). Для большей функциональности я добавил кнопку, которая поворачивает изображение на 90 градусов. Сразу обращаю Ваше внимание что размеры этих блоков сугубо индивидуальные.

Функции для десктопа.

Все начинается с функции onmousedown. Первое что я в нее добавил это изменение масштаба изображения нажатием левой кнопки мыши с зажатой клавишей shift. Работаем с тачпадом. Для передачи координат php обработчику я создал 4 input type=«hidden» со значениями id как x1 — координата смещения обрезки изображения по оси x, y1 — соответственно по оси y, w — ширина исходного изображения, h — высота исходного изображения.

//добавляем исходное изображение
var image = document.getElementById('image');
//добавляем тачпад
var a = document.getElementById('touch_pad');
//и input-ы координат и размеров	
var x1 = document.getElementById('x1');
var y1 = document.getElementById('y1');
var w = document.getElementById('w');
var h = document.getElementById('h');
//функция после нажатия левой кнопки мыши
a.onmousedown = function(e){
//если нажат shift, то объявляем функцию изменения масштаба изображения
	if(e.shiftKey){
//получаем коэффициент положения курсора 
                var koefx = e.clientX + e.clientY;
//получаем ширину изображения
		var d = image.offsetWidth;
//после движения мышью начинаем функцию вычисления 
		a.onmousemove = function(e){
//получаем новый коэффициент положения курсора, чтоб получить разницу от koefx
		var reli = e.clientX + e.clientY;
//и задаем стили изображению по полученному коэффициенту масштаба
		image.style.width = d + reli*2 - koefx*2 + 'px';
		var wid = image.width;
		var heig = image.height;
//поскольку ширина и высота тачпада 400px, а блока обрезки 200px и он находится в центре -
//мы смещаем результат на 100px по x и y
		var ll = 100 - image.offsetLeft;
		var tt = 100 - image.offsetTop;
//и вписываем в форму новые координаты x и y и задаем новую высоту и ширину изображения
		x1.setAttribute('value', ll);
		y1.setAttribute('value', tt);
		w.setAttribute('value', wid);
		h.setAttribute('value', heig);
		}
	}

Как видите здесь все просто, чуть позже мы объявим функцию для изменения масштаба при помощи прокрутки колеса мыши, что даст дополнительный функционал пользователю.

Далее начнем функцию изменения положения исходного изображения, если shift не зажали:

else {
//получаем текущие координаты курсора
	var x = e.pageX;
	var y = e.pageY;
//получаем координаты исходного изображения
	var lleft = image.offsetLeft;
	var ttop = image.offsetTop;
//и создаем коэффициенты по осям x и y 
	var lleft = x - lleft;
	var ttop = y - ttop;
//после движения мышью начинаем функцию вычисления
	a.onmousemove = function(e){
//получаем новые координаты курсора
		x = e.pageX;
		y = e.pageY;
//и заново получаем координаты изображения
		var l = image.offsetLeft;
		var t = image.offsetTop;
//здесь получаем коэффициент разницы между изображением и блоком обрезки 
		var ll = 100 - Number(l);
		var tt = 100 - Number(t);
//и наконец задаем стили и вписываем в форму полученные данные		
		image.style.marginLeft = x - lleft;
		image.style.marginTop = y - ttop;		
		var wid = image.width;
		var heig = image.height;
		x1.setAttribute('value', ll);
		y1.setAttribute('value', tt);
		w.setAttribute('value', wid);
		h.setAttribute('value', heig);
	}
	}

//при срабатывании события mouseup очищаем функцию mousemove
	a.onmouseup = function(){
	a.onmousemove = function(){}
	}
return	
}

Как видите здесь все очень просто, что касается получения координат обрезки и изменения масштаба.
Далее нужно сделать все то же, но уже с методами touchstart и touchmove для мобильных устройств:

a.ontouchstart = function(e){	
//начинаем функцию если пальец или пальцы коснулись сенсора
//если количество касаний равняется двум, то вызываем функцию изменения масштаба scal(), которую рассмотрим позже
	if(e.targetTouches.length == 2){
		scal(e);
	}
//если касание одно, то начинаем функцию перемещения изображения	
	if(e.targetTouches.length == 1){
//каждое касание записывается в массив targetTouches и так, как у нас касание одно
//мы берем первое касание, указывая [0], то есть как в случае с курсором (он всего один)
//мы вместо e (в функциях mousedown и mousemowe нам достаточно было одной переменной)
//используем e с методом targetTouches первого касания [0] и прописываем это все
//в переменную handl
	var handl = e.targetTouches[0];
	var x = handl.pageX;
	var y = handl.pageY;
	var lleft = image.offsetLeft;
	var ttop = image.offsetTop;
	var lleft = x - lleft;
	var ttop = y - ttop;
    a.ontouchmove = function(e) {
		var handle = e.targetTouches[0]
	    x = handle.pageX;
		y = handle.pageY;
		var l = image.offsetLeft;
		var t = image.offsetTop;
		var ll = 100 - Number(l);
		var tt = 100 - Number(t);
		
		image.style.marginLeft = x - lleft;
		image.style.marginTop = y - ttop;
		a.ontouchend = function(){
		var wid = image.width;
		var heig = image.height;
		x1.setAttribute('value', ll);
		y1.setAttribute('value', tt);
		w.setAttribute('value', wid);
		h.setAttribute('value', heig);
		}
		}		
	}
	return
}

То есть, в принципе, функции одинаковы, за исключением что при работе с сенсором нам доступно использование более одного «курсора».

Дальше чуть сложней. Если касаний все-таки 2, то нужно прописать функцию изменения масштаба, а там есть свои подводные камни. Во-первых при увеличении/уменьшении нужно сделать так, чтоб менялся масштаб изображения не от левого верхнего угла, а от центра. Во-вторых, коэффициент всегда должен быть положительным числом и принимать значение «ноль» при срабатывании функции вычисления. И так поехали:

//объявляем функцию изменения масштаб на тач-устройствах
function scal(e){	
//прописываем в переменную touc массив с касаниями	
		var touc = e.targetTouches;
		var cur_w = image.offsetWidth;
//получаем первый коэффициент следующим образом	
		var koef = Math.sqrt(Math.pow((touc[0].clientX - touc[1].clientX), 2) + Math.pow((touc[0].clientY - touc[1].clientY), 2));
//начинаем функцию после события touchmove и начинаем вычислять новый коэффициент
			a.ontouchmove = function(e){
				var tou = e.targetTouches;
				var relis = Math.sqrt(Math.pow((tou[0].clientX - tou[1].clientX), 2) + Math.pow((tou[0].clientY - tou[1].clientY), 2));						
				var im = image.offsetWidth;
//задаем размер изображения разницу коэффициентов
				image.style.width = cur_w + relis*2 - koef*2  + 'px';
			}
//при срабатывании события touchend прописываем изменения
			a.ontouchend = function(e){		
		var wid = image.width;
		var heig = image.height;
		var ll = 100 - image.offsetLeft;
		var tt = 100 - image.offsetTop;
		x1.setAttribute('value', ll);
		y1.setAttribute('value', tt);
		w.setAttribute('value', wid);
		h.setAttribute('value', heig);
			}
		
}

Осталось добавить изменения масштаба при прокрутке колеса мыши на десктопе:

//здесь все предельно просто
a.onwheel = function(e){
	var t = e.deltaY/5;
	var d = image.offsetWidth;	
	image.style.width = d-t;
		var wid = image.width;
		var heig = image.height;
		var ll = 100 - image.offsetLeft;
		var tt = 100 - image.offsetTop;
		x1.setAttribute('value', ll);
		y1.setAttribute('value', tt);
		w.setAttribute('value', wid);
		h.setAttribute('value', heig);
	return
}

После этого мы добавляем функцию на кнопку вращения изображения. Передавать параметры будем при помощи подключенной библиотекой jquery методом ajax. В моем случае функцию назвал leftSide(), поскольку вращение только в одну сторону:

function  leftSide(){	
		var ug = '90';
		$.ajax({
			url: 'rotate.php',
			method: 'GET',
			data: {"deg" : ug, "filename" : "img.jpg"}						
		}).done(function(data){	
//избавляемся от кеширования изображения				
			image.src = 'img.jpg?'+ Math.random();
			image.onload = function(){
			var ll = 100 - image.offsetLeft;
			var tt = 100 - image.offsetTop;
			var wid = image.width;
			var heig = image.height;
			x1.setAttribute('value', ll);
			y1.setAttribute('value', tt);
			w.setAttribute('value', wid);
			h.setAttribute('value', heig);
			}
		})	
	
}

Соответственно скрипт файла rotate.php очень прост:

<?php
	$filename = $_GET['filename'];
	$degrees = (int)$_GET['deg'];
	$source = imagecreatefromjpeg($filename);
	$rotate = imagerotate($source, $degrees, 0);
	imagejpeg($rotate,$filename, 100);
	imagedestroy($source);
	imagedestroy($rotate);
?>

Обработчик полученного изображения каждый выбирает сам, лично я использую следующий скрипт:

<?
if(isset($_POST['crop'])){
$filename = 'img.jpg';
$new_filen = 'ava/img.jpg';
list($current_width, $current_height) = getimagesize($filename);

$x1 = $_POST['x'];

$y1 = $_POST['y'];

$w = $_POST['w'];

$h = $_POST['h'];  

$new = imagecreatetruecolor($w, $h);
$current_image = imagecreatefromjpeg($filename);
imagecopyresampled($new, $current_image, 0, 0, 0, 0, $w, $h, $current_width, $current_height);
$final = imagecreatetruecolor(200, 200);
imagecopy($final, $new, 0, 0, $x1, $y1, 200, 200);
$fin_creat = imagejpeg($final, $new_filen, 100);
$handle = fopen($new_filen, "r");
unlink($filename );
}

?>

Спасибо за внимание!

Автор: jwwiskey

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js