événements de modification modifiables


359

Je veux exécuter une fonction lorsqu'un utilisateur modifie le contenu d'un attribut divwith contenteditable. Quel est l'équivalent d'un onchangeévénement?

J'utilise jQuery donc toutes les solutions qui utilisent jQuery sont préférées. Merci!


Je fais habituellement ceci: document.getElementById("editor").oninput = function() { ...}.
Basj

Réponses:


311

Je vous suggère d' attache des auditeurs à des événements clés tirés par l'élément modifiable, mais vous devez être conscient que keydownet keypressévénements sont déclenchés avant que le contenu lui - même est changé. Cela ne couvrira pas tous les moyens possibles de modifier le contenu: l'utilisateur peut également utiliser couper, copier et coller à partir des menus du navigateur d'édition ou de contexte, vous pouvez donc également gérer les événements cut copyet paste. De plus, l'utilisateur peut supprimer du texte ou d'autres contenus, il y a donc plus d'événements ( mouseuppar exemple). Vous souhaiterez peut-être interroger le contenu de l'élément comme solution de rechange.

MISE À JOUR 29 octobre 2014

L' événement HTML5input est la réponse à long terme. Au moment de la rédaction, il est pris en charge pour les contenteditableéléments des navigateurs Mozilla actuels (de Firefox 14) et WebKit / Blink, mais pas IE.

Démo:

document.getElementById("editor").addEventListener("input", function() {
    console.log("input event fired");
}, false);
<div contenteditable="true" id="editor">Please type something in here</div>

Démo: http://jsfiddle.net/ch6yn/2691/


11
Je dois également ajouter qu'il est possible de changer un élément modifiable en utilisant le menu d'édition du navigateur ou le menu contextuel pour couper, copier ou contenu coller, vous aurez donc besoin de gérer cut, copyet les pasteévénements dans les navigateurs qui les soutiennent (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down

4
Vous pouvez utiliser la touche clavier et vous n'aurez pas de problème de synchronisation.
Blowsie

2
@Blowsie: Oui, mais vous pourriez potentiellement vous retrouver avec de nombreux changements à partir d'une pression de touche répétée automatiquement avant le déclenchement de l'événement. Il existe également d'autres façons de modifier le texte modifiable non prises en compte par cette réponse, telles que le glisser-déposer et les modifications via les menus Edition et contextuel. À long terme, tout cela devrait être couvert par l' inputévénement HTML5 .
Tim Down le

1
Je fais du keyup avec debounce.
Aen Tan

1
@AndroidDev I testet Chrome, et l'événement d'entrée est également déclenché sur copier-coller et couper comme requis par les spécifications: w3c.github.io/uievents/#events-inputevents
fishbone

188

Voici une version plus efficace qui utilise onpour tous les contenus modifiables. Il est basé sur les meilleures réponses ici.

$('body').on('focus', '[contenteditable]', function() {
    const $this = $(this);
    $this.data('before', $this.html());
}).on('blur keyup paste input', '[contenteditable]', function() {
    const $this = $(this);
    if ($this.data('before') !== $this.html()) {
        $this.data('before', $this.html());
        $this.trigger('change');
    }
});

Le projet est ici: https://github.com/balupton/html5edit


Fonctionné parfaitement pour moi, merci pour CoffeeScript et Javascript
jwilcox09

7
Il y a des changements qui ne seront pas détectés par ce code jusqu'à ce que le contenteditable soit flou. Par exemple: glisser-déposer, couper depuis le menu contextuel et coller depuis la fenêtre du navigateur. J'ai configuré une page d'exemple qui utilise ce code avec lequel vous pouvez interagir ici .
jrullmann

@jrullmann Oui, j'ai eu des problèmes avec ce code lors du glisser-déposer des images car il n'écoute pas la charge de l'image (c'était un problème pour gérer une opération de redimensionnement dans mon cas)
Sebastien Lorber

@jrullmann Très bien, ceci fonctionne même changeant alors la valeur avec javascript, essayez dans la console: document.getElementById('editor_input').innerHTML = 'ssss'. L'inconvénient est qu'il nécessite IE11

