оспариваемые события изменения

359

Я хочу запустить функцию, когда пользователь редактирует содержимое атрибута divwith contenteditable. Что эквивалентно onchangeсобытию?

Я использую jQuery, поэтому любые решения, использующие jQuery, предпочтительнее. Спасибо!

Jourkey
источник
Я обычно делаю это: document.getElementById("editor").oninput = function() { ...}.
Basj

Ответы:

311

Я хотел бы предложить , крепящие слушатель ключевых событий обстреляны редактируемым элементом, хотя вы должны знать , что keydownи keypressсобытие вызываются перед само содержанием изменяется. Это не будет охватывать все возможные средства изменения содержания: пользователь может также использовать вырезать, копировать и вставлять текст из Edit или меню контекста браузера, так что вы можете обрабатывать cut copyи pasteсобытие тоже. Кроме того, пользователь может удалить текст или другой контент, чтобы там было больше событий ( mouseupнапример). Вы можете опрашивать содержимое элемента как запасной вариант.

ОБНОВЛЕНИЕ 29 октября 2014

HTML5 inputсобытие является ответом в долгосрочной перспективе. На момент написания он поддерживается для contenteditableэлементов в текущих браузерах Mozilla (из Firefox 14) и WebKit / Blink, но не в IE.

Демо-версия:

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

Демо: http://jsfiddle.net/ch6yn/2691/

Тим Даун
источник
11
Я хотел бы также добавить , что можно изменить редактируемый элемент с помощью меню редактирования браузера или контекстного меню , чтобы вырезать, копировать или вставлять содержимое, так что вам нужно обрабатывать cut, copyи pasteсобытие в браузерах , которые поддерживают их (IE 5 +, Firefox 3.0+, Safari 3+, Chrome)
Тим Даун
4
Вы можете использовать keyup, и у вас не будет проблем с синхронизацией.
Blowie
2
@Blowie: Да, но потенциально вы можете столкнуться с множеством изменений, происходящих от автоматического повторного нажатия клавиш до того, как событие сработает. Существуют также другие способы изменения редактируемого текста, не рассматриваемые в этом ответе, такие как перетаскивание и редактирование с помощью меню «Правка» и контекстных меню. В долгосрочной перспективе все это должно охватываться inputсобытием HTML5 .
Тим Даун
1
Я делаю keyup с debounce.
Aen Tan
1
@AndroidDev Я тестирую Chrome, и событие ввода также запускается при копировании, вставке и обрезке
fishbone
188

Вот более эффективная версия, которая используется onдля всех contenteditables. Это основано на лучших ответах здесь.

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

Проект находится здесь: https://github.com/balupton/html5edit

balupton
источник
Работал отлично для меня, спасибо за CoffeeScript и Javascript
jwilcox09
7
Существуют некоторые изменения, которые не будут отслеживаться этим кодом, пока содержимое не будет размыто. Например: перетаскивание, вырезание из контекстного меню и вставка из окна браузера. Я установил пример страницы, которая использует этот код, с которым вы можете взаимодействовать здесь .
Jrullmann
@jrullmann Да, у меня были проблемы с этим кодом при перетаскивании изображений, потому что он не слушает загрузку изображения (в моем случае это была проблема с обработкой операции изменения размера)
Себастьен Лорбер
@jrullmann Очень приятно, это работает даже тогда изменение значения с JavaScript, попробуйте в консоли: document.getElementById('editor_input').innerHTML = 'ssss'. Недостатком является то, что требуется IE11
1
@NadavB это не должно, так как это то, что "if ($ this.data ('before')! == $ this.html ()) {" for
balupton
52

Рассмотрите возможность использования MutationObserver . Эти наблюдатели призваны реагировать на изменения в DOM и заменять мутационные события .

Плюсы:

  • Срабатывает, когда происходит какое-либо изменение, которого трудно достичь, слушая ключевые события, как подсказывают другие ответы. Например, все это хорошо работает: перетаскивание, выделение курсивом, копирование / вырезание / вставка через контекстное меню.
  • Разработанный с учетом производительности.
  • Простой, понятный код. Намного проще понять и отладить код, который слушает одно событие, а не код, который слушает 10 событий.
  • Google имеет отличную библиотеку сводок мутаций, которая делает использование MutationObservers очень простым.

Минусы:

  • Требуется последняя версия Firefox (14.0+), Chrome (18+) или IE (11+).
  • Новый API для понимания
  • Пока не доступно много информации о лучших практиках или тематических исследованиях

Учить больше:

  • Я написал небольшой фрагмент, чтобы сравнить использование MutationObserers для обработки различных событий. Я использовал код Balupton, так как его ответ имеет наибольшее количество голосов.
  • У Mozilla есть отличная страница по API
  • Взгляните на библиотеку MutationSummary
jrullmann
источник
Это не совсем то же самое, что и inputсобытие HTML5 (которое поддерживается contenteditableво всех браузерах WebKit и Mozilla, которые поддерживают наблюдатели мутаций), поскольку мутация DOM может происходить как через сценарий, так и через пользовательский ввод, но это жизнеспособное решение для этих браузеров. Я думаю, что это может повредить производительности больше, чем inputсобытие, но у меня нет веских доказательств этого.
Тим Даун
2
+1, но вы поняли, что Mutation Events не сообщают о эффектах перевода строки в contenteditable? Нажмите ввод в вашем фрагменте.
ситикид
4
@citykid: Это потому, что фрагмент отслеживает только изменения в символьных данных. Также можно наблюдать структурные изменения DOM. См. Например , jsfiddle.net/4n2Gz/1 .
Тим Даун
Internet Explorer 11+ поддерживает наблюдателей мутаций .
Сэмпсон
Мне удалось проверить (по крайней мере, в Chrome 43.0.2357.130), что inputсобытие HTML5 возникает в каждой из этих ситуаций (в частности, перетаскивание, выделение курсивом, копирование / вырезание / вставка через контекстное меню). Я также проверил жирный шрифт w / cmd / ctrl + b и получил ожидаемые результаты. Я также проверил и смог убедиться, что inputсобытие не запускается из-за программных изменений (что кажется очевидным, но, возможно, противоречит неясному языку на соответствующей странице MDN; см. Здесь внизу, чтобы понять, что я имею в виду: разработчик .mozilla.org / en-US / docs / Web / Events / input )
mikermcneil
22

