JavaScript obtient les données du presse-papiers sur l'événement de collage (navigateur croisé)

299

Comment une application Web peut-elle détecter un événement de collage et récupérer les données à coller?

Je souhaite supprimer le contenu HTML avant de coller le texte dans un éditeur de texte enrichi.

Le nettoyage du texte après avoir été collé par la suite fonctionne, mais le problème est que tout le formatage précédent est perdu. Par exemple, je peux écrire une phrase dans l'éditeur et la mettre en gras, mais lorsque je colle un nouveau texte, tout le formatage est perdu. Je souhaite nettoyer uniquement le texte collé et laisser intacte toute mise en forme précédente.

Idéalement, la solution devrait fonctionner sur tous les navigateurs modernes (par exemple, MSIE, Gecko, Chrome et Safari).

Notez que MSIE l'a fait clipboardData.getData(), mais je n'ai pas pu trouver de fonctionnalités similaires pour d'autres navigateurs.

Alex
la source
Toutes ces réponses expliquent comment obtenir du contenu texte. Obtenir du contenu d'image ou du contenu de fichier nécessite beaucoup plus de travail. Peut-être que nous pouvons changer le titre en "JavaScript obtenir des données de presse-papiers de texte aseptisé ..."
1,21 gigawatts
1
comme l'a dit nico: event.clipboardData.getData('Text')travaillé pour moi.
Andre Elrico
document.addEventListener('paste'...a fonctionné pour moi mais a provoqué des conflits si un utilisateur voulait pouvoir coller ailleurs sur la page. Ensuite, j'ai essayé myCanvasElement.addEventListener('paste'..., mais cela n'a pas fonctionné. Finalement, j'ai compris que cela myCanvasElement.parentElement.addEventListener('paste'...fonctionnait.
Ryan

Réponses:

149

La situation a changé depuis la rédaction de cette réponse: maintenant que Firefox a ajouté la prise en charge dans la version 22, tous les principaux navigateurs prennent désormais en charge l'accès aux données du presse-papiers dans un événement de collage. Voir la réponse de Nico Burns pour un exemple.

Dans le passé, cela n'était généralement pas possible de manière croisée. L'idéal serait de pouvoir récupérer le contenu collé via l' pasteévénement, ce qui est possible dans les navigateurs récents mais pas dans certains navigateurs plus anciens (notamment Firefox <22).

Lorsque vous devez prendre en charge des navigateurs plus anciens, ce que vous pouvez faire est très impliqué et un peu un hack qui fonctionnera dans les navigateurs Firefox 2+, IE 5.5+ et WebKit tels que Safari ou Chrome. Les versions récentes de TinyMCE et CKEditor utilisent cette technique:

  1. Détecter un événement ctrl-v / shift-ins à l'aide d'un gestionnaire d'événements keypress
  2. Dans ce gestionnaire, enregistrez la sélection de l'utilisateur actuel, ajoutez un élément de zone de texte hors écran (disons à gauche -1000 px) au document, designModedésactivez-le et appelezfocus() la zone de texte, déplaçant ainsi le curseur et redirigeant efficacement la pâte
  3. Définissez une minuterie très brève (disons 1 milliseconde) dans le gestionnaire d'événements pour appeler une autre fonction qui stocke la valeur de la zone de texte, supprime la zone de texte du document, la rallume designMode, restaure la sélection de l'utilisateur et colle le texte.

Notez que cela ne fonctionnera que pour les événements de collage au clavier et non les pâtes du menu contextuel ou d'édition. Au moment où l'événement coller se déclenche, il est trop tard pour rediriger le curseur dans la zone de texte (dans certains navigateurs, au moins).

Dans le cas peu probable où vous devez prendre en charge Firefox 2, notez que vous devrez placer la zone de texte dans le document parent plutôt que le document iframe de l'éditeur WYSIWYG dans ce navigateur.

Tim Down
la source
1
Wow, merci pour ça! Il semble que ce soit un hack très sophistiqué ;-) Pourriez-vous décrire un peu plus ce designMode et cette sélection, en particulier à l'étape 3? Merci beaucoup!
Alex
5
J'ai eu un sentiment horrible que vous me demandiez cela. Comme je l'ai dit, c'est assez compliqué: je suggère de regarder la source de TinyMCE ou CKEditor, car je n'ai pas le temps de décrire tous les problèmes impliqués. Brièvement cependant, designModeest une propriété booléenne de documentet rend la page entière modifiable quand true. Les éditeurs WYSIWYG utilisent généralement une iframe avec designModeon comme volet modifiable. L'enregistrement et la restauration de la sélection des utilisateurs se font d'une manière dans IE et d'une autre dans d'autres navigateurs, tout comme le collage du contenu dans l'éditeur. Vous devez obtenir un TextRangedans IE et un Rangedans d'autres navigateurs.
Tim Down
6
@Samuel: Vous pouvez le détecter en utilisant l' pasteévénement mais il est généralement trop tard pour rediriger la pâte vers un autre élément, donc ce hack ne fonctionnera pas. La solution de rechange dans la plupart des éditeurs consiste à afficher une boîte de dialogue dans laquelle l'utilisateur peut coller.
Tim Down le
6
Quelques informations supplémentaires à ce sujet: Firefox ne vous permettra pas de déplacer le focus vers un autre élément dans l' pasteévénement, mais cela vous permettra d'effacer le contenu de l'élément (et de l'enregistrer dans une variable afin de pouvoir le restaurer plus tard). Si ce conteneur est un div(il fonctionne probablement pour un iframeaussi), vous pouvez alors parcourir le contenu collé à l'aide des méthodes dom normales, ou le récupérer sous forme de chaîne innerHTML. Vous pouvez ensuite restaurer le contenu précédent de la divet insérer le contenu que vous souhaitez. Oh, et vous devez utiliser le même hack de minuterie que ci-dessus. Je suis surpris que TinyMCE ne fasse pas ça ...
Nico Burns
8
@ResistDesign: Je ne suis pas d'accord - c'est une manière inélégante et compliquée de compenser le manque d'une API sensible. Il serait préférable de pouvoir obtenir le contenu collé directement à partir de l'événement de collage, ce qui est possible de manière limitée dans certains navigateurs .
Tim Down
318