1
@NadavB il ne devrait pas, car c'est ce que "if ($ this.data ('avant')! == $ this.html ()) {" est pour
balupton

52

Pensez à utiliser MutationObserver . Ces observateurs sont conçus pour réagir aux changements dans les DOM, et en remplacement performant des événements de mutation .

Avantages:

  • Se déclenche en cas de changement, ce qui est difficile à réaliser en écoutant les événements clés suggérés par d'autres réponses. Par exemple, tout cela fonctionne bien: glisser-déposer, italique, copier / couper / coller via le menu contextuel.
  • Conçu en pensant aux performances.
  • Code simple et direct. Il est beaucoup plus facile de comprendre et de déboguer du code qui écoute un événement plutôt que du code qui écoute 10 événements.
  • Google possède une excellente bibliothèque de résumé des mutations qui rend l'utilisation de MutationObservers très facile.

Les inconvénients:

  • Nécessite une version très récente de Firefox (14.0+), Chrome (18+) ou IE (11+).
  • Nouvelle API pour comprendre
  • Pas beaucoup d'informations disponibles sur les meilleures pratiques ou études de cas

Apprendre encore plus:

  • J'ai écrit un petit extrait pour comparer l'utilisation de MutationObserers à la gestion d'une variété d'événements. J'ai utilisé le code de balupton depuis sa réponse a le plus de votes positifs.
  • Mozilla a une excellente page sur l'API
  • Jetez un œil à la bibliothèque MutationSummary

Ce n'est pas tout à fait la même chose que l' inputévénement HTML5 (qui est pris contenteditableen charge dans tous les navigateurs WebKit et Mozilla qui prennent en charge les observateurs de mutation), car la mutation DOM peut se produire via un script ainsi que l'entrée utilisateur, mais c'est une solution viable pour ces navigateurs. J'imagine que cela pourrait nuire à la performance plus que l' inputévénement aussi, mais je n'ai aucune preuve tangible pour cela.
Tim Down

2
+1, mais saviez-vous que les événements de mutation ne signalent pas les effets du saut de ligne dans un contenu modifiable? Appuyez sur entrée dans votre extrait.
citykid

4
@citykid: En effet, l'extrait de code ne surveille que les modifications apportées aux données des personnages. Il est également possible d'observer les changements structurels du DOM. Voir jsfiddle.net/4n2Gz/1 , par exemple.
Tim Down

Internet Explorer 11+ prend en charge les observateurs de mutation .
Sampson

J'ai pu vérifier (au moins dans Chrome 43.0.2357.130) que l' inputévénement HTML5 se déclenche dans chacune de ces situations (en particulier glisser-déposer, italique, copier / couper / coller via le menu contextuel). J'ai également testé le bolding w / cmd / ctrl + b et obtenu les résultats attendus. J'ai également vérifié et j'ai pu vérifier que l' inputévénement ne se déclenche pas sur les changements de programmation (ce qui semble évident, mais est sans doute contraire à un langage déroutant sur la page MDN pertinente; voir le bas de la page ici pour voir ce que je veux dire: développeur .mozilla.org / en-US / docs / Web / Events / input )
mikermcneil

22

J'ai modifié la réponse de lawwantsin comme ça et cela fonctionne pour moi. J'utilise l'événement keyup au lieu de keypress qui fonctionne très bien.

$('#editor').on('focus', function() {
  before = $(this).html();
}).on('blur keyup paste', function() { 
  if (before != $(this).html()) { $(this).trigger('change'); }
});

$('#editor').on('change', function() {alert('changed')});

22

réponse non jQuery rapide et sale :

function setChangeListener (div, listener) {

    div.addEventListener("blur", listener);
    div.addEventListener("keyup", listener);
    div.addEventListener("paste", listener);
    div.addEventListener("copy", listener);
    div.addEventListener("cut", listener);
    div.addEventListener("delete", listener);
    div.addEventListener("mouseup", listener);

}

var div = document.querySelector("someDiv");

setChangeListener(div, function(event){
    console.log(event);
});

3
quel est cet événement de suppression?
James Cat

7

Deux options:

1) Pour les navigateurs modernes (à feuilles persistantes): L'événement "input" agirait comme un événement "change" alternatif.

