65 votes

Drag and Drop précis dans un Contenteditable

Le Programme D'Installation

Donc, j'ai un contenteditable div -- je suis en train de faire un éditeur WYSIWYG: gras, italique, mise en forme, que ce soit, et plus récemment: l'insertion de la fantaisie des images (dans une jolie boîte, avec une légende).

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

L'utilisateur ajoute ces fantaisie des images avec une boîte de dialogue je vous les présente avec: ils remplissez les détails, télécharger l'image, et puis comme les autres fonctions de l'éditeur, je l'utilise en document.execCommand('insertHTML',false,fancy_image_html); de plop à la sélection de l'utilisateur.

Les Fonctionnalités Souhaitées

Donc, maintenant que mon utilisateur peut plop dans une fantaisie de l'image -- dont ils ont besoin pour être en mesure de le déplacer. L'utilisateur doit être en mesure de cliquer et de faire glisser l'image (de fantaisie boîte et tout et tout) pour le placer n'importe où qu'ils s'il vous plaît dans le contenteditable. Ils doivent être en mesure de le déplacer entre les paragraphes, ou même à l'intérieur des paragraphes, entre deux mots, s'ils le souhaitent.

Ce qui me Donne de l'Espoir

Gardez à l'esprit, dans un contenteditable, plaine vieux - <img> balises sont déjà béni par l'agent utilisateur avec cette jolie glisser-déposer. Par défaut, vous pouvez faire glisser et déposer <img> tags partout où vous s'il vous plaît; la valeur par défaut de glisser-déposer l'opération se comporte comme un rêve.

Aussi, compte tenu de la façon dont ce comportement par défaut fonctionne déjà de manière smashingly sur notre <img> copains -- et je ne veux étendre ce comportement un peu pour inclure un peu plus de HTML -- cela semble être quelque chose qui devrait être facilement possible.

Mes Efforts Jusqu'À Présent

Tout d'abord, j'ai créé ma fantaisie <a> de la balise avec l'attribut draggable, et désactivé contenteditable (pas sûr si c'est nécessaire, mais il me semble qu'il peut aussi bien être éteint):

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

Ensuite, parce que l'utilisateur pourrait encore faire glisser l'image de la fantaisie <a> boîte, j'ai eu à faire un peu de CSS. Je travaille dans Chrome, donc je ne fais que vous montrer l'-webkit - préfixes, bien que j'ai utilisé les autres aussi.

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

Maintenant l'utilisateur peut faire glisser l'ensemble de la fantaisie de la boîte, et le petit partiellement décoloré cliquer-glisser de la représentation de l'image reflète ce, je m'aperçois que je suis à ramasser l'ensemble de la zone de maintenant :)

J'ai essayé plusieurs combinaisons de différentes propriétés CSS, le combo semble faire sens pour moi, et semble mieux fonctionner.

J'espérais que cette CSS, seul, serait suffisant pour le navigateur à utiliser la totalité de l'élément de l'élément déplaçable, automatiquement l'octroi de l'utilisateur de la fonctionnalité j'ai rêvé de... par contre, Il n'apparaît plus compliqué que ça.

HTML5 JavaScript API de Glisser-Déposer

Ce faire Glisser et Déposer des choses semble plus compliqué qu'il doit être.

Donc, j'ai commencé à entrer en profondeur dans Mdn api docs, et maintenant je suis coincé. Donc, voici ce que j'ai affublé (oui, 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');
    });

Donc voilà où j'en suis coincé: Ce presque complètement œuvres. Presque complètement. J'ai tout qui fonctionne, jusqu'à la toute fin. Dans la liste des événements, j'ai maintenant accès à la fantaisie de la zone de contenu HTML que je suis en train d'avoir inséré à l'emplacement de dépôt. Tout ce que je dois faire maintenant, c'est l'insérer au bon endroit!

Le problème est que je ne peux pas trouver le bon emplacement de dépôt, ou de toute façon à l'insérer à elle. J'ai été en espérant y trouver une sorte de 'dropLocation" objet pour vider mon costume de boîte à, quelque chose comme dropEvent.dropLocation.content=myFancyBoxHTML;, ou peut-être, au moins, un certain type d'emplacement de dépôt de valeurs qui trouver ma propre façon de mettre le contenu là-bas? Suis-je donné quelque chose?

Suis-je le mal? Suis-je complètement à côté de quelque chose?

J'ai essayé d'utiliser document.execCommand('insertHTML',false,content); comme je l'ai prévu, je devrais être en mesure de, mais malheureusement il me manque ici, comme la sélection de l'accent circonflexe n'est pas situé précisément à la chute de l'emplacement que j'avais de l'espoir.

