可编辑的变更事件


359

当用户编辑divwith contenteditable属性的内容时,我想运行一个函数。什么相当于一个onchange事件?

我使用的是jQuery,因此首选使用jQuery的解决方案。谢谢!


我通常这样做:document.getElementById("editor").oninput = function() { ...}
巴斯基

Answers:


311

我建议将侦听器附加到由editable元素触发的关键事件上,尽​​管您需要注意,keydown并且keypress在更改内容本身之前会触发事件。这不会涵盖更改内容的所有可能方法:用户还可以从“编辑”或上下文浏览器菜单中使用剪切,复制和粘贴,因此您可能也想处理cut copypaste事件。另外,用户可以放置文本或其他内容,因此在那里有更多事件(mouseup例如,)。您可能希望轮询元素的内容作为后备。

2014年10月29日更新

从长远来看,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
我还应该补充一点,可以通过使用浏览器的“编辑”菜单或上下文菜单来剪切,复制或粘贴内容来更改可编辑元素,因此您需要处理cutcopy以及paste支持它们的浏览器中的事件(IE 5 +,Firefox 3.0 +,Safari 3 +,Chrome)
Tim Down

4
您可以使用keyup,并且不会出现计时问题。
Blowsie 2011年

2
@Blowsie:是的,但是在事件触发之前,自动重复的按键可能会导致很多更改。还有其他更改此答案未考虑的可编辑文本的方法,例如通过“编辑”和上下文菜单进行拖放和编辑。长期而言,HTML5 input事件应涵盖所有这些。
Tim Down

1
我正在用去抖动功能进行抠像。
Aen Tan

1
@AndroidDev我testet Chrome和输入事件也被触发的复制粘贴和剪切所要求的规格:w3c.github.io/uievents/#events-inputevents
鱼刺

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


对我来说非常完美,感谢CoffeeScript和Javascript
jwilcox09 2012年

7
在contenteditable模糊之前,此代码不会捕获某些更改。例如:拖放,从上下文菜单中剪切以及从浏览器窗口中粘贴。我已经建立了一个使用此代码的示例页面,您可以在此处与之交互。
jrullmann

@jrullmann是的,我在拖放图像时遇到了这段代码的问题,因为它不收听图像负载(在我的情况下,这是处理调整大小操作的问题)
Sebastien Lorber 2014年

@jrullmann很好,即使使用javascript更改值也可以使用,请在控制台中尝试:document.getElementById('editor_input').innerHTML = 'ssss'。缺点是它需要IE11

1
@NadavB不应该这样,因为那是“ if($ this.data('before')!== $ this.html()){”的意思
balupton,

52

考虑使用MutationObserver。这些观察者旨在对DOM中的更改做出反应,并作为Mutation Events的高性能替代品。

优点:

  • 当发生任何更改时触发,这很难通过听其他答案建议的关键事件来实现。例如,所有这些都很好用:通过上下文菜单拖放,斜体,复制/剪切/粘贴。
  • 设计时注重性能。
  • 简单,直接的代码。与侦听10个事件的代码相比,理解和调试侦听一个事件的代码要容易得多。
  • Google拥有出色的变异摘要库,这使使用MutationObservers非常容易。

缺点:

  • 需要最新版本的Firefox(14.0 +),Chrome(18+)或IE(11+)。
  • 需要了解的新API
  • 关于最佳实践或案例研究的信息还很多

学到更多:

  • 我写了一个小片段来比较使用MutationObserers处理各种事件。我使用balupton的代码,因为他的回答最多。
  • Mozilla在API上有一个很棒的页面
  • 看看MutationSummary

它与HTML5 input事件(contenteditable在所有支持突变观察者的WebKit和Mozilla浏览器中都支持)并不完全相同,因为DOM突变可以通过脚本以及用户输入发生,但是对于那些浏览器而言,这是一个可行的解决方案。我想这可能比input比赛对表演的危害更大,但是对此我没有确凿的证据。
Tim Down

2
+1,但您是否意识到“突变事件”未在内容可修改的内容中报告换行的效果?在代码段中按Enter。
citykid 2013年

4
@citykid:这是因为该代码段仅监视字符数据的更改。也可以观察DOM结构的变化。例如,请参阅jsfiddle.net/4n2Gz/1
Tim Down

Internet Explorer 11+支持Mutation Observers
桑普森2014年

我能够验证(至少在Chrome 43.0.2357.130中)input在以下每种情况下(特别是通过上下文菜单进行拖放,斜体化,复制/剪切/粘贴)都会触发HTML5 事件。我还测试了w / cmd / ctrl + b的粗体,并获得了预期的结果。我还检查并能够验证该input事件不会在程序更改上触发(这似乎很明显,但可以说与相关的MDN页面上的某些令人困惑的语言相反;请参阅此处的底部以了解我的意思:开发人员.mozilla.org / zh-CN / docs / Web / Events / input
mikermcneil 2015年

22

我像这样修改了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')});

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
这是什么删除事件?
James Cat

7

两种选择:

1)对于现代(常绿)浏览器: “输入”事件将作为替代的“更改”事件。

https://developer.mozilla.org/zh-CN/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/zh-CN/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
这看起来很有趣。有人可以提供一些解释吗?😇–
WoII

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


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

1

为了避免使用计时器和“保存”按钮,可以在元素失去焦点时使用模糊事件触发。但要确保该元素实际上已更改(不仅仅是聚焦和散焦),应将其内容与上一个版本进行比较。或使用keydown事件在此元素上设置一些“脏”标志。


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

用户现在可以编辑该元素,直到失去焦点。


5
gh,为什么在无解释的情况下投票?这对写答案的人以及以后阅读答案的每个人都不利。
Mike Willis


0

在MutationEvents下使用DOMCharacterDataModified将导致相同的结果。已设置超时时间以防止发送错误的值(例如,在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在这种情况下更合适。另外,人们应该记住,旧版浏览器不支持这些事件。
Andy E

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存在的事件,并回退到类似事件,但要进行某种程度的反跳。
Tim Down

0

在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

请遵循以下简单的解决方案

在.ts中:

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

在.html中:

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

-1

看看这个想法。 http://pastie.org/1096892

我想快了。HTML 5确实需要将change事件添加到规范中。唯一的问题是,在实际在$(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>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.