62

セットアップ

それで、私は 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);、ユーザーの選択に使用します。

望ましい機能

これで、ユーザーは派手な画像を挿入できるようになりました。ユーザーは画像を移動できる必要があります。ユーザーは、画像 (ファンシー ボックスなど) をクリック アンド ドラッグして、contenteditable 内の好きな場所に配置できる必要があります。段落間、または段落内でも、必要に応じて 2 つの単語間で移動できる必要があります。

私に希望を与えるもの

心に留めておいてください -- contenteditable では、単純な古い<img>タグは、この素敵なドラッグ アンド ドロップ機能を備えたユーザー エージェントによって既に祝福されています。デフォルトでは、好き<img>な場所にタグをドラッグ アンド ドロップできます。デフォルトのドラッグ アンド ドロップ操作は、夢のように動作します。

したがって、このデフォルトの動作がすでに<img>仲間に非常にうまく機能していることを考えると、この動作を少し拡張して HTML を少し追加したいだけなので、これは簡単に実現できるように思えます。

これまでの私の取り組み

最初に、draggable 属性を使用して派手なタグを設定し、<a>contenteditable を無効にしました (それが必要かどうかはわかりませんが、オフにしたほうがよいようです)。

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

次に、ユーザーは引き続き画像をファンシー<a>ボックスからドラッグできるため、CSS を実行する必要がありました。私は Chrome で作業しているので、他のプレフィックスも使用しましたが、-webkit- プレフィックスのみを表示しています。

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

これで、ユーザーはファンシー ボックス全体をドラッグできるようになり、少し部分的に色あせたクリック アンド ドラッグ表現の画像がこれを反映しています。ボックス全体を選択していることがわかります :)

さまざまな CSS プロパティのいくつかの組み合わせを試しましたが、上記の組み合わせが理にかなっており、最もうまく機能しているようです。

この CSS だけで、ブラウザが要素全体をドラッグ可能なアイテムとして使用し、私が夢見ていた機能をユーザーに自動的に付与するのに十分であることを望んでいました...しかし、それよりも複雑なようです。

HTML5 の JavaScript ドラッグ アンド ドロップ 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');
    });

ここで私は立ち往生しています。これはほぼ完全に機能します。ほぼ完全に。最後まで、すべてが機能しています。ドロップ イベントでは、ドロップ位置に挿入しようとしているファンシー ボックスの HTML コンテンツにアクセスできるようになりました。あとは、正しい位置に挿入するだけです!

問題は、正しいドロップ場所、またはそこに挿入する方法が見つからないことです。ファンシーボックスをダンプするためのある種の「dropLocation」dropEvent.dropLocation.content=myFancyBoxHTML;オブジェクトを見つけたいと思っていました。 私は何かを与えられていますか?

私はそれを完全に間違っていますか?私は何かを完全に見逃していますか?

できるはずだと思っていたように使用しようとしましdocument.execCommand('insertHTML',false,content);たが、残念ながら、選択キャレットが希望どおりの正確なドロップ位置に配置されていないため、ここでは失敗します。

すべての 's をコメントアウトするevent.preventDefault();と、選択キャレットが表示されることを発見しました。希望どおり、ユーザーがドロップする準備をしているときに、ドラッグを contenteditable の上に置くと、小さな選択キャレットが文字間に沿って表示されることがわかります。ユーザーのカーソルとドロップ操作に従います。選択キャレットが正確なドロップ位置を表していることをユーザーに示します。この選択キャレットの場所が必要です。

いくつかの実験では、drop イベントと dragend イベントの間に execCommand-insertHTML'ing を試しました。dropping-selection-caret があった場所に HTML を挿入するのではなく、ドラッグ操作の前に選択された場所を使用します。

ドラッグオーバー中に選択キャレットが表示されるので、計画を立てました。

しばらくの間、ドラッグ オーバー イベントで、 の<span class="selection-marker">|</span>直後にのような一時マーカーを挿入$('.selection-marker').remove();しようとしていました。これは、ブラウザーが常に (ドラッグ オーバー中に) すべての選択マーカーを削除してから、挿入ポイントに 1 つ追加しようとするためです。基本的に、その挿入ポイントがどこにあっても、いつでも 1 つのマーカーを残します。もちろん、計画は、この一時的なマーカーを、私が持っているドラッグされたコンテンツに置き換えることでした.

もちろん、これはどれも機能しませんでした。選択マーカーを計画どおりに明らかに表示されている選択キャレットに挿入することができませんでした。ここでも、ドラッグ操作の前に、execCommand-insertedHTML が選択キャレットがあった場所に配置されました。

ハフ。それで、私は何を逃したのですか?それはどのように行われますか?

ドラッグ アンド ドロップ操作の正確な位置を取得または挿入するにはどうすればよいですか? これは、明らかに、ドラッグ アンド ドロップの一般的な操作のように感じます。確かに、ある種の重要で露骨な詳細を見落としたに違いありませんか? 私は JavaScript に深く入り込む必要さえありましたか、それとも、ドラッグ可能、ドロップ可能、コンテンツ編集可能、そして派手な CSS3 などの属性だけでこれを行う方法があるのでしょうか?

私はまだ探しています - まだいじくり回しています - 私が失敗しているものを見つけたらすぐに投稿します:)


The Hunt Continues (元の投稿後に編集)


Farrukh は良い提案を投稿しました -- 以下を使用してください:

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

選択キャレットが実際にどこにあるかを確認します。これは、選択キャレットが contenteditable 内の編集可能なコンテンツ間を目に見えて飛び回っていることがわかったときです

