eventi di cambiamento contenti


359

Voglio eseguire una funzione quando un utente modifica il contenuto di un attributo divwith contenteditable. Qual è l'equivalente di un onchangeevento?

Sto usando jQuery quindi è preferibile qualsiasi soluzione che utilizza jQuery. Grazie!


Io di solito faccio questo: document.getElementById("editor").oninput = function() { ...}.
Basj

Risposte:


311

Io suggerirei di ascoltatori connessi a eventi chiave sparato dall'elemento modificabile, anche se è necessario essere consapevoli del fatto che keydowne keypresseventi vengono generati prima che il contenuto stesso è cambiato. Questo non coprirà tutti i mezzi possibili per cambiare il contenuto: l'utente può anche usare taglia, copia e incolla dai menu del browser Modifica o contestuale, quindi potresti voler gestire anche gli eventi cut copye paste. Inoltre, l'utente può rilasciare testo o altri contenuti, quindi ci sono più eventi lì ( mouseup, ad esempio). Potresti voler eseguire il polling del contenuto dell'elemento come fallback.

AGGIORNAMENTO 29 ottobre 2014

L' evento HTML5input è la risposta a lungo termine. Al momento della stesura, è supportato per contenteditableelementi negli attuali browser Mozilla (da Firefox 14) e WebKit / Blink, ma non IE.

demo:

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

Demo: http://jsfiddle.net/ch6yn/2691/


11
Vorrei anche aggiungere che è possibile modificare un elemento modificabile tramite menu di modifica del browser o nel menu contestuale per tagliare, copiare o incollare il contenuto, quindi avrete bisogno di maniglia cut, copye pastegli eventi nei browser che li supportano (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down

4
È possibile utilizzare il keyup e non si verificherà un problema di temporizzazione.
Blowsie

2
@Blowsie: Sì, ma potresti potenzialmente finire con un sacco di cambiamenti che accadono da una pressione ripetuta automaticamente prima che l'evento si attivi. Esistono anche altri modi per cambiare il testo modificabile non considerato da questa risposta, come il trascinamento della selezione e le modifiche tramite i menu di modifica e contestuale. A lungo termine, tutto ciò dovrebbe essere coperto inputdall'evento HTML5 .
Tim Down

1
Sto eseguendo il keyup con il debounce.
Aen Tan,

1
@AndroidDev Ho testato Chrome, e l'evento di input viene anche generato su copia incolla e tagliato come richiesto dalle specifiche: w3c.github.io/uievents/#events-inputevents
fishbone

188

Ecco una versione più efficiente che utilizza onper tutti gli contenteditables. Si basa sulle risposte migliori qui.

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

Il progetto è qui: https://github.com/balupton/html5edit


Ha funzionato perfettamente per me, grazie sia per CoffeeScript che per Javascript
jwilcox09 il

7
Ci sono alcune modifiche che non verranno colte da questo codice fino a quando il contenteditable non sarà sfocato. Ad esempio: trascinamento della selezione, taglio dal menu di scelta rapida e incolla dalla finestra del browser. Ho impostato una pagina di esempio che utilizza questo codice con cui puoi interagire qui .
jrullmann,

@jrullmann Sì, ho avuto problemi con questo codice durante il trascinamento delle immagini perché non ascolta il caricamento dell'immagine (è stato un problema gestire un'operazione di ridimensionamento nel mio caso)
Sebastien Lorber,

@jrullmann Molto bello, questo funziona anche allora cambiano valore con javascript, cercano in consolle: document.getElementById('editor_input').innerHTML = 'ssss'. Lo svantaggio è che richiede IE11

1
@NadavB non dovrebbe, poiché questo è ciò che "if ($ this.data ('before')! == $ this.html ()) {" è per
balupton

52

Prendi in considerazione l'utilizzo di MutationObserver . Questi osservatori sono progettati per reagire ai cambiamenti nel DOM e in sostituzione degli eventi di mutazione .

