eventos de cambio contento


359

Quiero ejecutar una función cuando un usuario edita el contenido de un atributo divcon contenteditable. ¿Cuál es el equivalente de un onchangeevento?

Estoy usando jQuery, por lo que se prefiere cualquier solución que use jQuery. ¡Gracias!


Yo suelo hacer esto: document.getElementById("editor").oninput = function() { ...}.
Basj

Respuestas:


311

Yo sugeriría oyentes correspondientes a los eventos clave disparados por el elemento editable, aunque es necesario tener en cuenta que keydowny keypresseventos es despedido antes de que el contenido en sí se cambia. Esto no cubrirá todos los medios posibles para cambiar el contenido: el usuario también puede usar cortar, copiar y pegar desde los menús Editar o contextual del navegador, por lo que es posible que también desee manejar los eventos cut copyy paste. Además, el usuario puede soltar texto u otro contenido, por lo que hay más eventos allí ( mouseuppor ejemplo). Es posible que desee sondear el contenido del elemento como reserva.

ACTUALIZACIÓN 29 de octubre de 2014

El evento HTML5input es la respuesta a largo plazo. Al momento de escribir, es compatible con contenteditableelementos en los navegadores actuales de Mozilla (de Firefox 14) y WebKit / Blink, pero no IE.

Manifestación:

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

Demostración: http://jsfiddle.net/ch6yn/2691/


11
También me gustaría añadir que es posible cambiar un elemento editable usando el menú de edición del navegador o del menú contextual para cortar, copiar o pegar el contenido, por lo que necesita para manejar cut, copyy pasteeventos en los navegadores que las soportan (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down

44
Podrías usar keyup, y no tendrás un problema de sincronización.
Blowsie

2
@Blowsie: Sí, pero podría terminar con muchos cambios al presionar una tecla automáticamente antes de que se active el evento. También hay otras formas de cambiar el texto editable que no se considera en esta respuesta, como arrastrar y soltar y editar a través de los menús Editar y contextual. A largo plazo, todo esto debería estar cubierto por el inputevento HTML5 .
Tim Down

1
Estoy haciendo keyup con debounce.
Aen Tan

1
@AndroidDev I testet Chrome, y el evento de entrada también se dispara al copiar y pegar y cortar según lo requerido por las especificaciones: w3c.github.io/uievents/#events-inputevents
fishbone

188

Aquí hay una versión más eficiente que utiliza onpara todos los contentables. Se basa en las mejores respuestas aquí.

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

El proyecto está aquí: https://github.com/balupton/html5edit


Funcionó perfecto para mí, gracias por CoffeeScript y Javascript
jwilcox09

77
Hay algunos cambios que este código no detectará hasta que se borre lo contento. Por ejemplo: arrastrar y soltar, cortar desde el menú contextual y pegar desde la ventana del navegador. He configurado una página de ejemplo que usa este código con el que puedes interactuar aquí .
jrullmann

@jrullmann Sí, tuve problemas con este código al arrastrar y soltar imágenes porque no escucha la carga de la imagen (en mi caso fue un problema manejar una operación de cambio de tamaño)
Sebastien Lorber

@jrullmann muy agradable, incluso entonces funciona este cambio de valor con javascript, tratan en la consola: document.getElementById('editor_input').innerHTML = 'ssss'. El inconveniente es que requiere IE11

1
@NadavB no debería, ya que eso es lo que "if ($ this.data ('before')! == $ this.html ()) {" es para
balupton

52

Considere usar MutationObserver . Estos observadores están diseñados para reaccionar a los cambios en el DOM y como un reemplazo eficaz para eficaz de los eventos de mutación .

Pros:

  • Se activa cuando se produce cualquier cambio, lo cual es difícil de lograr escuchando eventos clave como lo sugieren otras respuestas. Por ejemplo, todo esto funciona bien: arrastrar y soltar, poner en cursiva, copiar / cortar / pegar a través del menú contextual.
  • Diseñado con el rendimiento en mente.
  • Código simple y directo. Es mucho más fácil de entender y depurar código que escucha un evento en lugar de código que escucha 10 eventos.
  • Google tiene una excelente biblioteca de resumen de mutaciones que facilita el uso de MutationObservers.

Contras:

  • Requiere una versión muy reciente de Firefox (14.0+), Chrome (18+) o IE (11+).
  • Nueva API para entender
  • Todavía no hay mucha información disponible sobre las mejores prácticas o estudios de casos.

Aprende más:

  • Escribí un pequeño fragmento para comparar el uso de MutationObserers con el manejo de una variedad de eventos. Usé el código de Balupton desde su respuesta tiene la mayor cantidad de votos.
  • Mozilla tiene una excelente página en la API
  • Echa un vistazo a la biblioteca MutationSummary

No es exactamente lo mismo que el inputevento HTML5 (que es compatible con contenteditabletodos los navegadores WebKit y Mozilla que admiten observadores de mutación), ya que la mutación DOM podría ocurrir a través del script y la entrada del usuario, pero es una solución viable para esos navegadores. Me imagino que también podría dañar el rendimiento más que el inputevento, pero no tengo pruebas contundentes de esto.
Tim Down

2
+1, pero ¿te diste cuenta de que los eventos de mutación no informan los efectos del salto de línea en un contenido contento? Presiona enter en tu fragmento.
citykid

44
@citykid: Eso se debe a que el fragmento solo está buscando cambios en los datos de los personajes. También es posible observar cambios estructurales DOM. Ver jsfiddle.net/4n2Gz/1 , por ejemplo.
Tim Down

Internet Explorer 11+ es compatible con Mutation Observers .
Sampson

Pude verificar (al menos en Chrome 43.0.2357.130) que el inputevento HTML5 se dispara en cada una de estas situaciones (específicamente arrastrar y soltar, cursiva, copiar / cortar / pegar a través del menú contextual). También probé negrita w / cmd / ctrl + b y obtuve los resultados esperados. También verifiqué y pude verificar que el inputevento no se dispara en los cambios programáticos (lo que parece obvio, pero podría decirse que es contrario a algún lenguaje confuso en la página MDN relevante; vea la parte inferior de la página aquí para ver lo que quiero decir: desarrollador .mozilla.org / es-es / docs / Web / Events / input )
mikermcneil

22

He modificado la respuesta de lawwantsin así y esto funciona para mí. Uso el evento keyup en lugar de presionar teclas, lo que funciona muy 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

no jQuery respuesta rápida y sucia :

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
¿Qué es este evento de eliminación?
James Cat

7

Dos opciones:

1) Para navegadores modernos (de hoja perenne): el evento "input" actuaría como un evento alternativo de "cambio".

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) Para tener en cuenta los navegadores IE11 y modernos (perennes): Esto busca cambios en los elementos y su contenido dentro 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
Esto se ve interesante. ¿Alguien puede dar alguna explicación? Wo
WoIIe

