可编辑元素的更改事件

494
我希望在用户编辑具有contenteditable属性的
内容时运行函数。相当于onchange事件的是什么?
我正在使用jQuery,因此任何使用jQuery的解决方案都是首选。谢谢!

3
我通常会这样做:document.getElementById("editor").oninput = function() { ...} - Basj
1
页面下方有一篇很好的答案,发布于2020年:https://dev59.com/N3M_5IYBdhLWcg3waSX9#49287032 - Jack Steam
22个回答

425

2022更新

正如评论中指出的那样,这并没有回答问题所要求的与change事件等同的内容,而是input事件。不过,我将保留它原来的形式。

我建议在可编辑元素上附加键盘事件监听器,但需要注意keydownkeypress事件是在更改内容本身之前触发的。这不会涵盖每种可能更改内容的方法:用户还可以使用“剪切”、“复制”和“粘贴”从编辑或上下文浏览器菜单,因此您可能还想处理cutcopypaste事件。此外,用户还可以拖放文本或其他内容,因此有更多的事件(例如mouseup)。您可能需要作为回退轮询元素的内容。

更新于2014年10月29日

HTML5 input 事件是长期的答案。在本文写作时,在当前版本的Mozilla(从Firefox14)和WebKit/Blink浏览器中支持contenteditable元素,但不支持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/


14
我需要指出的是,通过使用浏览器的编辑菜单或上下文菜单来剪切、复制或粘贴内容,可以更改可编辑元素。因此,在支持这些事件的浏览器中(IE 5+、Firefox 3.0+、Safari 3+、Chrome),您需要处理“剪切”、“复制”和“粘贴”事件。 - Tim Down
6
你可以使用keyup事件,这样就不会有时间上的问题。 - Blowsie
3
@Blowsie: 是的,但在事件触发之前,您可能会因为自动重复按键而出现大量更改。还有其他方式可以更改可编辑文本,这些方式没有被该答案考虑到,例如拖放和通过编辑和上下文菜单进行的编辑。从长远来看,所有这些都应该被HTML5的“input”事件所覆盖。 - Tim Down
1
我正在使用防抖技术进行键盘松开事件处理。 - Aen Tan
1
@AndroidDev 我测试了Chrome,输入事件也按照规范要求在复制、粘贴和剪切时触发:https://w3c.github.io/uievents/#events-inputevents - fishbone
显示剩余11条评论

219

这里有一个更高效的版本,它使用 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


1
对我来说完美地工作了,感谢CoffeeScript和Javascript。 - jwilcox09
7
这段代码有些更改只有在 contenteditable 失去焦点后才能被捕捉到,例如:拖放、使用上下文菜单剪切和从浏览器窗口粘贴。我已经设置了一个示例页面,其中使用了这段代码,您可以在此处与之交互。 - jrullmann
@jrullmann 是的,当我拖放图像时,我遇到了这段代码的问题,因为它没有监听图像加载(在我的情况下,处理调整大小操作是个问题)。 - Sebastien Lorber
@jrullmann 很好,即使使用JavaScript更改值也可以工作,请在控制台中尝试:document.getElementById('editor_input').innerHTML = 'ssss'。缺点是它需要IE11。 - user133408
1
@NadavB 不应该影响,因为这就是 "if ($this.data('before') !== $this.html()) {" 的作用。 - balupton
显示剩余5条评论

68
考虑使用MutationObserver。这些观察器旨在对DOM中的更改作出反应,并作为Mutation Events的高效替代品。
优点:
- 当发生任何更改时都会触发,这很难通过监听关键事件来实现,如其他答案所建议的。例如,所有这些都可以很好地工作:拖放、斜体、通过上下文菜单复制/剪切/粘贴。 - 专为性能而设计。 - 简单、直接的代码。与监听10个事件的代码相比,监听一个事件的代码更容易理解和调试。 - Google有一个出色的mutation summary library,使使用MutationObservers非常容易。
缺点:
  • 需要使用最新版本的Firefox(14.0+),Chrome(18+)或IE(11+)。
  • 需要了解新的API
  • 目前没有太多关于最佳实践或案例研究的信息可用

了解更多:

  • 我写了一个小snippet来比较使用MutationObserers处理各种事件。我使用了balupton的代码,因为他的answer得到了最多的赞。
  • Mozilla有一个关于该API的优秀页面
  • 看一下MutationSummary

2
这与HTML5的input事件不完全相同(在支持变异观察器的所有WebKit和Mozilla浏览器中,它支持contenteditable),因为DOM变异可能会通过脚本以及用户输入发生,但对于那些浏览器来说,这是一个可行的解决方案。我想它可能比input事件更影响性能,但我没有确凿的证据。 - Tim Down
2
+1,但你是否意识到Mutation Events在contenteditable中不会报告换行符的影响?请在你的代码片段中按Enter键。 - citykid
5
那是因为代码片段只监视字符数据的变化。也可以观察DOM结构的改变,例如请参考http://jsfiddle.net/4n2Gz/1/。 - Tim Down
Internet Explorer 11+支持Mutation Observers - Sampson
我已经验证过(至少在Chrome 43.0.2357.130中),HTML5的“input”事件会在这些情况下触发(具体包括拖放、斜体、通过上下文菜单复制/剪切/粘贴)。我还测试了使用cmd/ctrl+b加粗并获得了预期结果。我还检查并验证了“input”事件不会在编程更改时触发(这似乎是显而易见的,但与相关MDN页面上一些令人困惑的语言相反;请参见此处页面底部以了解详情:https://developer.mozilla.org/en-US/docs/Web/Events/input) - mikermcneil

29

无需使用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);
});

5
这个"delete event"是什么? - James Cat

24

我已经按照lawwantsin的答案进行了修改,以下是我的修改结果并且它对我有效。我使用了keyup事件而不是keypress, 这个方法非常好用。

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

正是我所需要的。不过我去掉了keyup,因为我需要在元素失去焦点时触发警报。 - code-is-life

23

两个选项:

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

2
只需使用 input 事件即可!如果需要支持 IE11,则可以使用变动观察器。简洁明了,喜欢它。 - Jack Steam
1
不完全是这样。问题在于 OP 想要在最终更改时执行某些操作(就像“更改”事件一样),而 input 会在您键入时触发。我正在触发一个 AJAX 请求 - 我不想在每次按键时都这样做。许多其他答案解释了如何处理这个问题。 - Auspex

11

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


4
这看起来很有趣。可以有人提供一些解释吗? - ˈvɔlə

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


3

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

这个元素现在可以被用户编辑,直到失去焦点为止。


7
为什么没有解释就踩了这个回答?这对于回答者和后来阅读答案的人都是一种伤害,希望能够说明原因。 - Mike Willis
这段代码使一个元素 contenteditable 直到失去焦点。我认为这并没有回答问题。问题是:“当 contenteditable 元素改变时,如何运行一个函数?” - Jack Steam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接