Solution n ° 1 (texte brut uniquement et nécessite Firefox 22+)

Fonctionne pour IE6 +, FF 22+, Chrome, Safari, Edge (testé uniquement dans IE9 +, mais devrait fonctionner pour les versions inférieures)

Si vous avez besoin de support pour coller du HTML ou Firefox <= 22, consultez la solution n ° 2.

HTML

<div id='editableDiv' contenteditable='true'>Paste</div>

Javascript

function handlePaste (e) {
    var clipboardData, pastedData;

    // Stop data actually being pasted into div
    e.stopPropagation();
    e.preventDefault();

    // Get pasted data via clipboard API
    clipboardData = e.clipboardData || window.clipboardData;
    pastedData = clipboardData.getData('Text');
    
    // Do whatever with pasteddata
    alert(pastedData);
}

document.getElementById('editableDiv').addEventListener('paste', handlePaste);

JSFiddle: https://jsfiddle.net/swL8ftLs/12/

Notez que cette solution utilise le paramètre 'Text' pour la getDatafonction, qui n'est pas standard. Cependant, cela fonctionne dans tous les navigateurs au moment de la rédaction.


Solution # 2 (HTML et fonctionne pour Firefox <= 22)

Testé dans IE6 +, FF 3.5+, Chrome, Safari, Edge

HTML

<div id='div' contenteditable='true'>Paste</div>

Javascript

var editableDiv = document.getElementById('editableDiv');

function handlepaste (e) {
    var types, pastedData, savedContent;
    
    // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
    if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
            
        // Check for 'text/html' in types list. See abligh's answer below for deatils on
        // why the DOMStringList bit is needed. We cannot fall back to 'text/plain' as
        // Safari/Edge don't advertise HTML data even if it is available
        types = e.clipboardData.types;
        if (((types instanceof DOMStringList) && types.contains("text/html")) || (types.indexOf && types.indexOf('text/html') !== -1)) {
        
            // Extract data and pass it to callback
            pastedData = e.clipboardData.getData('text/html');
            processPaste(editableDiv, pastedData);

            // Stop the data from actually being pasted
            e.stopPropagation();
            e.preventDefault();
            return false;
        }
    }
    
    // Everything else: Move existing element contents to a DocumentFragment for safekeeping
    savedContent = document.createDocumentFragment();
    while(editableDiv.childNodes.length > 0) {
        savedContent.appendChild(editableDiv.childNodes[0]);
    }
    
    // Then wait for browser to paste content into it and cleanup
    waitForPastedData(editableDiv, savedContent);
    return true;
}

function waitForPastedData (elem, savedContent) {

    // If data has been processes by browser, process it
    if (elem.childNodes && elem.childNodes.length > 0) {
    
        // Retrieve pasted content via innerHTML
        // (Alternatively loop through elem.childNodes or elem.getElementsByTagName here)
        var pastedData = elem.innerHTML;
        
        // Restore saved content
        elem.innerHTML = "";
        elem.appendChild(savedContent);
        
        // Call callback
        processPaste(elem, pastedData);
    }
    
    // Else wait 20ms and try again
    else {
        setTimeout(function () {
            waitForPastedData(elem, savedContent)
        }, 20);
    }
}

function processPaste (elem, pastedData) {
    // Do whatever with gathered data;
    alert(pastedData);
    elem.focus();
}

// Modern browsers. Note: 3rd argument is required for Firefox <= 6
if (editableDiv.addEventListener) {
    editableDiv.addEventListener('paste', handlepaste, false);
}
// IE <= 8
else {
    editableDiv.attachEvent('onpaste', handlepaste);
}

