Целая страница как dropzone для перетаскивания

Во время написания веб-приложений, в которых произошел ввод файлов, я хотел использовать drag 'n' drop, но я не хотел просто небольшой dropzone на странице. Я подумал, что было бы удобнее, если бы вы могли попасть на страницу. К счастью, событие window.ondrop срабатывает где угодно на странице, но я хотел, чтобы какой-то причудливый эффект показывал пользователю визуально, что перетаскивание возможно.

Чтобы сделать это, все, что было необходимо, было обнаружено, когда файл был перетащен в окно, а когда он был вытащен, чтобы вызвать эффект, который показал пользователю, что приложение было включено с помощью перетаскивания. Оказывается, события перетаскивания не так удобны. Я предположил, что window.ondragenter будет запускаться только один раз, когда пользователь войдет в страницу. Затем, когда вы покидаете окно, он запускает window.ondragleave. Неправильно. Он постоянно стреляет, когда мышь перемещается по дочерним элементам на странице.

Я посмотрел, какие свойства были доступны в объекте события, пытаясь найти что-нибудь, что могло бы выделить то, что мне нужно, но ничего не получилось. Дальше я получил возможность изменить цвет фона body. И только если на странице ничего не было.

Тонны сайтов загрузки файлов получили это право. Imgur и WeTransfer, например. Их сайты были все спайтти-кодированы и сжаты до степени нечитаемости, и я не смог найти что-либо по этому вопросу при помощи googling.

Итак, как это можно сделать?

Ответ 1

Фокус в том, чтобы использовать dropzone, охватывающий всю страницу, и кешировать target of window.ondragenter для сравнения с target от window.ondragleave.

Во-первых, dropzone:

<style>
div.dropzone
{
    /* positions to point 0,0 - required for z-index */
    position: fixed; top: 0; left: 0; 
    /* above all elements, even if z-index is used elsewhere
       it can be lowered as needed, but this value surpasses
       all elements when used on YouTube for example. */
    z-index: 9999999999;               
    /* takes up 100% of page */
    width: 100%; height: 100%;         
    /* dim the page with 50% black background when visible */
    background-color: rgba(0,0,0,0.5);
    /* a nice fade effect, visibility toggles after 175ms, opacity will animate for 175ms. note display:none cannot be animated.  */
    transition: visibility 175ms, opacity 175ms;
}
</style>
<!-- both visibility:hidden and display:none can be used,
     but the former can be used in CSS animations -->
<div style="visibility:hidden; opacity:0" class="dropzone"></div>

Несмотря на то, что dropzone будет охватывать всю страницу, использование visibility:hidden или display:none скроет ее из представления. Я использовал visibility:hidden, чтобы анимации CSS можно было использовать для анимации перехода.

Назначение событий

<script>
/* lastTarget is set first on dragenter, then
   compared with during dragleave. */
var lastTarget = null;

window.addEventListener("dragenter", function(e)
{
    lastTarget = e.target; // cache the last target here
    // unhide our dropzone overlay
    document.querySelector(".dropzone").style.visibility = "";
    document.querySelector(".dropzone").style.opacity = 1;
});

window.addEventListener("dragleave", function(e)
{
    // this is the magic part. when leaving the window,
    // e.target happens to be exactly what we want: what we cached
    // at the start, the dropzone we dragged into.
    // so..if dragleave target matches our cache, we hide the dropzone.
    if(e.target === lastTarget || e.target === document)
    {
        document.querySelector(".dropzone").style.visibility = "hidden";
        document.querySelector(".dropzone").style.opacity = 0;
    }
});
</script>

Итак, вот этот процесс: вы перетаскиваете файл по окну, а window.ondragenter немедленно запускается. target установлен в корневой элемент <html>. Затем вы сразу же обнаружите свою dropzone, которая охватывает всю страницу. window.ondragenter снова выстрелится, на этот раз цель будет вашей dropzone. Каждый раз, когда срабатывает событие dragenter, он кэширует цель, потому что это будет цель, которая будет соответствовать последнему событию window.ondragleave, которое срабатывает при перетаскивании из окна.

Почему это работает? Я понятия не имею, но вот как это сделать. Это почти единственный рабочий метод, который запускается, когда пользователь перетаскивает страницу.

Я считаю, что это работает, потому что когда dropzone не скрыт, он будет всегда быть последней целью. Он охватывает каждый пиксель страницы, даже тег <html>. При выходе из окна этот метод опирается на стрелочный огонь. К сожалению, есть ошибка в Firefox, которая предотвращает ее правильную работу. Пожалуйста, проголосуйте за него, чтобы он быстрее установился. Начиная с Firefox 57.0.2, dragleave, похоже, срабатывает должным образом. Однако требуется обходное решение, проверяя document вместо кэшированного элемента:

if(e.target === lastTarget || e.target === document)

Вот его JSBin в действии. Протестировано в последних версиях Chrome, Firefox, Edge и IE11.