https://developer.mozilla.org/en-US/docs/Web/Events/input

document.querySelector('div').addEventListener('input', (e) => {
    // Do something with the "change"-like event
});

ou

<div oninput="someFunc(event)"></div>

ou (avec jQuery)

$('div').on('click', function(e) {
    // Do something with the "change"-like event
});

2) Pour prendre en compte IE11 et les navigateurs modernes (à feuilles persistantes): Cela surveille les changements d'éléments et leur contenu à l'intérieur de la div.

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

var div = document.querySelector('div');
var divMO = new window.MutationObserver(function(e) {
    // Do something on change
});
divMO.observe(div, { childList: true, subtree: true, characterData: true });

7

const p = document.querySelector('p')
const result = document.querySelector('div')
const observer = new MutationObserver((mutationRecords) => {
  result.textContent = mutationRecords[0].target.data
  // result.textContent = p.textContent
})
observer.observe(p, {
  characterData: true,
  subtree: true,
})
<p contenteditable>abc</p>
<div />


2
Cela semble intéressant. Quelqu'un peut-il fournir des explications? 😇
WoIIe

3

Voici ce qui a fonctionné pour moi:

   var clicked = {} 
   $("[contenteditable='true']").each(function(){       
        var id = $(this).attr("id");
        $(this).bind('focus', function() {
            // store the original value of element first time it gets focus
            if(!(id in clicked)){
                clicked[id] = $(this).html()
            }
        });
   });

   // then once the user clicks on save
   $("#save").click(function(){
            for(var id in clicked){
                var original = clicked[id];
                var current = $("#"+id).html();
                // check if value changed
                if(original != current) save(id,current);
            }
   });

3

Ce fil a été très utile pendant que j'enquêtais sur le sujet.

J'ai modifié une partie du code disponible ici dans un plugin jQuery afin qu'il soit sous une forme réutilisable, principalement pour répondre à mes besoins, mais d'autres peuvent apprécier une interface plus simple pour démarrer en utilisant des balises modifiables.

https://gist.github.com/3410122

Mise à jour:

En raison de sa popularité croissante, le plugin a été adopté par Makesites.org

Le développement se poursuivra à partir d'ici:

https://github.com/makesites/jquery-contenteditable


2

Voici la solution que j'ai finalement utilisée et qui fonctionne à merveille. J'utilise $ (this) .text () à la place parce que j'utilise juste un div d'une ligne dont le contenu est modifiable. Mais vous pouvez également utiliser .html () de cette façon, vous n'avez pas à vous soucier de la portée d'une variable globale / non globale et l'avant est en fait attaché à l'éditeur div.

$('body').delegate('#editor', 'focus', function(){
    $(this).data('before', $(this).html());
});
$('#client_tasks').delegate('.task_text', 'blur', function(){
    if($(this).data('before') != $(this).html()){
        /* do your stuff here - like ajax save */
        alert('I promise, I have changed!');
    }
});

1

Pour éviter les minuteries et les boutons "enregistrer", vous pouvez utiliser un événement flou qui se déclenche lorsque l'élément perd le focus. mais pour être sûr que l'élément a bien été modifié (pas seulement focalisé et défocalisé), son contenu doit être comparé à sa dernière version. ou utilisez l'événement keydown pour définir un indicateur "sale" sur cet élément.


1

Une réponse simple dans JQuery, je viens de créer ce code et j'ai pensé qu'il serait utile pour les autres aussi

    var cont;

    $("div [contenteditable=true]").focus(function() {
        cont=$(this).html();
    });

    $("div [contenteditable=true]").blur(function() {
        if ($(this).html()!=cont) {
           //Here you can write the code to run when the content change
        }           
    });

Notez que $("div [contenteditable=true]")sélectionnera tous les enfants d'un div, directement ou indirectement, qui sont modifiables.
Bruno Ferreira

1

Réponse non JQuery ...

function makeEditable(elem){
    elem.setAttribute('contenteditable', 'true');
    elem.addEventListener('blur', function (evt) {
        elem.removeAttribute('contenteditable');
        elem.removeEventListener('blur', evt.target);
    });
    elem.focus();
}