J'ai découvert que si je commentaire de tous les de la event.preventDefault();s', la sélection curseur devient visible, et qu'on pourrait l'espérer, lorsque l'utilisateur se prépare à la baisse, passant la faites glisser sur la contenteditable, la petite sélection de signe peut être vu courir le long de entre les caractères suivants de l'utilisateur curseur et déplacer -- indiquant à l'utilisateur que la sélection du curseur représente le précis de l'emplacement de dépôt. J'ai besoin de l'emplacement de cette sélection curseur.

Avec quelques expériences, j'ai essayé execCommand-insertHTML pratiquent pendant la chute de l'événement, et la dragend événement -- ni insérer le code HTML où l'abandon de sélection-l'accent circonflexe a été, au contraire, il utilise tout ce emplacement a été choisi avant l'opération de glisser.

Parce que la sélection curseur est visible pendant dragover, j'ai échafaudé un plan.

Pendant un certain temps, j'ai essayé, dans l'événement dragover, pour insérer un marqueur temporaire, comme <span class="selection-marker">|</span>, juste après l' $('.selection-marker').remove();, dans une tentative pour le navigateur en permanence (pendant dragover) supprimer tous les marqueurs de sélection, puis en ajoutant un au point d'insertion-pour l'essentiel, laissant un marqueur à chaque fois que le point d'insertion sont, à tout moment. Le plan de cours, a été puis remplacer cette inscription temporaire avec la traîné de contenu que j'ai.

Rien de tout cela fonctionné, bien sûr: je ne pouvais pas obtenir la sélection-marqueur à insérer, à l'apparence visible de sélection lambda comme prévu, encore une fois, la execCommand-insertedHTML lui-même placé là où le curseur de sélection a été, avant l'opération de glisser.

Huff. Alors, qu'ai-je manqué? Comment est-il fait?

Comment puis-je obtenir, ou insérez-le dans, la localisation précise d'un glisser-déposer de l'opération? Je sens que c'est, de toute évidence, une opération commune entre les drag-and-drop -- oui, je dois avoir oublié un important et le plus flagrant de détail de quelque sorte? Je n'ai même pas avoir à pénétrer profondément dans le JavaScript, ou peut-être il y a un moyen de le faire juste avec des attributs comme draggable, droppable, contenteditable, et certains fancydancy CSS3?

Je suis toujours à la recherche -- encore bricoler -- je vais poster dès que je trouve ce que je ai été en situation d'échec :)

Merci beaucoup pour la lecture; l'aide est grandement appréciée :)

//Chase.



La traque Continue (modifications après le post original)


Farrukh posté une bonne suggestion d'utilisation:

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

Pour voir où est le curseur de sélection est en réalité. Je laissa dans la dragover événement, qui est que quand je me figure la sélection caret est visibily sautillant entre mon contenu modifiable dans le contenteditable.

Hélas, l'objet Range qui est retourné, les rapports de décalage des indices qui appartiennent à la sélection de signe avant de le glisser-déposer.

C'était un vaillant effort. Grâce Farrukh.

Alors que ce passe ici? Je suis arriver a la sensation que la petite sélection caret je vois sautiller, n'est-ce pas le curseur de sélection à tous! Je pense que c'est un imposteur!

Après Une Inspection Plus Poussée!

S'avère, il est un imposteur! Le réel de sélection curseur reste en place au cours de l'ensemble de l'opération de glisser! Vous pouvez voir le petit bougre!

J'ai lu le MDN faire Glisser et Déposer des Docs, et trouvé ceci:

Naturellement, vous pouvez avoir besoin de déplacer le marqueur d'insertion autour d'un événement dragover ainsi. Vous pouvez utiliser l'événement de clientX et clientY propriétés comme avec d'autres événements de la souris pour déterminer l'emplacement du pointeur de la souris.

Aïe, est-ce à dire que je suis censé pour comprendre, pour moi-même, basée sur clientX et clientY?? En utilisant les coordonnées de la souris pour déterminer l'emplacement de la sélection caret moi-même? Effrayant!!

Je vais regarder dans le faire demain, à moins que de moi-même, ou quelqu'un d'autre ici la lecture de ce, peut trouver un sane solution :)

Merci tout le monde. Je vais mettre à jour demain. //Chase.

42voto

ChaseMoskal Points 893

Dragon Drop

J'ai fait un montant ridicule de se tripoter. Tellement, tellement jsFiddling.

Ce n'est pas un solide, ou solution complète; j'ai peut-être jamais tout à fait venir avec un. Si quelqu'un a des meilleures solutions, je suis preneuse -- je n'avais pas envie de faire de cette façon, mais c'est la seule façon que j'ai pu découvrir jusqu'à présent. La suite de jsFiddle, et les informations que je suis sur le point de vomir en place, a travaillé pour moi dans ce cas particulier avec mes versions de Firefox et de google Chrome sur mon WAMP de l'installation et de l'ordinateur. Ne venez pas pleurer pour moi quand ça ne fonctionne pas sur votre site. Ce glisser-déposer de la merde est clairement chaque homme pour lui-même.