JSFiddle: https://jsfiddle.net/nicoburns/wrqmuabo/23/

Explication

L' onpasteévénement du diva la handlePastefonction qui lui est attachée et a passé un seul argument: l' eventobjet de l'événement coller. Un intérêt particulier pour nous est la clipboardDatapropriété de cet événement qui permet d'accéder au presse-papiers dans des navigateurs non-ie. Dans IE, l'équivalent est window.clipboardData, bien que celui-ci ait une API légèrement différente.

Voir la section des ressources ci-dessous.


La handlepastefonction:

Cette fonction a deux branches.

Le premier vérifie l'existence event.clipboardDataet vérifie si sa typespropriété contient 'text / html' ( typespeut être soit un DOMStringListqui est vérifié en utilisant la containsméthode, soit une chaîne qui est vérifiée en utilisant la indexOfméthode). Si toutes ces conditions sont remplies, nous procédons comme dans la solution n ° 1, sauf avec 'text / html' au lieu de 'text / plain'. Cela fonctionne actuellement dans Chrome et Firefox 22+.

Si cette méthode n'est pas prise en charge (tous les autres navigateurs), nous

  1. Enregistrez le contenu de l'élément dans un DocumentFragment
  2. Vider l'élément
  3. Appelez la waitForPastedDatafonction

La waitforpastedatafonction:

Cette fonction interroge d'abord les données collées (une fois toutes les 20 ms), ce qui est nécessaire car elles n'apparaissent pas immédiatement. Lorsque les données sont apparues, elles:

  1. Enregistre le innerHTML du div modifiable (qui est maintenant les données collées) dans une variable
  2. Restaure le contenu enregistré dans DocumentFragment
  3. Appelle la fonction 'processPaste' avec les données récupérées

La processpastefonction:

Fait des choses arbitraires avec les données collées. Dans ce cas, nous alertons simplement les données, vous pouvez faire ce que vous voulez. Vous souhaiterez probablement exécuter les données collées via une sorte de processus de nettoyage des données.


Sauvegarde et restauration de la position du curseur

Dans une situation réelle, vous voudrez probablement enregistrer la sélection avant et la restaurer après ( définir la position du curseur sur contentEditable <div> ). Vous pouvez ensuite insérer les données collées à la position où se trouvait le curseur lorsque l'utilisateur a lancé l'action de collage.

Ressources:

Merci à Tim Down d'avoir suggéré l'utilisation d'un DocumentFragment et abligh pour avoir détecté une erreur dans Firefox due à l'utilisation de DOMStringList au lieu d'une chaîne pour clipboardData.types

