змістовні події зміни


359

Я хочу , щоб запустити функцію , коли користувач редагує вміст divз contenteditableатрибутом. Що еквівалент onchangeподії?

Я використовую jQuery, тому будь-які рішення, які використовують jQuery, є кращими. Дякую!


Я зазвичай роблю це: document.getElementById("editor").oninput = function() { ...}.
Бась

Відповіді:


311

Я б запропонував приєднати слухачів до ключових подій, запущених редагованим елементом, хоча ви повинні мати на увазі, що keydownі keypressподії запускаються перед зміною самого вмісту. Це не охоплює всі можливі способи зміни вмісту: користувач також може використовувати вирізати, копіювати та вставляти з меню редагування або контекстного меню браузера, тому ви можете також обробляти 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
Ви можете скористатися клавіатурою, і у вас не буде проблеми з термінами.
Blowsie

2
@Blowsie: Так, але потенційно ви можете змінити багато змін, які відбудуться автоматично повторним натисканням клавіші до початку події. Існують також інші способи зміни редагованого тексту, які не враховуються даною відповіддю, наприклад перетягування та редагування через меню Правка та контекст. Довгостроково це все повинно бути охоплено inputподії HTML5 .
Тім Даун

1
Я роблю клавіатуру з debounce.
Aen Tan

1
@AndroidDev I testet Chrome, а подія введення також обстріляли копію пасти і вирізати відповідно з вимогами специфікацій: w3c.github.io/uievents/#events-inputevents
Fishbone

188

Ось більш ефективна версія, яка використовується onдля всіх матеріалів, що містять вміст. Тут засновані основні відповіді.

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


Для мене прекрасно працювали, дякую і за CoffeeScript, і для Javascript
jwilcox09

7
Є деякі зміни, які не будуть вхоплені цим кодом, доки змістовне значення не розминеться. Наприклад: перетягування та випадання, вирізання з контекстного меню та вставка з вікна браузера. Я налаштував сторінку прикладу, яка використовує цей код, з яким ви можете тут взаємодіяти .
jrullmann

@jrullmann Так, у мене виникли проблеми з цим кодом під час перетягування зображень, оскільки він не слухає завантаження зображення (в моєму випадку було проблемою з керуванням розміром)
Sebastien Lorber,

@jrullmann Дуже приємно, це працює навіть тоді зміна значення з JavaScript, спробуйте в консолі: document.getElementById('editor_input').innerHTML = 'ssss'. Недолік полягає в тому, що для нього потрібен IE11

1
@NadavB не повинен, тому що це "якщо ($ this.data ('до')! == $ this.html ()) {" призначено для
balupton

52

Подумайте про використання MutationObserver . Ці спостерігачі розраховані на те, щоб реагувати на зміни у DOM та слугувати заміною Mutation Events .

Плюси:

  • Запускається, коли відбувається будь-яка зміна, чого важко досягти, слухаючи ключові події, як це запропоновано іншими відповідями. Наприклад, усі вони добре працюють: перетягування, курсив, копіювання / вирізання / вставка через контекстне меню.
  • Розроблений з врахуванням продуктивності.
  • Простий, простий код. Це набагато простіше зрозуміти і налагодити код, який слухає одну подію, а не код, який слухає 10 подій.
  • У Google є чудова бібліотека підсумків мутацій, що робить використання MutationObservers дуже простим.

Мінуси:

  • Потрібна остання версія Firefox (14.0+), Chrome (18+) або IE (11+).
  • Новий API для розуміння
  • Ще мало інформації про найкращі практики чи тематичні дослідження

Вивчайте більше:

  • Я написав невеликий фрагмент для порівняння з використанням MutationObserers для обробки різноманітних подій. Я використав код балуптона, оскільки його відповідь має найбільше відгуків.
  • Mozilla має чудову сторінку в API
  • Погляньте на бібліотеку MutationSummary

Це не зовсім те саме, що inputподія HTML5 (яка підтримується contenteditableу всіх браузерах WebKit і Mozilla, які підтримують спостерігачів мутацій), оскільки мутація DOM може відбуватися як через скрипт, так і за допомогою введення користувача, але це життєздатне рішення для цих браузерів. Я думаю, що це може зашкодити виступу більше, ніж inputподія, але я не маю твердих доказів для цього.
Тім Даун

