Перетаскивание файлов на странице - отсутствие согласованного решения

Вот чего я пытаюсь достичь:

На странице есть несколько dropzones. Пользователи должны иметь возможность перетаскивать файлы из своей ОС и переносить их в дескрипторы.

Dropzones выделяются при перетаскивании. Существует два визуально разных типа выделения: "Цель" (например, элемент очерчен пунктирной линией) и "Hover" (например, элемент получает яркий фон).

Выделение цели применяется/удаляется на/из всех разбросов одновременно:

  • Когда пользователь перетаскивает файл по странице, все dropzones должны быть выделены с помощью выделения Target.
  • Когда пользователь перетаскивает файл за пределы страницы или отменяет операцию перетаскивания или выполняет перетаскивание, то выделение цели следует удалить из всех дескрипторов.

Подсветка Hover должна применяться только к одной dropzone:

  • Когда пользователь перетаскивает файл над dropzone, эта dropzone должна быть выделена подсветкой Hover.
  • Когда пользователь перетаскивает файл за пределы этой зоны смены или отменяет операцию перетаскивания или выполняет удаление, то выделение цели следует удалить из dropzone.

Когда пользователь бросает файл в dropzone, имя файла должно появиться внутри dropzone.

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

Решение должно быть настолько изящным, насколько это возможно: грязные хаки, такие как использование тайм-аутов, подсчет dragenter/dragleave и повторное использование подсветки для каждого dragover не приветствуются.

Решение должно работать в последних версиях основных браузеров.

Вот чего мне удалось добиться до сих пор: попытка 1, попытка 2.

Проблемы, которые я успешно разрешил

  1. Если вы удалили файл за пределами dropzone, браузер открыл его.

    Решение:

    $(document).on('dragover drop', function (e) {
        e.preventDefault();
    });
    
  2. Отбрасывание файла в dropzone создает событие drop с целью, равной ребенку dropzone, а не самой dropzone.

    Решение:

    $dropzones.on( 'drop', function (event) {
    
      /* ... */
    
      // Find the dropzone responsible for the event
      $targetDropzone = $(event.target).closest($dropzones);
    
      /* ... */
    });
    
  3. Наведение файла над детьми dragleave генерирует несколько событий dragleave, поэтому подсветка Hover исчезает сразу (подсветка Hover должна быть удалена из dropzone, когда курсор мыши покидает dropzone, поэтому он связан с событием dragleave).

    Решение: используйте событие dragout вместо dragleave. dragout - это настраиваемое событие, предоставляемое плагином jquery.event.dragout. Это событие не будет срабатывать для дочерних элементов.

Неразрешенные проблемы

  1. Невозможно обнаружить момент, когда перетаскиваемый файл покидает 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), но после первого успешного падения он перестанет работать. См. Неудачную попытку здесь.

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

  3. Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.


UPD 2014-03-16 15:30, ответ Яну Быткеку ответ

Привет товарищ! Благодарим вас за подробный ответ. К сожалению, есть ряд проблем с вашим решением.

1.

  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.

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

Событие запускается каждый раз, когда мышь вводит какой-либо элемент. Таким образом, когда вы перетаскиваете страницу, она вводит много элементов и срабатывает много раз. Чтобы этого избежать, вам нужно "отключить" все под каждой респирацией, есть два способа сделать это.

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

Во-вторых, чтобы создать прозрачный оверлей поверх вершины droparea - мышь ударит только это, а не элементы под ним, что предотвратит появление нескольких событий перетаскивания.

Эти решения приемлемы для запуска подсветки Hover, которая применяется, когда указатель мыши находится внутри dropzone. (BTW, я нашел более изящное решение для этого: dragout событий dragout, см. № 3 в разделе "Разрешенные проблемы" выше).

Но они совершенно не подходят для подсветки Target, которая должна применяться, когда указатель мыши находится внутри и за пределами dropzone. Вам нужно будет отключить события мыши (либо с помощью pointer-events: none; либо наложения) для всей страницы, а dropzones больше не будут принимать капли.

3.

  1. Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.

Я не на 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, а не подход на странице, как в ваших примерах. Эти компоненты, как правило, становятся довольно большими, когда вы добавляете логику загрузки, проверку и т.д. В двух словах:

  1. Базовый плагин jQuery расширен с логикой, требуемой в двух (более) компонентах.
  2. Он обрабатывает все перетаскивание бизнес + share base css/html, чтобы сохранить все СУХОЕ.
  3. Где-то в 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

  1. Указатель мыши во время перетаскивания должен отображать значок браузера "Невозможно отбросить здесь" при зависании внешних зон, а значок "может отбросить здесь" при наведении курсора на паззы.

Сделать документ не отбрасываемым:

$(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. Подпишитесь так, как вам нравится.