Nico Burns
la source
4
Intéressant. Je pensais avoir essayé cela dans le passé et cela n'avait pas fonctionné dans un navigateur, mais je suis sûr que vous avez raison. Je préférerais certainement déplacer le contenu existant vers un DocumentFragmentplutôt que de l'utiliser innerHTMLpour plusieurs raisons: d'abord, vous conservez tous les gestionnaires d'événements existants; deuxièmement, l'enregistrement et la restauration innerHTMLne sont pas garantis pour créer une copie identique du DOM précédent; troisièmement, vous pouvez ensuite enregistrer la sélection en tant que Rangeplutôt que d'avoir à vous déplacer avec l'ajout d'éléments de marqueur ou le calcul des décalages de texte (ce que vous auriez à faire si vous l'utilisiez innerHTML).
Tim Down du
3
Il y a en effet un flash sans contenu (FONC?), Qui sera évidemment pire si le traitement du contenu collé prend un certain temps. Btw, pourquoi extraire à DocumentFragmentune douleur dans IE? C'est la même chose que dans les autres navigateurs, sauf si vous utilisez une plage et extractContents()pour le faire, ce qui n'est pas plus concis que l'alternative dans tous les cas. J'ai implémenté un exemple de votre technique, en utilisant Rangy pour garder les choses agréables et uniformes sur tous les navigateurs: jsfiddle.net/bQeWC/4 .
Tim Down le
1
@Martin: La démo jsFiddle que j'ai postée dans les commentaires peut aider.
Tim Down
1
Il semble que cela ne fonctionne plus sur Firefox 28 (au moins) pour Windows. Il ne sort jamais de la waitforpastedatafonction
Oliboy50
1
FYI: Edge prend désormais en charge la lecture de données avec MIME-Type text/htmlà l'aide de l'API W3C Clipboard. Dans le passé, une telle tentative jetterait une exception. Donc, on n'a plus besoin de cette solution de contournement / hack pour Edge.
Jenny O'Reilly
130

Version simple:

document.querySelector('[contenteditable]').addEventListener('paste', (e) => {
    e.preventDefault();
    const text = (e.originalEvent || e).clipboardData.getData('text/plain');
    window.document.execCommand('insertText', false, text);
});

En utilisant clipboardData

Démo: http://jsbin.com/nozifexasu/edit?js,output

Testé sur Edge, Firefox, Chrome, Safari, Opera.

Document.execCommand () est désormais obsolète .


Remarque: n'oubliez pas de vérifier les entrées / sorties côté serveur également (comme les strip-tags PHP )

l2aelba
la source
4
Cela fonctionne vraiment bien, mais aucune version d'IE ne permet d'accéder au clipboardData de l'événement :( Excellente solution, cependant, cela devrait être plus élevé!
Eric Wood
1
Il semble que vous puissiez accéder aux données du presse-papiers dans IE d'une manière différente, donc si vous détectez IE, vous pouvez utiliser ces données au lieu de la solution de
Andrew
4
meilleure réponse croisée trouvée jusqu'à présent. ajoutez simplement le code pour IE et son parfait.
Arturo
6
Cela fonctionne dans IE (ah, doux, contraire IE)window.clipboardData.getData('Text');
Benjineer
9
e.preventDefault(); if (e.clipboardData) { content = (e.originalEvent || e).clipboardData.getData('text/plain'); document.execCommand('insertText', false, content); } else if (window.clipboardData) { content = window.clipboardData.getData('Text'); document.selection.createRange().pasteHTML(content); }
Yukulelix
26

Démo en direct

Testé sur Chrome / FF / IE11

Il y a une gêne pour Chrome / IE, c'est que ces navigateurs ajoutent un <div>élément pour chaque nouvelle ligne. Il y a un article à ce sujet ici et il peut être corrigé en définissant l' élément contenteditable surdisplay:inline-block

Sélectionnez du HTML en surbrillance et collez-le ici:

function onPaste(e){
  var content;
  e.preventDefault();

  if( e.clipboardData ){
    content = e.clipboardData.getData('text/plain');
    document.execCommand('insertText', false, content);
    return false;
  }
  else if( window.clipboardData ){
    content = window.clipboardData.getData('Text');
    if (window.getSelection)
      window.getSelection().getRangeAt(0).insertNode( document.createTextNode(content) );
  }
}


/////// EVENT BINDING /////////
document.querySelector('[contenteditable]').addEventListener('paste', onPaste);
[contenteditable]{ 
  /* chroem bug: https://stackoverflow.com/a/24689420/104380 */
  display:inline-block;
  width: calc(100% - 40px);
  min-height:120px; 
  margin:10px;
  padding:10px;
  border:1px dashed green;
}

/* 
 mark HTML inside the "contenteditable"  
 (Shouldn't be any OFC!)'
*/
[contenteditable] *{
  background-color:red;
}
<div contenteditable></div>

vsync
la source
1
J'avais besoin d'une pâte en tant que fonctionnalité de texte brut. Testé sur IE9 et IE10 et fonctionne très bien. Inutile de mentionner que cela fonctionne aussi sur les principaux navigateurs ... Merci.
Savas Vedova
2
Votre code contient un bogue: if (e.originalEvent.clipboardData) peut provoquer un NPE car vous ne savez pas si e.originalEvent existe à ce moment
Sebastian
15

J'ai écrit une petite preuve de concept pour la proposition de Tim Downs ici avec une zone de texte hors écran. Et voici le code:

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script> 
<script language="JavaScript">
 $(document).ready(function()
{

var ctrlDown = false;
var ctrlKey = 17, vKey = 86, cKey = 67;

$(document).keydown(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
    if (e.keyCode == ctrlKey) ctrlDown = false;
});

$(".capture-paste").keydown(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){
        $("#area").css("display","block");
        $("#area").focus();         
    }
});

$(".capture-paste").keyup(function(e)
{
    if (ctrlDown && (e.keyCode == vKey || e.keyCode == cKey)){                      
        $("#area").blur();
        //do your sanitation check or whatever stuff here
        $("#paste-output").text($("#area").val());
        $("#area").val("");
        $("#area").css("display","none");
    }
});

});
</script>

</head>
<body class="capture-paste">

<div id="paste-output"></div>


    <div>
    <textarea id="area" style="display: none; position: absolute; left: -99em;"></textarea>
    </div>

</body>
</html>