Pour l'utiliser, appelez (disons) un élément d'en-tête avec id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Cet élément sera désormais modifiable par l'utilisateur jusqu'à ce qu'il perd le focus.


5
Ugh, pourquoi le downvote sans explication? Cela ne rend pas service à la fois à la personne qui a écrit la réponse et à tous ceux qui lisent la réponse plus tard.
Mike Willis

0

L'événement onchange ne se déclenche pas lorsqu'un élément avec l'attribut contentEditable est modifié, une approche suggérée pourrait être d'ajouter un bouton, de "sauvegarder" l'édition.

Vérifiez ce plugin qui gère le problème de cette manière:


pour la postérité, dix ans en avant et il n'y a pas besoin d'un bouton "enregistrer", consultez le jsfiddle de la réponse acceptée
révélez le

0

L'utilisation de DOMCharacterDataModified sous MutationEvents entraînera la même chose. Le délai est configuré pour empêcher l'envoi de valeurs incorrectes (par exemple, dans Chrome, j'ai eu quelques problèmes avec la touche espace)

var timeoutID;
$('[contenteditable]').bind('DOMCharacterDataModified', function() {
    clearTimeout(timeoutID);
    $that = $(this);
    timeoutID = setTimeout(function() {
        $that.trigger('change')
    }, 50)
});
$('[contentEditable]').bind('change', function() {
    console.log($(this).text());
})

Exemple JSFIDDLE


1
+1 - DOMCharacterDataModifiedne se déclenche pas lorsque l'utilisateur modifie du texte existant, par exemple en appliquant du gras ou de l'italique. DOMSubtreeModifiedest plus approprié dans ce cas. De plus, les utilisateurs doivent se rappeler que les anciens navigateurs ne prennent pas en charge ces événements.
Andy E

3
Juste une note que les événements de mutation sont dépréciés par le w3c en raison de problèmes de performances. Plus d'informations peuvent être trouvées dans cette question Stack Overflow .
jrullmann

0

J'ai construit un plugin jQuery pour ce faire.

(function ($) {
    $.fn.wysiwygEvt = function () {
        return this.each(function () {
            var $this = $(this);
            var htmlold = $this.html();
            $this.bind('blur keyup paste copy cut mouseup', function () {
                var htmlnew = $this.html();
                if (htmlold !== htmlnew) {
                    $this.trigger('change')
                }
            })
        })
    }
})(jQuery);

Vous pouvez simplement appeler $('.wysiwyg').wysiwygEvt();

Vous pouvez également supprimer / ajouter des événements si vous le souhaitez


2
Cela deviendra lent et lent à mesure que le contenu modifiable s'allonge ( innerHTMLest cher). Je recommanderais d'utiliser l' inputévénement où il existe et de revenir à quelque chose comme ça, mais avec une sorte de rebond.
Tim Down

0

Dans Angular 2+

<div contentEditable (input)="type($event)">
   Value
</div>

@Component({
  ...
})
export class ContentEditableComponent {

 ...

 type(event) {
   console.log(event.data) // <-- The pressed key
   console.log(event.path[0].innerHTML) // <-- The content of the div 
 }
}


0

Veuillez suivre la solution simple suivante

En .ts:

getContent(innerText){
  this.content = innerText;
}

en .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>

-1

Découvrez cette idée. http://pastie.org/1096892

Je pense que c'est proche. HTML 5 doit vraiment ajouter l'événement change à la spécification. Le seul problème est que la fonction de rappel évalue si (avant == $ (this) .html ()) avant que le contenu ne soit réellement mis à jour dans $ (this) .html (). setTimeout ne fonctionne pas, et c'est triste. Laissez-moi savoir ce que vous pensez.


Essayez la touche au lieu de la touche.
Aen Tan

-2

D'après la réponse de @ balupton:

$(document).on('focus', '[contenteditable]', e => {
	const self = $(e.target)
	self.data('before', self.html())
})
$(document).on('blur', '[contenteditable]', e => {
	const self = $(e.target)
	if (self.data('before') !== self.html()) {
	  self.trigger('change')
	}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.