Подтвердить что ты не робот

Точное перетаскивание в пределах контента

Настройка

Итак, у меня есть contenteditable div - я создаю WYSIWYG-редактор: полужирный, курсив, форматирование, что угодно и последнее: вставка фантазийных изображений (в причудливой коробке с надписью).

<a class="fancy" href="i.jpg" target="_blank">
    <img alt="" src="i.jpg" />
    Optional Caption goes Here!
</a>

Пользователь добавляет эти причудливые изображения с помощью диалогового окна, в котором я им представляю: они заполняют детали, загружают изображение, а затем, как и другие функции редактора, я использую document.execCommand('insertHTML',false,fancy_image_html);, чтобы вскрыть его при выборе пользователя.

Желаемая функциональность

Итак, теперь, когда мой пользователь может заглянуть в причудливый образ - им нужно уметь перемещать его. Пользователь должен иметь возможность щелкнуть и перетащить изображение (причудливое поле и все), чтобы разместить его в любом месте, где они захотят, в пределах контента. Они должны иметь возможность перемещать его между абзацами или даже внутри абзацев - между двумя словами, если они хотят.

Что дает мне надежду

Имейте в виду - в контентных, простых старых тегах <img> уже благословляются пользовательским агентом с этой прекрасной возможностью перетаскивания. По умолчанию вы можете перетаскивать теги <img>, где бы вы ни находились; операция перетаскивания по умолчанию ведет себя так, как можно было бы мечтать.

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

Мои усилия до сих пор

Во-первых, я настроил свой причудливый тег <a> с помощью перетаскиваемого атрибута и отключил contenteditable (не уверен, что это необходимо, но похоже, что он также может быть отключен):

<a class="fancy" [...] draggable="true" contenteditable="false">

Затем, поскольку пользователь все еще мог вытащить изображение из фэнтезийного <a>, мне пришлось сделать некоторый CSS. Я работаю в Chrome, поэтому я показываю вам только префиксы -webkit-prefixes, хотя я тоже использовал других.

.fancy {
    -webkit-user-select:none;
    -webkit-user-drag:element; }
    .fancy>img {
        -webkit-user-drag:none; }

Теперь пользователь может перетащить весь причудливый бокс, и небольшое частично выцветшее изображение представления перетаскивания мышью отражает это - я вижу, что я собираю всю коробку сейчас:)

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

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

HTML5 API перетаскивания JavaScript JavaScript

Этот материал перетаскивания кажется более сложным, чем нужно.

Итак, я начал углубляться в api docs, и теперь я застрял. Итак, вот что я подстроил (да, jQuery):

$('.fancy')
    .bind('dragstart',function(event){
        //console.log('dragstart');
        var dt=event.originalEvent.dataTransfer;
        dt.effectAllowed = 'all';
        dt.setData('text/html',event.target.outerHTML);
    });

$('.myContentEditable')
    .bind('dragenter',function(event){
        //console.log('dragenter');
        event.preventDefault();
    })
    .bind('dragleave',function(event){
        //console.log('dragleave');
    })
    .bind('dragover',function(event){
        //console.log('dragover');
        event.preventDefault();
    })
    .bind('drop',function(event){
        //console.log('drop');      
        var dt = event.originalEvent.dataTransfer;
        var content = dt.getData('text/html');
        document.execCommand('insertHTML',false,content);
        event.preventDefault();
    })
    .bind('dragend',function(event){ 
        //console.log('dragend');
    });

Итак, здесь, где я застрял: Это почти полностью работает. Почти полностью. У меня все работает, вплоть до самого конца. В событии drop теперь у меня есть доступ к содержимому HTML-контента, которое я пытаюсь вставить в папку. Все, что мне нужно сделать сейчас, вставляет его в нужное место!

Проблема Я не могу найти правильное место для удаления или любой способ вставить в нее. Я надеялся найти какой-то объект dropLocation для сброса моего причудливого окна в, что-то вроде dropEvent.dropLocation.content=myFancyBoxHTML;, или, по крайней мере, по крайней мере, какие-то значения местоположения перетаскивания, с помощью которых можно найти свой собственный способ разместить там контент? Я что-то даю?

Я делаю это совершенно неправильно? Я что-то не хватает?

Я попытался использовать document.execCommand('insertHTML',false,content);, как я ожидал, я должен быть в состоянии, но, к сожалению, меня здесь не сработало, поскольку каретка выбора не находится в точном месте падения, так как я надеюсь.

Я обнаружил, что если я прокомментирую все event.preventDefault();, каретка выбора станет видимой, и, как можно было бы надеяться, когда пользователь будет готовиться к отбрасыванию, contenteditable, можно видеть, что небольшая каретка выбора просматривается между символами, следующими за курсором пользователя и операцией drop - указывая пользователю, что каретка выбора представляет точное местоположение перетаскивания. Мне нужно расположение этой каретки выбора.

