Точное перетаскивание в пределах 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 ответа:
Падение Дракона
я сделал смешное количество скрипки. Так что, так много 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, поэтому он выполняет быструю проверку только для FirefoxrelatedTarget
. Хафф.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(); });
ладно, я устал от этого. Я написал об этом все, что хотел. Я называю это днем и могу обновить это, если я думаю о чем-то важном.
спасибо всем. //Погоня.