HTML5: загрузка файлов с помощью Drag & Drop

Владимир | | Ajax, HTML, JavaScript, PHP, Web разработка.

html5 drag&drop upload

Приветствую всех!

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

Возможно, в каких-то случаях виноваты разработчики сервиса, но часто проблема заключается в ограничениях, которые накладывают браузеры. Рассмотрим загрузку файлов на сервер. В большинстве случаев вам предложат стандартное поле с кнопкой выбора файла с вашего компьютера и/или поле, в котором можно указать URL файла, размещенного где-нибудь в Сети.

Загрузку файлов с локального компьютера трогать пока не будем, я планирую опубликовать отдельный пост на эту тему, разберем загрузку с удалённого сервера.

Проблемы начинаются с первого же шага. Даже если вы четко понимаете, где искать URL и хорошо умеете пользоваться инструментами вроде firebug, то всё равно потребуется несколько кликов мышкой чтобы получить нужный адрес. Было бы гораздо удобнее просто перетянуть нужную картинку из одного окна браузера в другое.

Пример реализации такого интерфейса я покажу в этой статье. Если хотите, можете посмотреть, как он работает на демонстрационной страничке или скачать архив с исходниками.

Source

Обратите внимание! Данный пример работает только в браузере Google Chrome. По-идее, поддержка всех необходимых технологий есть в Firefox и Safari, но с ними я пока не разбирался.
В качестве объектов для «перетягивания» я брал в основном картинки с википедии. Было замечено несколько проблем связанных с не латинскими символами в URL картинок, но чтобы не перегружать пример проверками и преобразованиями я их оставил как есть.

Принцип работы

Стандарт HTML5 предусматривает поддержку «перетягивания» объектов страницы (Drag and Drop). Кстати, пример простейшей реализации D&D я уже показывал – Drag & Drop с использованием HTML5. И, кроме того, есть довольно много JavaScript библиотек, реализующих поддержку D&D.

Но тут важно понимать, что если необходимо «перетягивать» картинки со сторонних ресурсов, то использовать библиотеки не получится. Т.к. вы не сможете добавить свой JS код на чужую страницу. А для того, чтобы загрузить картинку, нам нужно получить её URL, т.е. браузер должен вместе с перетягиваемым объектом передавать и его параметры (например, атрибут src картинки или весь тег img).

В этом случае мы можем создать на своей странице «приёмник» картинок. Это будет обычный div которому назначен обработчик события drop. Если пользователь «сбросит» картинку над этим div'ом, то будет вызван обработчик и в первом параметре он получит объект, содержащий информацию о перетягиваемой картинке.

Дальше всё достаточно просто. Если мы получили нужный URL, то можно отправить AJAX-запрос серверному скрипту, который выполнит загрузку файла.

Реализация

Начнём со страницы нашего приложения.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Images Upload</title>
		<link rel="stylesheet" type="text/css" href="styles.css" />
    </head>
    <body>
		<div id="images"></div>
		<div id="img_target"></div>
		<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
		<script src="main.js"></script>
    </body>
</html>

На ней размещены два блока: images – здесь будем показывать загруженные изображения и img_target – на этот блок нужно перетягивать картинки.

Внизу страницы подключаем библиотеку jQuery и скрипт main.js, который будет отправлять информацию о перетянутых изображениях на сервер.

Рассмотрим main.js

$(function() {
    $('#img_target')
        .bind('dragenter', function(event) {
            $(this).addClass('drop_here');
            return false;
        })
        .bind('dragleave', function(event) {
            $(this).removeClass('drop_here');
            return false;
        })
        .bind('dragover', function(event) {
            return false;
        })
        .bind('drop', function(event) {
            $(this).removeClass('drop_here');
			var srcRegex = /src=\"([^\s]+)\"/ig;
            var data = event.originalEvent.dataTransfer.getData('text/html');
			var img_data = srcRegex.exec(data);
			$.post('upload.php', {'file_url':img_data[1]}, function(res) {
				var response = eval('(' + res + ')');
				$('#images').append($('<img src="' + response.file_url + '" />'));
			});
            return true;
        });
});

Здесь мы назначаем обработчики событиям dragenter, dragleave и dragover. Все они должны просто возвращать false и, чтобы как-то проинформировать пользователя о том, что можно «сбрасывать» картинку, в обработчике dragenter устанавливаем CSS класс drop_here для блока-приёмника.

Основная часть работы выполняется в обработчике события drop. При возникновении этого события мы читаем информацию о «сброшенном» объекте и «вырезаем» значение атрибута src, т.е. URL картинки (строки 16-18). Информация передается в объекте event.originalEvent.dataTransfer (строка 17).