В некоторых экспериментах я попробовал execCommand-insertHTML'ing во время события drop, а событие dragend - не вставлял HTML, где был drop-selection-caret, вместо этого он использовал любое местоположение, выбранное до операции перетаскивания.

Поскольку каретка выбора видна во время перетаскивания, я запустил план.

В течение некоторого времени я пытался в событии dragover вставить временный маркер, например <span class="selection-marker">|</span>, сразу после $('.selection-marker').remove();, при попытке браузера постоянно (во время перетаскивания) удалять все маркеры выделения и затем добавление одного в точку вставки - по существу, оставляя один маркер, где бы ни находилась эта точка вставки, в любой момент. Конечно, план заключался в том, чтобы затем заменить этот временный маркер перетаскиваемым контентом, который у меня есть.

Ничего из этого не получилось, конечно: я не мог заставить маркер выбора вставлять в видимую видимую каретку выбора, как и планировалось, - снова, вставленный execCommandHTML, где бы он ни находился, до перетаскивания операции.

Хафф. Так что я пропустил? Как это делается?

Как получить или вставить точное местоположение операции перетаскивания? Я чувствую, что это, очевидно, общая операция среди перетаскиваний - наверняка я, должно быть, не обратил внимания на важную и вопиющую деталь какого-то рода? Мне даже нужно углубиться в JavaScript, или, может быть, есть способ сделать это только с такими атрибутами, как draggable, droppable, contenteditable и некоторый fancydancy CSS3?

Я все еще нахожусь на охоте - по-прежнему возиться - я отправлю сообщение, как только узнаю, что я провалил:)


Hunt Continues (редактирование после оригинального сообщения)


Фаррух добавил хорошее предложение - используйте:

console.log( window.getSelection().getRangeAt(0) );

Чтобы увидеть, где находится каттинг выбора. Я вложил это в событие dragover, которое, когда я полагаю, что каретка выбора видимо перемещается между моим редактируемым контентом в contenteditable.

Увы, объект Range, который возвращается, сообщает об индексах смещения, относящихся к каретке выбора, перед операцией перетаскивания.

Это было отважное усилие. Спасибо Фарруху.

Итак, что здесь происходит? Я получаю ощущение, что маленькая каретка выбора, которую я вижу, прыгает вокруг, не является кареткой выбора вообще! Я считаю это самозванцем!

При дальнейшем осмотре!

Оказывается, это самозванец! Каретка выбора реального остается на месте во время всей операции перетаскивания! Вы можете увидеть маленького педераста!

Я читал MDN Drag and Drop Docs и нашел это:

Естественно, вам может понадобиться переместить маркер вставки вокруг события перетаскивания. Вы можете использовать свойства client client client и clientY как другие события мыши для определения местоположения указателя мыши.

Yikes, значит ли это, что я должен сам это понять, на основе clientX и clientY? Использовать координаты мыши для определения местоположения каретки выбора? Страшно!!

Я буду смотреть на это завтра - если только я или кто-то еще здесь не читает это, можно найти разумное решение:)

4b9b3361

Ответ 1

Бросок дракона

Я сделал нелепое количество возиться. Итак, так много jsFiddling.

Это не надежное или полное решение; Я, возможно, никогда не придумаю этого. Если у кого-то есть лучшие решения, у меня все уши - я не хотел, чтобы это делалось так, но это единственный способ, которым я смог раскрыть до сих пор. Следующий jsFiddle и информация, которую я собираюсь вырвать, работали для меня в этом конкретном случае с моими конкретными версиями Firefox и Chrome на моей конкретной настройке WAMP и на компьютере. Не плачьте мне, когда это не работает на вашем веб-сайте. Это дерьмовое дерьмо явно для каждого человека.

jsFiddle: Chase Moskal Dragon Drop

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

Оказывается - это немного кошмар. API-интерфейс перетаскивания HTML5 даже на первый взгляд ужасен. Затем вы почти разогреваетесь до него, когда начинаете понимать и соглашаться с тем, как он должен работать. Тогда вы понимаете, что такое ужасающее на самом деле это кошмар, поскольку вы узнаете, как Firefox и Chrome идут по этой спецификации по-своему, и, похоже, полностью игнорируют все ваши потребности. Вы задаете себе такие вопросы, как: "Подождите, какой элемент даже перетаскивается прямо сейчас? Как это сделать? Я получаю эту информацию? Как мне отменить операцию перетаскивания? Как я могу остановить эту уникальную уникальную настройку браузера по умолчанию для этой ситуации?"... Ответы на ваши вопросы: "Ты сам по себе, ЛОСЕР! Продолжайте взламывать вещи, пока что-то не работает!".

Итак, вот как я совершил Точное перетаскивание произвольных элементов HTML внутри, вокруг и между несколькими contenteditable. (примечание: я не буду полностью углубленно с каждой деталью, вы я должен посмотреть на jsFiddle для этого - я просто сбрасываю, казалось бы, соответствующие детали, которые я помню из опыта, поскольку у меня ограниченное время)

