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


Настройки

Итак, у меня есть 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); чтобы плюхнуть его на выбор пользователя.

нужные Функциональность

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

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

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

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

Мои Усилия До Сих Пор

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

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

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

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

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

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

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

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

это перетаскивание вещи кажется сложнее, чем это должно быть.

Итак, я начал углубляться в dnd 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-контенту fancy box, который я пытаюсь вставить в место drop. Все, что мне нужно сделать сейчас, это вставить его в нужное место!

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

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

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

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

С некоторыми экспериментами я попробовал execCommand-inserthtml'ING во время события drop и события dragend-ни вставить HTML, где был dropping-selection-caret, вместо этого он использует любое место, выбранное до операции перетаскивания.

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

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

нет конечно, это сработало: я не мог заставить маркер выбора вставляться в явно видимый каретку выбора, как планировалось,-опять же, execCommand-insertedHTML помещал себя туда, где был каретка выбора, до операции перетаскивания.

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

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

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


охота продолжается (редактирование после оригинала post)


Фаррух опубликовал хорошее предложение -- use:

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

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

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

это была героическая попытка. Спасибо, Фаррух.

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

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

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

я читал MDN Drag and Drop Docs, и обнаружил это:

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

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

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

2 59

2 ответа:

Падение Дракона

я сделал смешное количество скрипки. Так что, так много jsFiddling.

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

jsFiddle: Chase Moskal's 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.

к draggables (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 имеют разные способы получения объекта диапазона, поэтому усилия пришлось вставлять, чтобы сделать это по-разному для каждого браузера в событии 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();
});

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

спасибо всем. //Погоня.

  1. событие dragstart;dataTransfer.setData("text/html", "<div class='whatever'></div>");
  2. падение событие : 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);