Professionisti:

  • Viene eseguito quando qualsiasi si verifica il cambiamento, che è difficile da raggiungere con l'ascolto di eventi chiave come suggerito da altre risposte. Ad esempio, tutti questi funzionano bene: trascina e rilascia, corsivo, copia / taglia / incolla attraverso il menu contestuale.
  • Progettato pensando alle prestazioni.
  • Codice semplice e diretto. È molto più semplice comprendere e eseguire il debug del codice che ascolta un evento anziché il codice che ascolta 10 eventi.
  • Google ha un'eccellente libreria di riepilogo delle mutazioni che rende molto semplice l'utilizzo di MutationObservers.

Contro:

  • Richiede una versione molto recente di Firefox (14.0+), Chrome (18+) o IE (11+).
  • Nuova API per capire
  • Non ci sono ancora molte informazioni disponibili sulle migliori pratiche o casi di studio

Per saperne di più:

  • Ho scritto un piccolo frammento da confrontare usando MutationObserers per gestire una varietà di eventi. Ho usato il codice balupton poiché la sua risposta ha il maggior numero di voti.
  • Mozilla ha una pagina eccellente sull'API
  • Dai un'occhiata alla libreria MutationSummary

Non è esattamente lo stesso inputdell'evento HTML5 (che è supportato contenteditablein tutti i browser WebKit e Mozilla che supportano gli osservatori di mutazione), poiché la mutazione DOM potrebbe verificarsi tramite script e input dell'utente, ma è una soluzione praticabile per quei browser. Immagino che potrebbe danneggiare le prestazioni più inputdell'evento, ma non ho prove concrete per questo.
Tim Down,

2
+1, ma ti sei reso conto che gli eventi di mutazione non riportano gli effetti del feed di linea in un contenteditable? Premi Invio nel tuo frammento.
citykid,

4
@citykid: Questo perché lo snippet sta solo osservando le modifiche ai dati dei personaggi. È anche possibile osservare i cambiamenti strutturali del DOM. Vedi jsfiddle.net/4n2Gz/1 , per esempio.
Tim Down

Internet Explorer 11+ supporta gli osservatori di mutazioni .
Sampson

Sono stato in grado di verificare (almeno in Chrome 43.0.2357.130) che l' inputevento HTML5 si attiva in ognuna di queste situazioni (in particolare trascinamento della selezione, corsivo, copia / taglia / incolla attraverso il menu contestuale). Ho anche testato il grassetto w / cmd / ctrl + b e ho ottenuto i risultati previsti. Ho anche controllato e sono stato in grado di verificare che l' inputevento non si attiva su modifiche programmatiche (il che sembra ovvio, ma è probabilmente contrario a un linguaggio confuso sulla pagina MDN pertinente; vedi il fondo della pagina qui per vedere cosa intendo: sviluppatore .mozilla.org / it-US / docs / Web / Events / input )
mikermcneil,

22

Ho modificato la risposta di Lawwantsin in questo modo e questo funziona per me. Uso l'evento keyup invece di keypress che funziona alla grande.

$('#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

risposta rapida e sporca non jQuery :

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
che cos'è questo evento di eliminazione?
James Cat,

7

Due opzioni:

1) Per i browser moderni (sempreverdi): l'evento "input" fungerebbe da evento alternativo "change".

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

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

o

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

o (con jQuery)

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

2) Per tenere conto di IE11 e dei browser moderni (sempreverdi): controlla i cambiamenti degli elementi e il loro contenuto all'interno del 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
Questo sembra interessante. Qualcuno può fornire qualche spiegazione? 😇
WoIIe

3

Ecco cosa ha funzionato per me:

   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

Questo thread è stato molto utile mentre stavo indagando sull'argomento.

Ho modificato parte del codice disponibile qui in un plug-in jQuery, quindi è in una forma riutilizzabile, principalmente per soddisfare le mie esigenze, ma altri potrebbero apprezzare un'interfaccia più semplice per iniziare a utilizzare tag contenteditable.

