問題描述
所以,我有一個內容可編輯的 div —— 我正在制作一個所見即所得的編輯器:粗體、斜體、格式等,以及最近的:插入精美的圖像(在精美的框中,帶有標題).
So, I have a contenteditable div -- I'm making a WYSIWYG editor: bold, italics, formatting, whatever, and most lately: inserting fancy images (in a fancy box, with a caption).
<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);
在用戶選擇時將其放入.
The user adds these fancy images with a dialog I present them with: they fill out the details, upload the image, and then much like the other editor functions, I use document.execCommand('insertHTML',false,fancy_image_html);
to plop it in at the user's selection.
所以,現在我的用戶可以放入精美的圖片——他們需要能夠四處移動它.用戶需要能夠單擊并拖動圖像(花哨的框和所有)以將其放置在 contenteditable 中他們喜歡的任何位置.他們需要能夠在段落之間移動它,甚至在段落內——如果他們愿意的話,可以在兩個詞之間移動.
So, now that my user can plop in a fancy image -- they need to be able to move it around. The user needs to be able to click and drag the image (fancy box and all) to place it anywhere that they please within the contenteditable. They need to be able to move it between paragraphs, or even within paragraphs -- between two words if they want.
請記住——在一個內容可編輯的、普通的舊 <img>
標簽中,用戶代理已經用這種可愛的拖放功能祝福了標簽.默認情況下,您可以隨意拖放<img>
標簽;默認的拖放操作表現得如人所愿.
Keep in mind -- in a contenteditable, plain old <img>
tags are already blessed by the user-agent with this lovely drag-and-drop capability. By default, you can drag and drop <img>
tags around wherever you please; the default drag-and-drop operation behaves as one would dream.
所以,考慮到這種默認行為如何在我們的 <img>
伙伴中如此出色地發揮作用——我只想稍微擴展這種行為以包含更多 HTML——這看起來應該很容易實現.
So, considering how this default behavior already works so smashingly on our <img>
buddies -- and I only want to extend this behaviour a little bit to include a tad more HTML -- this seems like something that should be easily possible.
首先,我用可拖動屬性設置了我喜歡的 <a>
標記,并禁用了 contenteditable(不確定是否有必要,但它似乎也可以關閉):
First, I set up my fancy <a>
tag with the draggable attribute, and disabled contenteditable (not sure if that's necessary, but it seems like it may as well be off):
<a class="fancy" [...] draggable="true" contenteditable="false">
然后,因為用戶仍然可以將圖像拖出花哨的 <a>
框,所以我不得不做一些 CSS.我在 Chrome 中工作,所以我只向您展示了 -webkit- 前綴,盡管我也使用了其他前綴.
Then, because the user could still drag the image out of the fancy <a>
box, I had to do some CSS. I'm working in Chrome, so I'm only showing you the -webkit- prefixes, though I used the others too.
.fancy {
-webkit-user-select:none;
-webkit-user-drag:element; }
.fancy>img {
-webkit-user-drag:none; }
現在用戶可以拖動整個花哨的盒子,部分褪色的點擊拖動表示圖像反映了這一點——我可以看到我現在正在拿起整個盒子:)
Now the user can drag the whole fancy box, and the little partially-faded click-drag representation image reflects this -- I can see that I'm picking up the entire box now :)
我嘗試了幾種不同 CSS 屬性的組合,上面的組合對我來說似乎很有意義,而且效果最好.
I've tried several combinations of different CSS properties, the above combo seems to make sense to me, and seems to work best.
我希望僅此 CSS 就足以讓瀏覽器將整個元素用作可拖動項,自動授予用戶我夢寐以求的功能...... 然而,它似乎比這更復雜.
I was hoping that this CSS alone would be enough for the browser to use the entire element as the draggable item, automagically granting the user the functionality I've been dreaming of... It does however, appear to be more complicated than that.
這個拖放的東西似乎比它需要的要復雜.
This Drag and Drop stuff seems more complicated than it needs to be.
所以,我開始深入研究 DnD api 文檔,但現在我陷入了困境.所以,這就是我編造的(是的,jQuery):
So, I started getting deep into DnD api docs, and now I'm stuck. So, here's what I've rigged up (yes, 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');
});
這就是我卡住的地方:這幾乎完全有效.幾乎完全.我一切正常,直到最后.在放置事件中,我現在可以訪問我試圖在放置位置插入的精美盒子的 HTML 內容.我現在需要做的就是將它插入正確的位置!
So here's where I'm stuck: This almost completely works. Almost completely. I have everything working, up until the very end. In the drop event, I now have access to the fancy box's HTML content that I'm trying to have inserted at the drop location. All I need to do now, is insert it at the correct location!
問題是我找不到正確的放置位置,或任何插入它的方法.我一直希望找到某種'dropLocation' 將我的精美盒子轉儲到的對象,例如 dropEvent.dropLocation.content=myFancyBoxHTML;
,或者至少可以找到某種放置位置值我自己把內容放在那里的方式?我得到了什么嗎?
The problem is I can't find the correct drop location, or any way to insert to it. I've been hoping to find some kind of 'dropLocation' object to dump my fancy box into, something like dropEvent.dropLocation.content=myFancyBoxHTML;
, or perhaps, at least, some kind of drop location values with which to find my own way to put the content there?
Am I given anything?
我做錯了嗎?我完全錯過了什么嗎?
我嘗試使用 document.execCommand('insertHTML',false,content);
就像我預期的那樣,但不幸的是,我在這里失敗了,因為選擇插入符號沒有像我希望的那樣位于精確的放置位置.
I tried to use document.execCommand('insertHTML',false,content);
like I expected I should be able to, but it unfortunately fails me here, as the selection caret is not located at the precise drop location as I'd hope.
我發現,如果我注釋掉所有 event.preventDefault();
,選擇插入符號就會變得可見,正如人們希望的那樣,當用戶準備放下,將他們的拖動懸停在 contenteditable 上,可以看到小選擇插入符號在用戶光標和放置操作之后的字符之間運行 - 向用戶指示選擇插入符號代表精確的放置位置.我需要此選擇插入符號的位置.
I discovered that if I comment out all of the event.preventDefault();
's, the selection caret becomes visible, and as one would hope, when the user prepares to drop, hovering their drag over the contenteditable, the little selection caret can be seen running along between characters following the user's cursor and drop operation -- indicating to the user that the selection caret represents the precise drop location. I need the location of this selection caret.
通過一些實驗,我在 drop 事件和 dragend 事件期間嘗試了 execCommand-insertHTML'ing - 既不將 HTML 插入到 drop-selection-caret 所在的位置,而是使用在拖動操作之前選擇的任何位置.
With some experiments, I tried execCommand-insertHTML'ing during the drop event, and the dragend event -- neither insert the HTML where the dropping-selection-caret was, instead it uses whatever location was selected prior to the drag operation.
因為選擇插入符號在拖動過程中是可見的,所以我制定了一個計劃.
有一段時間,我試圖在 dragover 事件中插入一個臨時標記,例如 <span class="selection-marker">|</span>
$('.selection-marker').remove();
,試圖讓瀏覽器不斷(在拖動期間)刪除所有選擇標記,然后在插入點添加一個 - 本質上隨時在該插入點所在的任何位置留下一個標記.當然,計劃是用我擁有的拖動內容替換這個臨時標記.
For awhile, I was trying, in the dragover event, to insert a temporary marker, like <span class="selection-marker">|</span>
, just after $('.selection-marker').remove();
, in an attempt for the browser to constantly (during dragover) be deleting all selection markers and then adding one at the insertion point -- essentially leaving one marker wherever that insertion point is, at any moment. The plan of course, was to then replace this temporary marker with the dragged content which I have.
當然,這些都不起作用:我無法按計劃將選擇標記插入到明顯可見的選擇插入符處——同樣,execCommand-insertedHTML 在拖動之前將自身放置在選擇插入符所在的任何位置操作.
None of this worked, of course: I couldn't get the selection-marker to insert at the apparently visible selection caret as planned -- again, the execCommand-insertedHTML placed itself wherever the selection caret was, prior to the drag operation.
如何獲取或插入拖放操作的精確位置?我覺得這顯然是拖放操作中的常見操作——我肯定忽略了某種重要且明顯的細節嗎?我什至必須深入了解 JavaScript,或者也許有一種方法可以只使用可拖動、可放置、可內容編輯和一些花哨的 CSS3 等屬性?
How do I obtain, or insert into, the precise location of a drag-and-drop operation? I feel like this is, obviously, a common operation among drag-and-drops -- surely I must have overlooked an important and blatant detail of some kind? Did I even have to get deep into JavaScript, or maybe there's a way to do this just with attributes like draggable, droppable, contenteditable, and some fancydancy CSS3?
我仍在尋找——仍在修補——我會在發現我一直失敗的地方后立即回復:)
I'm still on the hunt -- still tinkering around -- I'll post back as soon as I find out what I've been failing at :)
Farrukh 提出了一個很好的建議——使用:
Farrukh posted a good suggestion -- use:
console.log( window.getSelection().getRangeAt(0) );
查看選擇插入符號的實際位置.我將它放入 dragover 事件中,此時我發現選擇插入符號明顯在 contenteditable 中的可編輯內容之間跳躍.
To see where the selection caret actually is. I plopped this into the dragover event, which is when I figure the selection caret is visibily hopping around between my editable content in the contenteditable.
唉,返回的 Range 對象在拖放操作之前報告屬于選擇插入符號的偏移索引.
這是一次勇敢的努力.謝謝法魯克.
It was a valiant effort. Thanks Farrukh.
那么這里發生了什么?我感覺我看到的那個小選擇插入符號在跳來跳去,根本就不是選擇插入符號!我認為這是一個冒名頂替者!
So what's going on here? I am getting the sensation that the little selection caret I see hopping around, isn't the selection caret at all! I think it's an imposter!
原來,這是一個冒名頂替者!真正的選擇插入符號在整個拖動操作期間保持原位!你可以看到那個小蟲子!
Turns out, it is an imposter! The real selection caret remains in place during the entire drag operation! You can see the little bugger!
我在閱讀 MDN 拖放文檔,發現這個:
I was reading MDN Drag and Drop Docs, and found this:
當然,您可能還需要在拖動事件周圍移動插入標記.您可以像使用其他鼠標事件一樣使用事件的 clientX 和 clientY 屬性來確定鼠標指針的位置.
Naturally, you may need to move the insertion marker around a dragover event as well. You can use the event's clientX and clientY properties as with other mouse events to determine the location of the mouse pointer.
哎呀,這是否意味著我應該根據 clientX 和 clientY 自己解決這個問題?自己使用鼠標坐標確定選擇插入符號的位置?嚇人!!
Yikes, does this mean I'm supposed to figure it out for myself, based on clientX and clientY?? Using mouse coordinates to determine the location of the selection caret myself? Scary!!
我明天會考慮這樣做 -- 除非我自己或其他閱讀本文的人能找到一個理智的解決方案 :)
I'll look into doing so tomorrow -- unless myself, or somebody else here reading this, can find a sane solution :)
推薦答案
Dragon Drop
我做了很多荒謬的擺弄.所以,這么多的jsFiddleing.
Dragon Drop
I've done a ridiculous amount of fiddling. So, so much jsFiddling.
這不是一個穩健或完整的解決方案;我可能永遠也想不出一個.如果有人有更好的解決方案,我會全力以赴——我不想這樣做,但這是迄今為止我能夠發現的唯一方法.下面的 jsFiddle 以及我即將吐出的信息,在我的特定 WAMP 設置和計算機上使用特定版本的 Firefox 和 Chrome 在這個特定實例中為我工作. 別哭了當它在您的網站上不起作用時我.這種拖放式的廢話顯然是每個人都為自己.
jsFiddle: Chase Moskal 的龍滴
所以,我把我女朋友的腦子弄得無聊了,她以為我一直在說dragon drop",而實際上,我只是在說drag-and-drop".它卡住了,所以這就是我為處理這些拖放情況而創建的 JavaScript 小伙伴.
So, I was boring my girlfriend's brains out, and she thought I kept saying "dragon drop" when really, I was just saying "drag-and-drop". It stuck, so that's what I call my little JavaScript buddy I've created for handling these drag-and-drop situations.
事實證明,這有點像一場噩夢.HTML5 Drag-and-Drop API 即使乍一看也很糟糕.然后,當您開始理解并接受它應該的工作方式時,您幾乎對它產生了興趣.. 然后你會意識到這實際上是一場多么可怕的噩夢,因為你了解了 Firefox 和 Chrome 如何以他們自己的特殊方式執行此規范,并且似乎完全忽略了你的所有需求.你會發現自己在問這樣的問題:等等,什么元素現在正在被拖動?如何獲取該信息?如何取消此拖動操作?如何停止此特定瀏覽器對這種情況的獨特默認處理?"...您的問題的答案:您只能靠自己了,失敗者!繼續破解,直到成功!".
Turns out -- it's a bit of a nightmare. The HTML5 Drag-and-Drop API even at first glance, is horrible. Then, you almost warm up to it, as you start to understand and accept the way it's supposed to work.. Then you realize what a terrifying nightmare it actually is, as you learn how Firefox and Chrome go about this specification in their own special way, and seem to completely ignore all of your needs. You find yourself asking questions like: "Wait, what element is even being dragged right now? How to do I get that information? How do I cancel this drag operation? How can I stop this particular browser's unique default handling of this situation?"... The answers to your questions: "You're on your own, LOSER! Keep hacking things in, until something works!".
所以,這就是我如何在多個 contenteditable 內部、周圍和之間實現 任意 HTML 元素的精確拖放. (注意:我不會對每個詳細信息,您必須查看 jsFiddle ——我只是在漫無邊際地談論我從經驗中記得的看似相關的細節,因為我的時間有限)
So, here's how I accomplished Precise Drag and Drop of Arbitrary HTML Elements within, around, and between multiple contenteditable's. (note: I'm not going fully in-depth with every detail, you'll have to look at the jsFiddle for that -- I'm just rambling off seemingly relevant details that I remember from the experience, as I have limited time)
- 首先,我將 CSS 應用于可拖動對象(fancybox)——我們需要
user-select:none;user-drag:element;
在花式框上,然后特別是user-drag:none;
在花式框內的圖像上(以及任何其他元素,為什么不呢?).不幸的是,這對于 Firefox 來說還不夠,它需要在圖像上顯式設置屬性draggable="false"
以防止它被拖動. - 接下來,我將屬性
draggable="true"
和dropzone="copy"
應用到 contenteditables.
- First, I applied CSS to the draggables (fancybox) -- we needed
user-select:none; user-drag:element;
on the fancy box, and then specificallyuser-drag:none;
on the image within the fancy box (and any other elements, why not?). Unfortunately, this was not quite enough for Firefox, which required attributedraggable="false"
to be explicitly set on the image to prevent it from being draggable. - Next, I applied attributes
draggable="true"
anddropzone="copy"
onto the contenteditables.
對于可拖動對象(花式框),我為 dragstart
綁定了一個處理程序. 我們將 dataTransfer 設置為復制 HTML ' ' 的空白字符串——因為我們需要欺騙它認為我們要拖動 HTML,但我們正在取消任何默認行為.有時默認行為會以某種方式滑入,并導致重復(就像我們自己進行插入一樣),所以現在最糟糕的故障是在拖動失敗時插入''(空格).我們不能依賴默認行為,因為它經常會失敗,所以我發現這是最通用的解決方案.
To the draggables (fancyboxes), I bind a handler for dragstart
. We set the dataTransfer to copy a blank string of HTML ' ' -- because we need to trick it into thinking we are going to drag HTML, but we are cancelling out any default behavior. Sometimes default behavior slips in somehow, and it results in a duplicate (as we do the insertion ourselves), so now the worst glitch is a ' ' (space) being inserted when a drag fails. We couldn't rely on the default behavior, as it would fail to often, so I found this to be the most versatile solution.
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 中一樣,當您嘗試將其拖到 contenteditable 之外時,拖放將起作用(Chrome 默認拒絕您),因此它會針對 Firefox-only relatedTarget
執行快速檢查.呵呵.
To the dropzones, I bind a handler for dragleave
and drop
. The dragleave handler exists only for Firefox, as in Firefox, the drag-drop would work (Chrome denies you by default) when you tried to drag it outside the contenteditable, so it performs a quick check against the Firefox-only relatedTarget
. Huff.
Chrome 和 Firefox 獲取 Range 對象的方式不同,因此必須努力為每個瀏覽器在 drop 事件中采取不同的方式.Chrome 基于鼠標坐標 (是的,沒錯) 構建了一個范圍,但 Firefox 在事件數據中提供了它.document.execCommand('insertHTML',false,blah)
原來是我們處理 drop 的方式.哦,我忘了提——我們不能在 Chrome 上使用 dataTransfer.getData()
來獲取我們的 dragstart 設置 HTML——這似乎是規范中的某種奇怪的錯誤.Firefox 將規范稱為廢話,并無論如何都會給我們數據——但 Chrome 沒有,所以我們向后彎腰,將內容設置為全局,并通過地獄殺死所有默認行為......
Chrome and Firefox have different ways of acquiring the Range object, so effort had to be put in to do it differently for each browser in the drop event. Chrome builds a range based on mouse-coordinates (yup that's right), but Firefox provides it in the event data. document.execCommand('insertHTML',false,blah)
turns out to be how we handle the drop. OH, I forgot to mention -- we can't use dataTransfer.getData()
on Chrome to get our dragstart set HTML -- it appears to be some kind of weird bug in the specification. Firefox calls the spec out on it's bullcrap and gives us the data anyways -- but Chrome doesn't, so we bend over backwards and to set the content to a global, and go through hell to kill all the default behavior...
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();
});
好吧,我受夠了.我已經寫了我想寫的所有內容.我正在收工,如果我想到任何重要的事情,可能會更新此內容.
謝謝大家.//追逐.
這篇關于在 contenteditable 中精確拖放的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!