3

Esto es lo que funcionó para mí:

   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

Este hilo fue muy útil mientras investigaba el tema.

Modifiqué parte del código disponible aquí en un complemento jQuery para que sea reutilizable, principalmente para satisfacer mis necesidades, pero otros pueden apreciar una interfaz más simple para iniciar usando etiquetas contentables.

https://gist.github.com/3410122

Actualizar:

Debido a su creciente popularidad, el complemento ha sido adoptado por Makesites.org

El desarrollo continuará desde aquí:

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


2

Aquí está la solución que terminé usando y funciona fabulosamente. En su lugar, uso $ (this) .text () porque solo estoy usando un div de una línea que tiene contenido editable. Pero también puede usar .html () de esta manera, no tiene que preocuparse por el alcance de una variable global / no global y lo anterior está realmente unido al editor 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

Para evitar temporizadores y botones "guardar", puede usar un evento de desenfoque que se dispara cuando el elemento pierde el foco. pero para asegurarse de que el elemento haya cambiado realmente (no solo enfocado y desenfocado), su contenido debe compararse con su última versión. o use el evento keydown para establecer una bandera "sucia" en este elemento.


1

Una respuesta simple en JQuery, acabo de crear este código y pensé que sería útil para otros también

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

Tenga en cuenta que $("div [contenteditable=true]")seleccionará todos los niños de un div, directa o indirectamente, que sean contentables.
Bruno Ferreira

1

Respuesta no JQuery ...

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

Para usarlo, invoque (digamos) un elemento de encabezado con id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Ese elemento ahora será editable por el usuario hasta que pierda el foco.


55
Ugh, ¿por qué el voto negativo sin explicación? Esto perjudica tanto a la persona que escribió la respuesta como a todos los que leen la respuesta más tarde.
Mike Willis

0

El evento onchange no se activa cuando se cambia un elemento con el atributo contentEditable, un enfoque sugerido podría ser agregar un botón, para "guardar" la edición.

Marque este complemento que maneja el problema de esa manera:


para la posteridad, diez años adelante y no hay necesidad de un botón "guardar", verifique la respuesta aceptada jsfiddle
revelt

0

El uso de DOMCharacterDataModified en MutationEvents conducirá a lo mismo. El tiempo de espera está configurado para evitar el envío de valores incorrectos (por ejemplo, en Chrome tuve algunos problemas con la tecla de espacio)

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

Ejemplo JSFIDDLE


1
+1: DOMCharacterDataModifiedno se activará cuando el usuario modifique el texto existente, por ejemplo, aplicando negrita o cursiva. DOMSubtreeModifiedEs más apropiado en este caso. Además, las personas deben recordar que los navegadores heredados no admiten estos eventos.
Andy E

3
Solo una nota de que los eventos de mutación son depreciados por el w3c debido a problemas de rendimiento. Se puede encontrar más en esta pregunta de desbordamiento de pila .
jrullmann

0

Construí un complemento jQuery para hacer esto.

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

Simplemente puedes llamar $('.wysiwyg').wysiwygEvt();

También puede eliminar / agregar eventos si lo desea


2
Eso se volverá lento y lento a medida que el contenido editable se alargue ( innerHTMLes costoso). Recomendaría usar el inputevento donde existe y recurrir a algo como esto pero con algún tipo de rebote.
Tim Down

0

En 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

Siga la siguiente solución simple

En .ts:

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

en .html:

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

-1

Mira esta idea. http://pastie.org/1096892

Creo que está cerca. HTML 5 realmente necesita agregar el evento de cambio a la especificación. El único problema es que la función de devolución de llamada evalúa if (before == $ (this) .html ()) antes de que el contenido se actualice realmente en $ (this) .html (). setTimeout no funciona, y es triste. Déjame saber lo que piensas.


Prueba keyup en lugar de presionar teclas.
Aen Tan

-2

Basado en la respuesta 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>

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.