https://gist.github.com/3410122

Aggiornare:

A causa della sua crescente popolarità, il plugin è stato adottato da Makesites.org

Lo sviluppo continuerà da qui:

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


2

Ecco la soluzione che ho finito per usare e funziona favolosamente. Uso $ (this) .text () invece perché sto solo usando un div di una riga che è modificabile nel contenuto. Ma puoi anche usare .html () in questo modo non devi preoccuparti dell'ambito di una variabile globale / non globale e il precedente è effettivamente collegato al div editor.

$('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

Per evitare timer e pulsanti "salva", è possibile utilizzare l'evento sfocatura che si attiva quando l'elemento perde lo stato attivo. ma per essere sicuri che l'elemento sia stato effettivamente modificato (non solo focalizzato e sfocato), il suo contenuto dovrebbe essere confrontato con la sua ultima versione. oppure usa l'evento keydown per impostare un flag "sporco" su questo elemento.


1

Una semplice risposta in JQuery, ho appena creato questo codice e ho pensato che sarebbe stato utile anche per gli altri

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

Nota che $("div [contenteditable=true]")selezionerà tutti i bambini di un div, direttamente o indirettamente, che sono contenti.
Bruno Ferreira,

1

Risposta non JQuery ...

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

Per usarlo, chiama (diciamo) un elemento header con id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Quell'elemento sarà ora modificabile dall'utente fino a quando non perde lo stato attivo.


5
Ugh, perché il downvote senza spiegazioni? Questo è un disservizio sia per la persona che ha scritto la risposta, sia per tutti coloro che leggono la risposta in un secondo momento.
Mike Willis,

0

L'evento onchange non si attiva quando viene modificato un elemento con l'attributo contentEditable, un approccio suggerito potrebbe essere quello di aggiungere un pulsante, per "salvare" l'edizione.

Controlla questo plugin che gestisce il problema in questo modo:


per i posteri, tra dieci anni e non c'è bisogno di un pulsante "salva", controlla il jsfiddle della risposta accettata
rivisto l'

0

L'uso di DOMCharacterDataModified in MutationEvents porterà allo stesso. Il timeout è impostato per impedire l'invio di valori errati (ad es. In Chrome ho avuto alcuni problemi con la chiave dello spazio)

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

Esempio JSFIDDLE


1
+1: DOMCharacterDataModifiednon si attiva quando l'utente modifica il testo esistente, ad esempio applicando grassetto o corsivo. DOMSubtreeModifiedè più appropriato in questo caso. Inoltre, le persone dovrebbero ricordare che i browser legacy non supportano questi eventi.
Andy E

3
Solo una nota che gli eventi di mutazione sono ammortizzati dal w3c a causa di problemi di prestazioni. Altro può essere trovato in questa domanda Stack Overflow .
jrullmann,

0

Ho creato un plugin jQuery per farlo.

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

Puoi semplicemente chiamare $('.wysiwyg').wysiwygEvt();

Se lo desideri, puoi anche rimuovere / aggiungere eventi


2
Ciò diventerà lento e lento man mano che il contenuto modificabile si allunga ( innerHTMLè costoso). Consiglierei di utilizzare l' inputevento in cui esiste e di tornare a qualcosa del genere ma con una sorta di rimbalzo.
Tim Down,

0

In 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

Segui la seguente semplice soluzione

In .ts:

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

in .html:

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

-1

Dai un'occhiata a questa idea. http://pastie.org/1096892

Penso che sia vicino. HTML 5 deve davvero aggiungere l'evento change alle specifiche. L'unico problema è che la funzione di callback valuta se (prima == $ (questo) .html ()) prima che il contenuto sia effettivamente aggiornato in $ (questo) .html (). setTimeout non funziona ed è triste. Fatemi sapere cosa ne pensate.


Prova il keyup invece del keypress.
Aen Tan,

-2

Basato sulla risposta di @ 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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.