Copiez et collez tout le code dans un fichier html et essayez de coller (à l'aide de ctrl-v) du texte du presse-papiers n'importe où sur le document.

Je l'ai testé dans IE9 et dans les nouvelles versions de Firefox, Chrome et Opera. Fonctionne assez bien. Il est également bon que l'on puisse utiliser la combinaison de touches qu'il préfère pour améliorer cette fonctionnalité. Bien sûr, n'oubliez pas d'inclure les sources jQuery.

N'hésitez pas à utiliser ce code et si vous venez avec des améliorations ou des problèmes, merci de les poster. Notez également que je ne suis pas un développeur Javascript, donc j'ai peut-être raté quelque chose (=> faites votre propre testign).

JanM
la source
Les Mac ne collent pas avec ctrl-v, ils utilisent cmd-v. Définissez donc ctrlKey = 91 au lieu de 17
Jeremy T
2
Ou peut-être que ce n'est pas toujours 91: stackoverflow.com/questions/3834175/… Quoi qu'il en soit, je suis presque sûr que jQuery gère tout cela pour vous, vérifiez simplement e.ctrlKey ou e.metaKey je pense.
Jeremy T
3
e.ctrlKey ou e.metaKey fait partie du DOM JavaScript, pas jQuery: developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
rvighne
2
Je ne pense pas que cela fonctionne pour un clic droit et un collage. Beaucoup de gens adoptent cette approche.
Eric Wood
10

Basé sur la réponse l2aelba. Cela a été testé sur FF, Safari, Chrome, IE (8,9,10 et 11)

    $("#editText").on("paste", function (e) {
        e.preventDefault();

        var text;
        var clp = (e.originalEvent || e).clipboardData;
        if (clp === undefined || clp === null) {
            text = window.clipboardData.getData("text") || "";
            if (text !== "") {
                if (window.getSelection) {
                    var newNode = document.createElement("span");
                    newNode.innerHTML = text;
                    window.getSelection().getRangeAt(0).insertNode(newNode);
                } else {
                    document.selection.createRange().pasteHTML(text);
                }
            }
        } else {
            text = clp.getData('text/plain') || "";
            if (text !== "") {
                document.execCommand('insertText', false, text);
            }
        }
    });
tmorell
la source
Existe-t-il un moyen de conserver de nouvelles lignes lors du collage sur IE?
Staysee
10

Celui-ci n'utilise aucun setTimeout ().

J'ai utilisé cet excellent article pour obtenir une prise en charge multi-navigateurs.

$(document).on("focus", "input[type=text],textarea", function (e) {
    var t = e.target;
    if (!$(t).data("EventListenerSet")) {
        //get length of field before paste
        var keyup = function () {
            $(this).data("lastLength", $(this).val().length);
        };
        $(t).data("lastLength", $(t).val().length);
        //catch paste event
        var paste = function () {
            $(this).data("paste", 1);//Opera 11.11+
        };
        //process modified data, if paste occured
        var func = function () {
            if ($(this).data("paste")) {
                alert(this.value.substr($(this).data("lastLength")));
                $(this).data("paste", 0);
                this.value = this.value.substr(0, $(this).data("lastLength"));
                $(t).data("lastLength", $(t).val().length);
            }
        };
        if (window.addEventListener) {
            t.addEventListener('keyup', keyup, false);
            t.addEventListener('paste', paste, false);
            t.addEventListener('input', func, false);
        }
        else {//IE
            t.attachEvent('onkeyup', function () {
                keyup.call(t);
            });
            t.attachEvent('onpaste', function () {
                paste.call(t);
            });
            t.attachEvent('onpropertychange', function () {
                func.call(t);
            });
        }
        $(t).data("EventListenerSet", 1);
    }
}); 

Ce code est étendu avec la poignée de sélection avant de coller: démo

AsgarAli
la source
+1 J'aime mieux celui-ci que Nico Burns, bien que je pense que chacun a sa propre place.
n0nag0n
5

Pour nettoyer le texte collé et remplacer le texte actuellement sélectionné par le texte collé, la question est assez banale:

<div id='div' contenteditable='true' onpaste='handlepaste(this, event)'>Paste</div>

JS:

function handlepaste(el, e) {
  document.execCommand('insertText', false, e.clipboardData.getData('text/plain'));
  e.preventDefault();
}
Matt Crinklaw-Vogt
la source
Pouvez-vous fournir une page de démonstration où cela fonctionne? Je l'ai essayé et cela ne fonctionne pas
vsync
5

Cela devrait fonctionner sur tous les navigateurs qui prennent en charge l'événement onpaste et l'observateur de mutation.

Cette solution va au-delà de l'obtention du texte uniquement, elle vous permet en fait de modifier le contenu collé avant qu'il ne soit collé dans un élément.

Il fonctionne en utilisant un événement onpaste modifiable (pris en charge par tous les principaux navigateurs) et des observateurs de mutation (pris en charge par Chrome, Firefox et IE11 +)

étape 1

Créer un élément HTML avec contenteditable

<div contenteditable="true" id="target_paste_element"></div>

étape 2

Dans votre code Javascript, ajoutez l'événement suivant

document.getElementById("target_paste_element").addEventListener("paste", pasteEventVerifierEditor.bind(window, pasteCallBack), false);

Nous devons lier pasteCallBack, car l'observateur de mutation sera appelé de manière asynchrone.

étape 3

Ajoutez la fonction suivante à votre code

function pasteEventVerifierEditor(callback, e)
{
   //is fired on a paste event. 
    //pastes content into another contenteditable div, mutation observer observes this, content get pasted, dom tree is copied and can be referenced through call back.
    //create temp div
    //save the caret position.
    savedCaret = saveSelection(document.getElementById("target_paste_element"));

    var tempDiv = document.createElement("div");
    tempDiv.id = "id_tempDiv_paste_editor";
    //tempDiv.style.display = "none";
    document.body.appendChild(tempDiv);
    tempDiv.contentEditable = "true";

    tempDiv.focus();

    //we have to wait for the change to occur.
    //attach a mutation observer
    if (window['MutationObserver'])
    {
        //this is new functionality
        //observer is present in firefox/chrome and IE11
        // select the target node
        // create an observer instance
        tempDiv.observer = new MutationObserver(pasteMutationObserver.bind(window, callback));
        // configuration of the observer:
        var config = { attributes: false, childList: true, characterData: true, subtree: true };

        // pass in the target node, as well as the observer options
        tempDiv.observer.observe(tempDiv, config);

    }   

}



function pasteMutationObserver(callback)
{

    document.getElementById("id_tempDiv_paste_editor").observer.disconnect();
    delete document.getElementById("id_tempDiv_paste_editor").observer;

    if (callback)
    {
        //return the copied dom tree to the supplied callback.
        //copy to avoid closures.
        callback.apply(document.getElementById("id_tempDiv_paste_editor").cloneNode(true));
    }
    document.body.removeChild(document.getElementById("id_tempDiv_paste_editor"));

}

function pasteCallBack()
{
    //paste the content into the element.
    restoreSelection(document.getElementById("target_paste_element"), savedCaret);
    delete savedCaret;

    pasteHtmlAtCaret(this.innerHTML, false, true);
}   


saveSelection = function(containerEl) {
if (containerEl == document.activeElement)
{
    var range = window.getSelection().getRangeAt(0);
    var preSelectionRange = range.cloneRange();
    preSelectionRange.selectNodeContents(containerEl);
    preSelectionRange.setEnd(range.startContainer, range.startOffset);
    var start = preSelectionRange.toString().length;

    return {
        start: start,
        end: start + range.toString().length
    };
}
};

restoreSelection = function(containerEl, savedSel) {
    containerEl.focus();
    var charIndex = 0, range = document.createRange();
    range.setStart(containerEl, 0);
    range.collapse(true);
    var nodeStack = [containerEl], node, foundStart = false, stop = false;

    while (!stop && (node = nodeStack.pop())) {
        if (node.nodeType == 3) {
            var nextCharIndex = charIndex + node.length;
            if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                range.setStart(node, savedSel.start - charIndex);
                foundStart = true;
            }
            if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                range.setEnd(node, savedSel.end - charIndex);
                stop = true;
            }
            charIndex = nextCharIndex;
        } else {
            var i = node.childNodes.length;
            while (i--) {
                nodeStack.push(node.childNodes[i]);
            }
        }
    }

    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