Затем формируем обычный AJAX запрос и в качестве параметра передаём ему найденный URL.

Серверный скрипт (upload.php) получит URL изображения на удалённом сервере и загрузит его. А в ответе на AJAX запрос он отправит новый URL загруженной картинки.

В свою очередь, обработчик AJAX-запроса создаст тег img и вставит его в блок images. Таким образом, загруженные картинки будут появляться над полем загрузки.

Рассмотрим upload.php

<?php

define('BASE_URL', 'http://localhost/tests/images-upload/');

function upload_from_url($file_url) {
	$url_segments = explode('/', $file_url);
	$file_name = urldecode(end($url_segments));
	if (false !== $file_name) {
		$file_name_parts = explode('.', $file_name);
		if (in_array(strtolower(end($file_name_parts)), array('jpeg','jpg','png','gif'))) {
			$destination=fopen("upload/".$file_name,"w");
			$source=fopen($file_url,"r");
			$maxsize=300*1024;
			$length=0;
			while (($a=fread($source,1024))&&($length<$maxsize)) {
				$length=$length+1024;
				fwrite($destination,$a);
			}
			fclose($source);
			fclose($destination);
		}
	}
	$new_file_url = BASE_URL.'upload/'.$file_name;
	return $new_file_url;
}

$res = array('err' => 'Не указан URL файла');

if (isset($_POST['file_url'])) {
	$new_url = upload_from_url($_POST['file_url']);
	$res = array('file_url' => $new_url);
}

echo json_encode($res);

Принцип работы следующий. Читаем URL картинки и пытаемся её загрузить (строки 29-32).

Если картинка загружена, сохраняем её в папку upload. Получение картинки с удалённого сервера осуществляется с помощью функций fread. Файл читаем блоками по 1кБ (строки 15-18). Такой подход позволяет прервать загрузку файла, если его размер превышает заданный предел (в данном случае 300кБ).

После загрузки файла формируем для него URL и отправляем браузеру в формате JSON.

Как видите, реализовать такой загрузчик несложно. И пользоваться им достаточно удобно. Естественно, основным недостатком является поддержка HTML5 браузерами, точнее её отсутствие 😉

Тем не менее, если вы создаёте интерфейс для сотрудников какой-нибудь компании, и можете оговорить тип браузера, то HTML5 вполне можно использовать.

Успехов!

Интересно почитать.

Фотокнига — фантастический сюрприз для любимого мужчины. Попробуйте сами!

Правильно подготовим 3-ндфл при покупке квартиры за 2011 год, дадим инструкции для получения вычета

Нужно распарсить сайт? Интересный пример как написан парсер на php. Можно даже скачать скрипт парсера.

  • Ну вот, на деле оказалось легче некуда.

  • Не понял только зачем делать это:

    $url_segments = explode('/', $file_url); $file_name = urldecode(end($url_segments));

    для того чтобы получить имя файла, если можно так:

    $file_name =
    urldecode(
    basename(
    $file_url
    ));
      

  • Аноним

    А у меня почему-то в демке картинки заливает на http://localhost

    • Большое спасибо, что написали. Я забыл исправить BASE_URL в upload.php, когда переносил его на сервер.Сейчас все работает.

  • Не работает ни в фф, ни в хроме, при перетаскивании картинки просто открывается url картинки на странице с демкой, событие drop обрабатывается, а «event.originalEvent.dataTransfer.getData('text/html')» возвращает бинарный мусор, после чего падает при вызове $.post

    P. S. linux

  • Проверил и под линухом и под виндой в хроме, не работает, единственное под виндой выдает изображение битой картинки…

    • Странно, только что проверил. Картинки загружаются. Можете посмотреть отладчиком chrome какие ошибки возникают?

  • Дмитрий

    В ff заработало после изменения 
    getData('text/html');
    на
    getData('text/plain');

    в var data — сразу имеется url картинки

  • Alexander Tkachev

    В Firefox getData('text/html'); выдает иероглифы…
    а getData('text/plain'); всегда возвращает пустую строку..
    что делать?…

    • Воспроизводится для всех картинок или только для каких-то конкретных?

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

    • Эти возможности обычно используются в админках сайтов. Поэтому, об «обычном» посетителе речь не идет. А оформить можно также как в админке новых версий WP (большой прямоугольник с надписью «Перетащите сюда файл» 🙂 )

  • Kastes

    Спасибо, полезная статья. Хотелось бы продолжения про загрузку файлов с локального компьютера