Мое решение

  • Во-первых, я применил CSS к draggables (fancybox) - нам понадобилось user-select:none; user-drag:element; в причудливом поле, а затем, в частности, user-drag:none; на изображении в причудливом поле (и любых других элементах, почему бы и нет?). К сожалению, этого недостаточно для Firefox, для которого явно должен быть установлен атрибут draggable="false" на изображении, чтобы он не перетаскивался.
  • Затем я применил атрибуты draggable="true" и dropzone="copy" к contenteditables.

Для перетаскиваемых объектов (fancyboxes) я привязываю обработчик для dragstart. Мы устанавливаем dataTransfer для копирования пустой строки HTML '' - потому что нам нужно обмануть это, думая, что мы будут перетаскивать HTML, но мы отменяем любое поведение по умолчанию. Иногда поведение по умолчанию скользит каким-то образом, и оно приводит к дублированию (как и мы сами вставляем), поэтому теперь худший сбой - это "" (пробел), вставленный при неудачном перетаскивании. Мы не могли полагаться на поведение по умолчанию, так как это часто не удавалось, поэтому я нашел это наиболее универсальным решением.

DD.$draggables.off('dragstart').on('dragstart',function(event){
    var e=event.originalEvent;
    $(e.target).removeAttr('dragged');
    var dt=e.dataTransfer,
        content=e.target.outerHTML;
    var is_draggable = DD.$draggables.is(e.target);
    if (is_draggable) {
        dt.effectAllowed = 'copy';
        dt.setData('text/plain',' ');
        DD.dropLoad=content;
        $(e.target).attr('dragged','dragged');
    }
});

В dropzones я привязываю обработчик для dragleave и drop. Обработчик dragleave существует только для Firefox, как и в Firefox, перетаскивание будет работать (Chrome отказывает вам по умолчанию), когда вы пытались перетащить его за пределы contenteditable, поэтому он выполняет быструю проверку только для Firefox relatedTarget. Хафф.

У Chrome и Firefox есть разные способы приобретения объекта Range,, поэтому нужно было приложить усилия, чтобы сделать это по-разному для каждого браузера в событии drop. Chrome создает диапазон, основанный на координатах мыши (точно так же), но Firefox предоставляет его в данных события. document.execCommand('insertHTML',false,blah) оказывается, как мы обрабатываем падение. ОН, я забыл упомянуть - мы не можем использовать dataTransfer.getData() в Chrome, чтобы получить наш HTML-код для dragstart - это, по-видимому, какая-то странная ошибка в спецификации. Firefox вызывает спецификацию на нем, и дает нам данные в любом случае - но Chrome не делает этого, поэтому мы наклоняемся назад и устанавливаем контент в глобальное, и пробираемся через ад, чтобы убить все поведение по умолчанию...

DD.$dropzones.off('dragleave').on('dragleave',function(event){
    var e=event.originalEvent;

    var dt=e.dataTransfer;
    var relatedTarget_is_dropzone = DD.$dropzones.is(e.relatedTarget);
    var relatedTarget_within_dropzone = DD.$dropzones.has(e.relatedTarget).length>0;
    var acceptable = relatedTarget_is_dropzone||relatedTarget_within_dropzone;
    if (!acceptable) {
        dt.dropEffect='none';
        dt.effectAllowed='null';
    }
});
DD.$dropzones.off('drop').on('drop',function(event){
    var e=event.originalEvent;

    if (!DD.dropLoad) return false;
    var range=null;
    if (document.caretRangeFromPoint) { // Chrome
        range=document.caretRangeFromPoint(e.clientX,e.clientY);
    }
    else if (e.rangeParent) { // Firefox
        range=document.createRange(); range.setStart(e.rangeParent,e.rangeOffset);
    }
    var sel = window.getSelection();
    sel.removeAllRanges(); sel.addRange(range);

    $(sel.anchorNode).closest(DD.$dropzones.selector).get(0).focus(); // essential
    document.execCommand('insertHTML',false,'<param name="dragonDropMarker" />'+DD.dropLoad);
    sel.removeAllRanges();

    // verification with dragonDropMarker
    var $DDM=$('param[name="dragonDropMarker"]');
    var insertSuccess = $DDM.length>0;
    if (insertSuccess) {
        $(DD.$draggables.selector).filter('[dragged]').remove();
        $DDM.remove();
    }

    DD.dropLoad=null;
    DD.bindDraggables();
    e.preventDefault();
});

Хорошо, мне это надоело. Я написал все, что хочу об этом. Я называю это днем ​​и могу обновить это, если я думаю о чем-то важном.

Спасибо всем.//Chase.

Ответ 2

  • событие dragstart; dataTransfer.setData("text/html", "<div class='whatever'></div>");
  • событие: var me = this; setTimeout(function () { var el = me.element.getElementsByClassName("whatever")[0]; if (el) { //do stuff here, el is your location for the fancy img } }, 0);