function pasteHtmlAtCaret(html, returnInNode, selectPastedContent) {
//function written by Tim Down

var sel, range;
if (window.getSelection) {
    // IE9 and non-IE
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
        range = sel.getRangeAt(0);
        range.deleteContents();

        // Range.createContextualFragment() would be useful here but is
        // only relatively recently standardized and is not supported in
        // some browsers (IE9, for one)
        var el = document.createElement("div");
        el.innerHTML = html;
        var frag = document.createDocumentFragment(), node, lastNode;
        while ( (node = el.firstChild) ) {
            lastNode = frag.appendChild(node);
        }
        var firstNode = frag.firstChild;
        range.insertNode(frag);

        // Preserve the selection
        if (lastNode) {
            range = range.cloneRange();
            if (returnInNode)
            {
                range.setStart(lastNode, 0); //this part is edited, set caret inside pasted node.
            }
            else
            {
                range.setStartAfter(lastNode); 
            }
            if (selectPastedContent) {
                range.setStartBefore(firstNode);
            } else {
                range.collapse(true);
            }
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }
} else if ( (sel = document.selection) && sel.type != "Control") {
    // IE < 9
    var originalRange = sel.createRange();
    originalRange.collapse(true);
    sel.createRange().pasteHTML(html);
    if (selectPastedContent) {
        range = sel.createRange();
        range.setEndPoint("StartToStart", originalRange);
        range.select();
    }
}
}

Ce que fait le code:

  1. Quelqu'un déclenche l'événement de collage à l'aide de ctrl-v, du menu contextuel ou d'autres moyens
  2. Dans l'événement de collage, un nouvel élément avec contenteditable est créé (un élément avec contenteditable a des privilèges élevés)
  3. La position caret de l'élément cible est enregistrée.
  4. Le focus est mis sur le nouvel élément
  5. Le contenu est collé dans le nouvel élément et est rendu dans le DOM.
  6. L'observateur de mutation attrape cela (il enregistre toutes les modifications apportées à l'arborescence dom et au contenu). Déclenche ensuite l'événement de mutation.
  7. Le dom du contenu collé est cloné dans une variable et renvoyé au rappel. L'élément temporaire est détruit.
  8. Le rappel reçoit le DOM cloné. Le caret est restauré. Vous pouvez le modifier avant de l'ajouter à votre cible. élément. Dans cet exemple, j'utilise les fonctions Tim Downs pour enregistrer / restaurer le curseur et coller du code HTML dans l'élément.

