Перетаскивание файлов на странице - отсутствие согласованного решения
Вот чего я пытаюсь достичь:
На странице есть несколько dropzones. Пользователи должны иметь возможность перетаскивать файлы из своей ОС и переносить их в дескрипторы.
Dropzones выделяются при перетаскивании. Существует два визуально разных типа выделения: "Цель" (например, элемент очерчен пунктирной линией) и "Hover" (например, элемент получает яркий фон).
Выделение цели применяется/удаляется на/из всех разбросов одновременно:
- Когда пользователь перетаскивает файл по странице, все dropzones должны быть выделены с помощью выделения Target.
- Когда пользователь перетаскивает файл за пределы страницы или отменяет операцию перетаскивания или выполняет перетаскивание, то выделение цели следует удалить из всех дескрипторов.
Подсветка Hover должна применяться только к одной dropzone:
- Когда пользователь перетаскивает файл над dropzone, эта dropzone должна быть выделена подсветкой Hover.
- Когда пользователь перетаскивает файл за пределы этой зоны смены или отменяет операцию перетаскивания или выполняет удаление, то выделение цели следует удалить из dropzone.
Когда пользователь бросает файл в dropzone, имя файла должно появиться внутри dropzone.
Когда пользователь бросает файл на странице за пределами dropzones, все выделения выделенных областей должны быть удалены, и ничего больше не должно произойти. В частности, броузер не открывается браузером.
Решение должно быть настолько изящным, насколько это возможно: грязные хаки, такие как использование тайм-аутов, подсчет dragenter
/dragleave
и повторное использование подсветки для каждого dragover
не приветствуются.
Решение должно работать в последних версиях основных браузеров.
Вот чего мне удалось добиться до сих пор: попытка 1, попытка 2.
Проблемы, которые я успешно разрешил
-
Если вы удалили файл за пределами dropzone, браузер открыл его.
Решение:
$(document).on('dragover drop', function (e) {
e.preventDefault();
});
-
Отбрасывание файла в dropzone создает событие drop
с целью, равной ребенку dropzone, а не самой dropzone.
Решение:
$dropzones.on( 'drop', function (event) {
/* ... */
// Find the dropzone responsible for the event
$targetDropzone = $(event.target).closest($dropzones);
/* ... */
});
-
Наведение файла над детьми dragleave
генерирует несколько событий dragleave
, поэтому подсветка Hover исчезает сразу (подсветка Hover должна быть удалена из dropzone, когда курсор мыши покидает dropzone, поэтому он связан с событием dragleave
).
Решение: используйте событие dragout
вместо dragleave
. dragout
- это настраиваемое событие, предоставляемое плагином jquery.event.dragout. Это событие не будет срабатывать для дочерних элементов.
Неразрешенные проблемы
-
Невозможно обнаружить момент, когда перетаскиваемый файл покидает document
или window
, чтобы можно было выполнить команду "удалить целевую подсветку".
Пользовательский плагин dragout
предназначен для работы только для детей <body>
. Он не работает ни для document
ни для window
.
Официальные dragstart
и dragend
не будут работать вообще для перетаскивания файлов. Это ожидаемое поведение. :(
События dragenter
и dragleave
связанные с document
, запускаются не только тогда, когда указатель мыши входит/выходит из документа, но также и когда указатель вводит/оставляет документы. Хуже всего, event.target
первого появления $(document).on('dragenter')
может отображаться как один элемент (это может быть document
или его дочерний элемент), а последнее вхождение $(document).on('dragleave')
может отображаться как другой элемент, поэтому вы не можете решить проблему, сравнивая event.target
s.
Из-за этих проблем я не смог изящно отслеживать момент, когда мышь покидает документ.
Я попытался использовать плагин draghover, предназначенный для решения этой проблемы. Мне удалось заставить его работать (протестировано только в Chrome), но после первого успешного падения он перестанет работать. См. Неудачную попытку здесь.
-
Хотя визуальное dragenter
событие dragenter
запускается многократно, пока файл парит над документом. Таким образом, подсветка применяется несколько раз, а не одна.
-
Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.
UPD 2014-03-16 15:30, ответ Яну Быткеку ответ
Привет товарищ! Благодарим вас за подробный ответ. К сожалению, есть ряд проблем с вашим решением.
1.
- Невозможно обнаружить момент, когда перетаскиваемый файл покидает документ или окно, чтобы можно было выполнить команду "удалить целевую подсветку".
$ (document).on('dragleave',... должен сделать трюк, см. скрипку ниже.
Нет, это очень плохо.
Позвольте сказать, что вы слушаете события dragenter
и dragleave
на <body>
. Всякий раз, когда перетаскивание указателя мыши навешивается на край любого элемента, запускаются два события:
-
dragenter
на <body>
с event.target
установленным на зависающий элемент; -
dragleave
on <body>
с event.target
установленным для родителя зависающего элемента.
Я предположил, что выделение цели будет применяться с селектором .dropzone.target-higlighing
. Вы сделали остроумный трюк, применив подсветку Target с помощью селектора .target-highlighting.dropzone
.
Посмотрите на свой код:
$('body')
.on('dragenter', function (event) {
$(event.target).addClass('target-highlighting');
event.preventDefault();
})
.on('dragleave drop', function (event) {
$(event.target).removeClass('target-highlighting-class');
event.preventDefault();
})
Если dropzone находится в нескольких вложенных контейнерах, перетаскивание файла по контейнерам приведет к миграции мишени класса целевого выделения из самого внешнего контейнера в самый внутренний. Из-за того, что вы использовали .target-highlighting.dropzone
в CSS, похоже, что целевая подсветка стоит...
... пока вы не перетащите файл над элементом, который не является родителем dropzone. Это может быть боковая панель или сама капля. Когда это произойдет, .target-highlighting.dropzone
перестает применяться, и подсветка цели исчезает.
Это неприемлемо. Выделение цели должно появляться только тогда, когда файл перетаскивается на страницу и удаляется, когда файл вытаскивается из страницы или когда перетаскивание завершено (путем удаления или отмены).
2.
- Хотя визуальное отображение невозможно, событие dragenter запускается многократно, пока файл парит над документом. Таким образом, подсветка применяется несколько раз, а не одна.
Событие запускается каждый раз, когда мышь вводит какой-либо элемент. Таким образом, когда вы перетаскиваете страницу, она вводит много элементов и срабатывает много раз. Чтобы этого избежать, вам нужно "отключить" все под каждой респирацией, есть два способа сделать это.
Во-первых, это использование событий css-указателей, это самое элегантное, но менее ориентированное на браузер решение. Он работает с самыми последними, и я лично его люблю.
Во-вторых, чтобы создать прозрачный оверлей поверх вершины droparea - мышь ударит только это, а не элементы под ним, что предотвратит появление нескольких событий перетаскивания.
Эти решения приемлемы для запуска подсветки Hover, которая применяется, когда указатель мыши находится внутри dropzone. (BTW, я нашел более изящное решение для этого: dragout
событий dragout
, см. № 3 в разделе "Разрешенные проблемы" выше).
Но они совершенно не подходят для подсветки Target, которая должна применяться, когда указатель мыши находится внутри и за пределами dropzone. Вам нужно будет отключить события мыши (либо с помощью pointer-events: none;
либо наложения) для всей страницы, а dropzones больше не будут принимать капли.
3.
- Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.
Я не на 100% уверен в этом, но на MAC я не могу изменить значок при перетаскивании, так как он использует специальный по умолчанию. Я предполагаю, что этого не может быть сделано, но хотелось бы узнать иначе.
Я заметил, что то, что я прошу, уже работает в Chrome! См. Ссылку ниже.
Однако Firefox не изменил бы указатель мыши. :(
Лучший шаблон для тестирования решений на
Есть также довольно красивые библиотеки, такие как http://www.dropzonejs.com/, с которыми у меня нет опыта, но они являются хорошим источником "вдохновения".
Я видел этот плагин. Он не разрешает проблемы, описанные выше. Выделение цели не применяется вообще, и подсветка Hover мерцает при перетаскивании файла над dropzone.
Кроме того, я не могу использовать его, потому что у меня есть собственная реализация dropzone. Например, моя dropzone позволяет пользователям сортировать файлы, добавленные в dropzone. Мне нужно только решение для обработки событий перетаскивания.
Мои личные рекомендации состоят в том, чтобы использовать подход плагинов per-droparea, а не подход на странице, как в ваших примерах. Эти компоненты, как правило, становятся довольно большими, когда вы добавляете логику загрузки, проверку и т.д.
Вы абсолютно правы. В моем проекте я использую замечательный jQuery UI Widget Factory. Это метод определения плагинов jQuery, которые ведут себя отдельно друг от друга.
Здесь я создал лучший шаблон для тестирования дальнейших решений: http://jsbin.com/rupaloba/4/edit?html,css,js,output
Надеюсь, это не слишком сложно.
Ответы
Ответ 1
Когда я узнал, как dragenter
и dragleave
, я понял, что нет другого выбора, кроме как подсчитать элементы event.target
.
Я понял, что плагин draghover.js очень близок к тому, что мне нужно.
Поэтому я переписал его в стиле $.event.special
code для более удобного использования, а также изменил его, чтобы он не прерывался после успешного падения.
Вот вы: https://github.com/lolmaus/jquery.dragbetter
Демо: http://jsbin.com/rupaloba/15/edit?html,js,output
Ответ 2
Привет Андрей! Я недавно столкнулся с большинством из них, попытаюсь поделиться знаниями.
1. Невозможно определить момент, когда перетаскиваемый файл покидает документ или окно, чтобы можно было выполнить команду "удалить целевую подсветку".
$(document).on('dragleave', …
должен сделать трюк, см. скрипку ниже.
2. Хотя визуальное отображение невозможно, событие dragenter запускается многократно, пока файл парит над документом. Таким образом, подсветка применяется несколько раз, а не одна.
Событие запускается каждый раз, когда мышь вводит какой-либо элемент. Таким образом, когда вы перетаскиваете страницу, она вводит много элементов и срабатывает много раз. Чтобы этого избежать, вам нужно "отключить" все под каждой респирацией, есть два способа сделать это.
Во-первых, это использование событий css-указателей, это самое элегантное, но менее ориентированное на браузер решение. Он работает с самыми последними, и я лично его люблю.
Во-вторых, чтобы создать прозрачный оверлей поверх вершины droparea - мышь ударит только это, а не элементы под ним, что предотвратит появление нескольких событий перетаскивания.
3. Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить" здесь, когда вы нависаете за пределами dropzones и значок "can drop here" при зависании над dropzones.
Я не на 100% уверен в этом, но на MAC я не могу изменить значок при перетаскивании, так как он использует специальный по умолчанию. Я предполагаю, что этого не может быть сделано, но хотелось бы узнать иначе. Вы можете использовать другой дизайн, например, изменение цвета фона или, возможно, добавить div-указатель, который будет следовать за мышью. Пример показывает трюк с фоном.
Скрипт с примерами: http://jsfiddle.net/ianbytchek/Q6uEp/8/
Это касается вопросов. Мои личные рекомендации состоят в том, чтобы использовать подход плагинов per-droparea, а не подход на странице, как в ваших примерах. Эти компоненты, как правило, становятся довольно большими, когда вы добавляете логику загрузки, проверку и т.д. В двух словах:
- Базовый плагин jQuery расширен с логикой, требуемой в двух (более) компонентах.
- Он обрабатывает все перетаскивание бизнес + share base css/html, чтобы сохранить все СУХОЕ.
- Где-то в index.js
$(document).on('dragenter dragover drop', function…
предотвращает открытие файлов в браузере и навигацию.
Есть также довольно красивые библиотеки, такие как http://www.dropzonejs.com/, с которыми у меня нет опыта, но они являются хорошим источником "вдохновения".
Я также использовал следующее в своем коде, чтобы скрыть pointer-events
в старых браузерах (но никогда не проверял их на самом деле) - он проверяет, находится ли мышь вне границ элемента.
// jQuery event configuration.
jQuery.event.props.push('dataTransfer', 'pageX', 'pageY');
element.on('dragleave', function ( event) {
var elementPosition = element.offset();
var elementWidth = element.width();
var elementHeight = element.height();
if (event.pageX < elementPosition.left || event.pageX > elementPosition.left + elementWidth || event.pageY < elementPosition.top || event.pageY > elementPosition.top + elementHeight) {
element.removeClass(States.HIGHLIGHTED);
}
// …
// …
// …
Обновление 1 (2014-03-16 19:00)
@Andrey'lolmaus'Mikhaylov, вы правы в этих точках - это беспорядок, когда вы начинаете влагать вещи. Он играл дальше, и это оказалось настоящей сукой, поэтому я заинтриговал. Мне не повезло решить его с dragenter
событий dragenter
и dragleave
, но я уверен, что решение существует. Я придумал что-то менее привлекательное: http://jsfiddle.net/ianbytchek/Q6uEp/14/
Это довольно аккуратное решение, и я думаю, что это будет чище, чем то, что вы можете получить с другими приложениями. В то же время он чувствует себя немного взломанным со всеми проверками координат. Я устал смотреть на него, если его отполируют до более качественной версии, было бы здорово узнать.
Ответ 3
- Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.
Сделать документ не отбрасываемым:
$(document).on('dragover drop', function(e) {
e.preventDefault();
e.stopPropagation();
e.originalEvent.dataTransfer.dropEffect = "none";
});
Настройте перетаскивание мышью по вашему желанию:
$("[selector]").on('dragover', function(e) {
e.dataTransfer.dropEffect = 'copy'; // or 'move' or 'link'
});
Конечно, подписка на события может быть не с JQuery. Подпишитесь так, как вам нравится.