Я изменил ответ Lawwantsin так, и это работает для меня. Я использую событие keyup вместо нажатия клавиши, которая прекрасно работает.

$('#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')});
Dennkster
источник
22

не 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);
});
Developia
источник
3
что это за событие удаления?
Джеймс Кэт
7

Два варианта:

1) Для современных (вечнозеленых) браузеров: событие «input» будет действовать как альтернативное событие «change».

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

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

или

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

или (с помощью jQuery)

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

2) Для учета IE11 и современных (вечнозеленых) браузеров: это отслеживает изменения элементов и их содержимое внутри 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 />

superwf
источник
2
Это выглядит интересно. Может кто-нибудь дать какое-то объяснение? Wo
Во II
3

Вот что сработало для меня:

   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

Эта тема была очень полезна, пока я изучал эту тему.

Я изменил часть кода, доступного здесь, в плагин jQuery, чтобы он имел форму многократного использования, в первую очередь для удовлетворения моих потребностей, но другие могут оценить более простой интерфейс для быстрого запуска с использованием тегов contenteditable.

https://gist.github.com/3410122

Обновить:

В связи с растущей популярностью плагин был принят Makesites.org

Развитие будет продолжено отсюда:

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

tracend
источник
2

Вот решение, которое я в конечном итоге использовал и работает сказочно. Вместо этого я использую $ (this) .text (), потому что я использую однострочный div, который можно редактировать. Но вы также можете использовать .html () таким образом, чтобы вам не приходилось беспокоиться о области действия глобальной / неглобальной переменной, а оператор before действительно присоединяется к редактору 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!');
    }
});
user740433
источник
1

Чтобы избежать таймеров и кнопок «сохранить», вы можете использовать событие размытия, которое срабатывает, когда элемент теряет фокус. но чтобы быть уверенным, что элемент действительно был изменен (а не только сфокусирован и расфокусирован), его содержимое следует сравнить с его последней версией. или используйте событие keydown, чтобы установить некоторый «грязный» флаг для этого элемента.

joox
источник
1

Простой ответ в JQuery, я только что создал этот код и подумал, что он будет полезен и для других

    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
        }           
    });
Сарат
источник
Обратите внимание, что $("div [contenteditable=true]")будут выбраны все дочерние элементы div, прямо или косвенно, которые являются спорными.
Бруно Феррейра
1

Не JQuery ответ ...

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

Чтобы использовать его, вызовите (скажем) элемент заголовка с id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Этот элемент теперь будет редактироваться пользователем, пока он не потеряет фокус.

DrMcCleod
источник
5
Тьфу, почему downvote без объяснения причин? Это оказывает плохую услугу как человеку, который написал ответ, так и всем, кто читает ответ позже.
Майк Уиллис
0

Событие onchange не срабатывает при изменении элемента с атрибутом contentEditable, поэтому можно предложить добавить кнопку, чтобы «сохранить» издание.

Проверьте этот плагин, который обрабатывает проблему таким образом:

CMS
источник
для потомков - десять лет вперед и нет необходимости в кнопке «сохранить», проверьте jsfiddle принятого ответа
наслаждайтесь
0

Использование DOMCharacterDataModified в MutationEvents приведет к тому же. Тайм-аут настроен для предотвращения отправки неправильных значений (например, в Chrome у меня были некоторые проблемы с клавишей пробела)

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

Пример JSFIDDLE

janfabian
источник
1
+1 - DOMCharacterDataModifiedне срабатывает, когда пользователь изменяет существующий текст, например, применяя жирный шрифт или курсив. DOMSubtreeModifiedболее уместно в этом случае. Кроме того, люди должны помнить, что устаревшие браузеры не поддерживают эти события.
Энди Е
3
Просто обратите внимание, что Mutation Events устарели в w3c из-за проблем с производительностью. Больше можно найти в этом вопросе переполнения стека .
Jrullmann
0

Я сделал плагин JQuery, чтобы сделать это.

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

Вы можете просто позвонить $('.wysiwyg').wysiwygEvt();

Вы также можете удалить / добавить события, если хотите

Blowsie
источник
2
Это будет медленным и медленным, поскольку редактируемый контент становится длиннее ( innerHTMLдороже). Я бы порекомендовал использовать inputсобытие там, где оно существует, и прибегнуть к чему-то подобному, но с некоторым видом разоблачения.
Тим Даун
0

В угловых 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

Пожалуйста, следуйте следующему простому решению

In .ts:

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

в .html:

<div (blur)="getContent($event.target.innerHTML)" contenteditable [innerHTML]="content"></div>
Сахиль Ралкар
источник
-1

Проверьте эту идею. http://pastie.org/1096892

Я думаю, что это близко. HTML 5 действительно должен добавить событие изменения в спецификацию. Единственная проблема заключается в том, что функция обратного вызова оценивает if (before == $ (this) .html ()) перед тем, как содержимое фактически обновляется в $ (this) .html (). setTimeout не работает, и это печально. Дайте мне знать, что вы думаете.

Лоуренс Уайтсайд
источник
Попробуйте keyup вместо нажатия клавиши.
Aen Tan
-2

Основываясь на ответе @ 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>

Филипп Сенн
источник