Exemple


Un grand merci à Tim Down Voir cet article pour la réponse:

Récupérer le contenu collé sur le document lors de l'événement de collage

Mouser
la source
4

La solution qui fonctionne pour moi consiste à ajouter un écouteur d'événements pour coller un événement si vous le collez dans une entrée de texte. Étant donné que l'événement paste se produit avant que le texte dans les modifications d'entrée, dans mon gestionnaire on paste, je crée une fonction différée à l'intérieur de laquelle je vérifie les changements dans ma zone de saisie qui se sont produits lors du collage:

onPaste: function() {
    var oThis = this;
    setTimeout(function() { // Defer until onPaste() is done
        console.log('paste', oThis.input.value);
        // Manipulate pasted input
    }, 1);
}
Lex
la source
2
L'horreur fait malheureusement partie de notre description de poste;) Mais je suis d'accord, c'est un hack et les hacks ne doivent être utilisés QUE lorsque toutes les autres options sont épuisées.
Lex
4

C'était trop long pour un commentaire sur la réponse de Nico, qui ne fonctionne plus sur Firefox (selon les commentaires), et n'a pas fonctionné pour moi sur Safari tel quel.

Premièrement, vous semblez maintenant pouvoir lire directement depuis le presse-papiers. Plutôt que de coder comme:

if (/text\/plain/.test(e.clipboardData.types)) {
    // shouldn't this be writing to elem.value for text/plain anyway?
    elem.innerHTML = e.clipboardData.getData('text/plain');
}

utilisation:

types = e.clipboardData.types;
if (((types instanceof DOMStringList) && types.contains("text/plain")) ||
    (/text\/plain/.test(types))) {
    // shouldn't this be writing to elem.value for text/plain anyway?
    elem.innerHTML = e.clipboardData.getData('text/plain');
}

parce que Firefox a un typeschamp qui n'est DOMStringListpas implémenté test.

Firefox suivant n'autorisera pas le collage sauf si le focus est dans un contenteditable=truechamp.

Enfin, Firefox n'autorisera pas le collage de manière fiable à moins que le focus soit dans un textarea(ou peut-être une entrée) qui n'est pas seulement contenteditable=truemais aussi:

  • ne pas display:none
  • ne pas visibility:hidden
  • pas de taille nulle

J'essayais de masquer le champ de texte pour que je puisse faire fonctionner le collage sur un émulateur JS VNC (c'est-à-dire qu'il allait vers un client distant et qu'il n'y avait pas réellement textareaetc.) dans lequel coller. J'ai trouvé que le fait d'essayer de masquer le champ de texte ci-dessus donnait des symptômes où cela fonctionnait parfois, mais échouait généralement sur le deuxième collage (ou lorsque le champ était effacé pour éviter de coller deux fois les mêmes données) car le champ perdait le focus et ne revenait pas correctement malgré cela focus(). La solution que j'ai trouvée était de le mettre z-order: -1000, de le faire display:none, de le faire en 1px par 1px et de définir toutes les couleurs sur transparent. Beurk.

Sur Safari, la deuxième partie de ce qui précède s'applique, c'est-à-dire que vous devez en avoir un textareaqui ne l'est pas display:none.

abligh
la source
Peut-être que les développeurs qui travaillent sur les moteurs de rendu du navigateur devraient avoir une page ou un espace sur les sites de documentation qu'ils peuvent utiliser pour écrire des notes sur les fonctionnalités sur lesquelles ils travaillent. Par exemple, s'ils travaillaient sur la fonction coller, ils ajouteraient: "Le collage ne fonctionnera pas si l'affichage est nul, la visibilité est masquée ou la taille est nulle".
1,21 gigawatts le
3

Le premier qui me vient à l'esprit est le gestionnaire de pâte de la fermeture de Google lib http://closure-library.googlecode.com/svn/trunk/closure/goog/demos/pastehandler.html

tDo
la source
celui-ci semble détecter en toute sécurité un événement de collage, mais il ne semble pas être en mesure d'attraper / retourner le contenu collé?
Alex
@Alex: vous avez raison, et cela ne fonctionne que sur les zones de texte, pas sur les éditeurs de texte enrichi.
Tim Down
3

Solution simple:

document.onpaste = function(e) {
    var pasted = e.clipboardData.getData('Text');
    console.log(pasted)
}
lama12345
la source
2

Cela a fonctionné pour moi:

function onPasteMe(currentData, maxLen) {
    // validate max length of pasted text
    var totalCharacterCount = window.clipboardData.getData('Text').length;
}

<input type="text" onPaste="return onPasteMe(this, 50);" />
Timmy Duncan
la source
2
function myFunct( e ){
    e.preventDefault();

    var pastedText = undefined;
    if( window.clipboardData && window.clipboardData.getData ){
    pastedText = window.clipboardData.getData('Text');
} 
else if( e.clipboardData && e.clipboardData.getData ){
    pastedText = e.clipboardData.getData('text/plain');
}

//work with text

}
document.onpaste = myFunct;
Ivan
la source
1