残念ながら、返される Range オブジェクトは、ドラッグ アンド ドロップ操作の前に、選択キャレットに属するオフセット インデックスを報告します。

それは勇敢な努力でした。ありがとうファルク。

それで、ここで何が起こっているのですか?飛び回っている小さな選択キャレットが、選択キャレットではないという感覚が得られます。詐欺師だと思います!

さらに検査すると!

結局のところ、それは詐欺師です!ドラッグ操作全体を通して、実際の選択キャレットはそのまま残ります。あなたは小さなバガーを見ることができます!

私はMDN Drag and Drop Docsを読んでいて、これを見つけました:

当然、ドラッグオーバー イベントの周りで挿入マーカーを移動する必要がある場合もあります。他のマウス イベントと同様に、イベントの clientX および clientY プロパティを使用して、マウス ポインターの位置を特定できます。

ええ、これはclientXclientYに基づいて、自分でそれを理解することになっているということですか?? マウス座標を使用して選択キャレットの位置を自分で決定しますか? 怖い!!

私は明日そうすることにします-私自身、またはこれを読んでいる他の誰かが正気の解決策を見つけることができない限り:)

4

4 に答える 4

41

ドラゴンドロップ

私はばかげた量のいじりをしました。だから、そんなにjsFiddler。

これは堅牢な、または完全なソリューションではありません。私はまったく思いつかないかもしれません。誰かがより良い解決策を持っているなら、私はすべて耳を傾けています-私はこのようにする必要はありませんでしたが、これまでに発見できた唯一の方法です. 次のjsFiddleと、これから吐き出そうとする情報は、特定のWAMPセットアップとコンピューターで特定のバージョンのFirefoxとChromeを使用したこの特定のインスタンスで機能しました。 あなたのウェブサイトでうまくいかなくても、泣かないでください。このドラッグアンドドロップのがらくたは、明らかにすべての人が自分のために持っています.

jsFiddle: Moskal の Dragon Drop を追跡する

それで、私はガールフレンドの頭脳を退屈させていました. 彼女は私が「ドラゴンドロップ」と言い続けていると思っていました. それがスタックしたので、これらのドラッグ アンド ドロップの状況を処理するために作成した小さな JavaScript 仲間と呼んでいるものです。

結局のところ、それはちょっとした悪夢です。HTML5 のドラッグ アンド ドロップ API は、一見しただけでもひどいものです。次に、それが機能するはずの方法を理解し、受け入れ始めると、ほとんどウォームアップします.. その後、Firefox と Chrome が独自の特別な仕様でこの仕様にどのように取り組んでいるかを学ぶにつれて、それが実際にどれほど恐ろしい悪夢であるかを理解します.方法、そしてあなたのすべてのニーズを完全に無視しているようです. 「待って、今ドラッグされている要素は何ですか? その情報を取得するにはどうすればよいですか? このドラッグ操作をキャンセルするにはどうすればよいですか? この状況に対するこの特定のブラウザー固有のデフォルト処理を停止するにはどうすればよいですか?」 ... あなたの質問への答え: 「あなたは独りで、敗者! 何かがうまくいくまで、ハッキングを続けてください!」.

そこで、複数の contenteditable 内、周囲、およびそれらの間で、任意の HTML 要素の正確なドラッグ アンド ドロップを どのように達成したかを次に示します。(注:すべての詳細について完全に掘り下げているわけではありません。そのためにはjsFiddleを確認する必要があります。時間が限られているため、経験から覚えている一見関連性のある詳細をとりとめのないものにしています。 )

私の解決策

  • まず、CSS をドラッグ可能な要素 (ファンシーボックス) に適用しました。ファンシー ボックスに必要user-select:none; user-drag:element;であり、次にuser-drag:none;ファンシー ボックス内の画像 (およびその他の要素) に特に必要でした。残念ながら、これは Firefox では十分ではありませんでした。Firefox ではdraggable="false"、画像をドラッグできないようにするために、画像に属性を明示的に設定する必要がありました。
  • 次に、属性draggable="true"dropzone="copy"contenteditables に適用しました。

ドラッグ可能 (fancyboxes) に、 のハンドラーをバインドしdragstartます。HTML ' ' の空白文字列をコピーするように dataTransfer を設定します。これは、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');
    }
});

ドロップゾーンには、 と のハンドラーをバインドしdragleaveますdropdragleave ハンドラーは Firefox にのみ存在します。Firefox では、コンテンツ編集可能の外にドラッグしようとするとドラッグドロップが機能するため (Chrome はデフォルトで拒否します)、Firefox-only に対してクイックチェックを実行しrelatedTargetます。ハフ。

Chrome と Firefox では Range オブジェクトを取得する方法が異なるため、ドロップ イベントでブラウザーごとに異なる方法で取得するように努力する必要がありました。Chrome はマウス座標 に基づいて範囲を構築します(そうです)が、Firefox はイベント データでそれを提供します。document.execCommand('insertHTML',false,blah)私たちがドロップを処理する方法であることが判明しました。言い忘れていましたがdataTransfer.getData()、Chrome でドラッグスタート セットの HTML を取得することはできません。これは、仕様の奇妙なバグのようです。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();
});

さて、私はこれにうんざりしています。これについて書きたいことはすべて書きました。私はそれを一日と呼んでいます。何か重要なことを考えたら、これを更新するかもしれません.

みんなありがとう。//追跡。

于 2013-02-05T04:54:49.550 に答える
0
  1. イベントドラッグスタート;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);
于 2014-08-10T09:28:57.460 に答える