jsFiddle: Chase Moskal du Dragon Drop

Donc, j'ai été chiante ma copine cervelle, et elle me disait "dragon drop", alors qu'en réalité, j'étais juste en disant "glisser-déposer". Il coincer, alors c'est ce que j'appelle mon petit bout de JavaScript copain que j'ai créé pour la manipulation de ces glisser-déposer et de situations.

S'avère -- c'est un peu un cauchemar. Le HTML5 Drag-and-Drop de l'API, même à première vue, est horrible. Ensuite, vous avez presque chaud jusqu'à elle, vous commencez à comprendre et à accepter la façon dont il est censé travailler.. Puis vous vous rendez compte qu'un terrifiant cauchemar, il est en fait, que vous apprenez comment Firefox et Chrome aller sur cette spécification dans leur propre manière spéciale, et semblent ignorer complètement tous vos besoins. Vous vous posez des questions comme: "Attendez, quel élément est même glissé à droite maintenant? Comment puis-je obtenir cette information? Comment puis-je annuler cette opération de glisser? Comment puis-je arrêter ce navigateur unique de gestion par défaut de cette situation?"... Les réponses à vos questions: "Vous êtes sur votre propre, PERDANT! Garder le piratage des choses, jusqu'à ce que quelque chose fonctionne!".

Donc, voici comment j'ai réalisé Précis de Glisser et de Déposer de l'Arbitraire des Éléments HTML à l'intérieur, autour et entre plusieurs contenteditable de l'. (note: je ne suis pas d'aller en profondeur avec tous les détails, vous aurez à regarder le jsFiddle pour que -- je suis juste la randonnée hors apparemment les détails pertinents que je me souviens de l'expérience, comme je dispose de peu de temps)

Ma Solution

  • Tout d'abord, j'ai appliqué de la CSS à la draggables (fancybox) -- nous avons besoin d' user-select:none; user-drag:element; sur la fantaisie de la zone, puis spécifiquement user-drag:none; sur l'image dans la boîte de fantaisie (et tous les autres éléments, pourquoi pas?). Malheureusement, ce n'était pas assez pour Firefox, qui ont requis l'attribut draggable="false" à être définie explicitement sur l'image pour l'empêcher d'être déplaçable.
  • Ensuite, j'ai appliqué les attributs draggable="true" et dropzone="copy" sur le contenteditables.

À la draggables (fancyboxes), je la lie un gestionnaire pour dragstart. Nous avons mis la dataTransfer pour copier une chaîne vide de HTML '' -- parce que nous avons besoin de le tromper en pensant que nous allons glisser HTML, mais nous sommes à l'annulation de tout comportement par défaut. Parfois, le comportement par défaut glisse en quelque sorte, et il en résulte un double (comme nous le faisons à l'insertion de nous-mêmes), alors maintenant, le pire glitch est un '' (espace) être inséré lors d'un glisser échoue. Nous ne pouvions pas compter sur le comportement par défaut, car il ne pourrait pas souvent, donc j'ai trouvé ceci pour être la solution la plus polyvalente.

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');
    }
});

Pour les centres, je la lie un gestionnaire pour dragleave et drop. Le dragleave gestionnaire n'existe que pour Firefox, comme dans Firefox, la fonction de glisser-déposer, serait de travailler (Chrome refuse vous par défaut) lorsque vous avez essayé de glisser en dehors de l'contenteditable, de sorte qu'il effectue une vérification rapide contre la Firefox seulement relatedTarget. Huff.

Chrome et Firefox ont différents modes d'acquisition de l'objet de la Plage, de sorte efforts devaient être mis en pour le faire différemment pour chaque navigateur dans le menu de l'événement. Chrome construit une gamme basée sur la souris-les coordonnées (ouais c'est vrai), mais Firefox fournit des données de l'événement. document.execCommand('insertHTML',false,blah) s'avère être la façon dont nous traitons la baisse. OH, j'ai oublié de mentionner -- nous ne pouvons pas utiliser dataTransfer.getData() sur Chrome pour obtenir notre dragstart définir HTML -- il semble que ce soit une sorte de bug bizarre dans le cahier des charges. Firefox appelle la spec sur c'est bullcrap et nous fournit les données de toute façon-mais Chrome ne fonctionne pas, nous avons donc se plier en quatre et pour définir le contenu d'un mondial, et de passer par l'enfer pour tuer tous le comportement par défaut...

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();
});

D'accord, je suis malade de cette. J'ai écrit tout ce que je veux à ce sujet. Je suis de l'appeler un jour, et peut mettre à jour cette si je pense à quelque chose d'important.

Merci tout le monde. //Chase.

0voto

holistic Points 1
  1. dragstart d'événement; dataTransfer.setData("text/html", "<div class='whatever'></div>");
  2. chute d'événement: 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);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X