Vous pouvez le faire de cette manière:

utilisez ce plugin jQuery pour les événements avant et après collage:

$.fn.pasteEvents = function( delay ) {
    if (delay == undefined) delay = 20;
    return $(this).each(function() {
        var $el = $(this);
        $el.on("paste", function() {
            $el.trigger("prepaste");
            setTimeout(function() { $el.trigger("postpaste"); }, delay);
        });
    });
};

Vous pouvez maintenant utiliser ce plugin ;:

$('#txt').on("prepaste", function() { 

    $(this).find("*").each(function(){

        var tmp=new Date.getTime();
        $(this).data("uid",tmp);
    });


}).pasteEvents();

$('#txt').on("postpaste", function() { 


  $(this).find("*").each(function(){

     if(!$(this).data("uid")){
        $(this).removeClass();
          $(this).removeAttr("style id");
      }
    });
}).pasteEvents();

Explication

Définissez d'abord un uid pour tous les éléments existants comme attribut de données.

Comparez ensuite tous les nœuds de l'événement POST PASTE. Ainsi, en comparant, vous pouvez identifier celui qui vient d'être inséré, car il aura un uid, puis supprimez simplement l'attribut style / classe / id des éléments nouvellement créés, afin de conserver votre ancienne mise en forme.

Peeyush
la source
1
$('#dom').on('paste',function (e){
    setTimeout(function(){
        console.log(e.currentTarget.value);
    },0);
});
Roman Yudin
la source
1

Laissez simplement le navigateur coller comme d'habitude dans son div modifiable de contenu, puis après le collage, échangez tous les éléments de portée utilisés pour les styles de texte personnalisés avec le texte lui-même. Cela semble fonctionner correctement dans Internet Explorer et les autres navigateurs que j'ai essayés ...

$('[contenteditable]').on('paste', function (e) {
    setTimeout(function () {
        $(e.target).children('span').each(function () {
            $(this).replaceWith($(this).text());
        });
    }, 0);
});

Cette solution suppose que vous exécutez jQuery et que vous ne souhaitez pas la mise en forme du texte dans aucun de vos divs modifiables de contenu .

Le côté positif est que c'est super simple.

DaveAlger
la source
Pourquoi spantag? J'imagine que la question concernait toutes les balises.
Alexis Wilke
1

Cette solution consiste à remplacer la balise html, c'est simple et multi-navigateur; vérifiez ce jsfiddle: http://jsfiddle.net/tomwan/cbp1u2cx/1/ , code principal:

var $plainText = $("#plainText");
var $linkOnly = $("#linkOnly");
var $html = $("#html");

$plainText.on('paste', function (e) {
    window.setTimeout(function () {
        $plainText.html(removeAllTags(replaceStyleAttr($plainText.html())));
    }, 0);
});

$linkOnly.on('paste', function (e) {
    window.setTimeout(function () {
        $linkOnly.html(removeTagsExcludeA(replaceStyleAttr($linkOnly.html())));
    }, 0);
});

function replaceStyleAttr (str) {
    return str.replace(/(<[\w\W]*?)(style)([\w\W]*?>)/g, function (a, b, c, d) {
        return b + 'style_replace' + d;
    });
}

function removeTagsExcludeA (str) {
    return str.replace(/<\/?((?!a)(\w+))\s*[\w\W]*?>/g, '');
}

function removeAllTags (str) {
    return str.replace(/<\/?(\w+)\s*[\w\W]*?>/g, '');
}

remarque: vous devriez faire un peu de travail sur le filtre xss à l'arrière car cette solution ne peut pas filtrer les chaînes comme '<< >>'

TomWan
la source
Le filtrage XSS sur le serveur n'a rien à voir avec le bon fonctionnement de votre filtre JavaScript. Les pirates contournent de toute façon 100% de votre filtrage JS.
Alexis Wilke
N'utilisez jamais Regex pour analyser / transformer du HTML!
SubliemeSiem
0

Il s'agit d'un code existant publié ci-dessus, mais je l'ai mis à jour pour IE, le bogue était lorsque le texte existant est sélectionné et collé ne supprimera pas le contenu sélectionné. Cela a été corrigé par le code ci-dessous

selRange.deleteContents(); 

Voir le code complet ci-dessous

$('[contenteditable]').on('paste', function (e) {
    e.preventDefault();

    if (window.clipboardData) {
        content = window.clipboardData.getData('Text');        
        if (window.getSelection) {
            var selObj = window.getSelection();
            var selRange = selObj.getRangeAt(0);
            selRange.deleteContents();                
            selRange.insertNode(document.createTextNode(content));
        }
    } else if (e.originalEvent.clipboardData) {
        content = (e.originalEvent || e).clipboardData.getData('text/plain');
        document.execCommand('insertText', false, content);
    }        
});
Ravi Selvaraj
la source