2
+1, але чи ви зрозуміли, що події мутації не повідомляють про ефекти каналів рядків, що містяться в розрізі? Натисніть клавішу Enter у своєму фрагменті.
citykid

4
@citykid: Це тому, що фрагмент спостерігає лише за змінами в даних про символи. Можна також спостерігати структурні зміни DOM. Наприклад , див. Jsfiddle.net/4n2Gz/1 .
Tim Down

Internet Explorer 11+ підтримує мутаційні спостерігачі .
Сампсон

Мені вдалося переконатись (принаймні в Chrome 43.0.2357.130), що inputподія HTML5 спрацьовує у кожній із цих ситуацій (зокрема перетягування, курсив, копіювання / вирізання / вставка через контекстне меню). Я також випробував сміливий w / cmd / ctrl + b і отримав очікувані результати. Я також перевірив і зміг переконатися, що inputподія не спрацьовує через програмні зміни (що здається очевидним, але, суперечить, суперечить деякій заплутаній мові на відповідній сторінці MDN; дивіться внизу сторінки тут, щоб побачити, що я маю на увазі: розробник .mozilla.org / en-US / docs / Web / Події / вхід )
mikermcneil

22

Я змінив відповідь законніків так, і це працює для мене. Я використовую подію клавіатури замість натискання клавіші, яка чудово працює.

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

швидка та брудна відповідь, що не стосується 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
що це за подія видалення?
Джеймс Кіт

7

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

1) Для сучасних (вічнозелених) браузерів: подія "введення" буде виступати альтернативною подією "зміни".

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 />


2
Це виглядає цікаво. Хтось може надати пояснення? 😇
WoIIe

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, щоб він був у використанні для повторного використання, в першу чергу, щоб задовольнити мої потреби, але інші можуть оцінити більш простий інтерфейс для швидкого запуску, використовуючи змістовні теги.

https://gist.github.com/3410122

Оновлення:

Завдяки зростаючій популярності плагін був прийнятий Makesites.org

Розвиток триватиме звідси:

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


2

Ось рішення, яке я закінчив використовувати і працює казково. Я використовую натомість $ (this) .text (), тому що я просто використовую одно рядковий div, який може редагувати вміст. Але ви також можете використовувати .html () таким чином, вам не доведеться турбуватися про сферу глобальної / не глобальної змінної, а раніше фактично додається до редактора 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

Щоб уникнути таймерів та кнопок "збереження", ви можете використовувати розмиття подій, що виникає при пожежі, коли елемент втрачає фокус. але щоб бути впевненим, що елемент був фактично змінений (а не просто зосереджений і розфокусований), його вміст слід порівняти з останньою версією. або використовувати подію клавіш, щоб встановити якийсь "брудний" прапор на цьому елементі.


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]")вибиратимуть всі дитячі диві, прямо чи опосередковано, які можуть бути змістовними.
Бруно Феррейра

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'))

Цей елемент тепер буде редагований користувачем, поки він не втратить фокус.


5
Тьфу, чому потік без пояснень? Це створює сумніви як для людини, яка написала відповідь, так і всіх, хто прочитає відповідь пізніше.
Майк Вілліс

0

Подія onchange не запускається, коли елемент із атрибутом contentEditable змінено, пропонованим підходом може бути додавання кнопки для "збереження" видання.

Перевірте цей плагін, який вирішує проблему таким чином:


щодо нащадків, десять років вперед і немає потреби в кнопці "збереження", перевірте jsfiddle прийнятої відповіді
revelt

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


1
+1 - DOMCharacterDataModifiedне запускається, коли користувач модифікує існуючий текст, наприклад, застосовуючи жирний шрифт або курсив. DOMSubtreeModifiedє більш доречним у цьому випадку. Також люди повинні пам’ятати, що застарілі браузери не підтримують ці події.
Енді Е

3
Лише зауважте, що події мутації амортизуються 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();

Ви також можете видалити / додати події, якщо бажаєте


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

Дотримуйтесь наступного простого рішення

В .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 не працює, і це сумно. Дайте мені знати, що ви думаєте.


Спробуйте клавіатуру замість натискання клавіші.
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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.