sự kiện thay đổi nội dung


359

Tôi muốn chạy một chức năng khi người dùng chỉnh sửa nội dung của thuộc tính divwith contenteditable. Tương đương với một onchangesự kiện là gì?

Tôi đang sử dụng jQuery nên mọi giải pháp sử dụng jQuery đều được ưu tiên. Cảm ơn!


Tôi thường làm điều này : document.getElementById("editor").oninput = function() { ...}.
Basj

Câu trả lời:


311

Tôi khuyên bạn nên gắn người nghe vào các sự kiện chính được kích hoạt bởi yếu tố có thể chỉnh sửa, mặc dù bạn cần lưu ý rằng keydownkeypresscác sự kiện được kích hoạt trước khi nội dung được thay đổi. Điều này sẽ không bao gồm tất cả các phương tiện có thể thay đổi các nội dung: người sử dụng cũng có thể sử dụng cắt, sao chép và dán từ Edit hoặc menu trình duyệt bối cảnh, vì vậy bạn có thể muốn để xử lý cut copypastecác sự kiện quá. Ngoài ra, người dùng có thể thả văn bản hoặc nội dung khác, vì vậy có nhiều sự kiện hơn ở đó ( mouseupví dụ). Bạn có thể muốn thăm dò nội dung của phần tử dưới dạng dự phòng.

CẬP NHẬT 29 tháng 10 năm 2014

Sự kiện HTML5input là câu trả lời trong dài hạn. Tại thời điểm viết bài, nó được hỗ trợ cho contenteditablecác thành phần trong Mozilla hiện tại (từ Firefox 14) và trình duyệt WebKit / Blink, nhưng không phải IE.

Bản giới thiệu:

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

Bản trình diễn: http://jsfiddle.net/ch6yn/2691/


11
Tôi cũng nên thêm rằng nó có thể thay đổi một yếu tố có thể chỉnh sửa bằng cách sử dụng chỉnh sửa menu của trình duyệt hoặc menu ngữ cảnh để cắt, sao chép hoặc dán nội dung, vì vậy bạn sẽ cần phải xử lý cut, copypastecác sự kiện trong các trình duyệt hỗ trợ họ (IE 5+, Firefox 3.0+, Safari 3+, Chrome)
Tim Down

4
Bạn có thể sử dụng keyup và bạn sẽ không gặp vấn đề về thời gian.
Blowsie

2
@Blowsie: Có, nhưng bạn có khả năng kết thúc với rất nhiều thay đổi xảy ra từ một phím nhấn tự động lặp lại trước khi sự kiện bắt đầu. Ngoài ra còn có các cách khác để thay đổi văn bản có thể chỉnh sửa không được xem xét bởi câu trả lời này, chẳng hạn như kéo và thả và chỉnh sửa thông qua menu Chỉnh sửa và ngữ cảnh. Về lâu dài, tất cả điều này sẽ được bao phủ bởi inputsự kiện HTML5 .
Tim Down

1
Tôi đang làm keyup với gỡ lỗi.
Aen Tân

1
@AndroidDev Tôi đã thử nghiệm Chrome và sự kiện đầu vào cũng được kích hoạt khi sao chép và cắt theo yêu cầu của thông số kỹ thuật: w3c.github.io/uievents/#events-inputevents
xương cá

188

Đây là một phiên bản hiệu quả hơn sử dụng oncho tất cả các nội dung. Nó dựa trên các câu trả lời hàng đầu ở đây.

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

Dự án ở đây: https://github.com/balupton/html5edit


Làm việc hoàn hảo với tôi, cảm ơn vì cả CoffeeScript và Javascript
jwilcox09

7
Có một số thay đổi sẽ không bị bắt bởi mã này cho đến khi nội dung có thể bị mờ. Ví dụ: kéo và thả, cắt từ menu ngữ cảnh và dán từ cửa sổ trình duyệt. Tôi đã thiết lập một trang ví dụ sử dụng mã này mà bạn có thể tương tác ở đây .
jrullmann

@jrullmann Có Tôi gặp vấn đề với mã này khi kéo và thả hình ảnh vì nó không nghe tải hình ảnh (đó là vấn đề xử lý thao tác thay đổi kích thước trong trường hợp của tôi)
Sebastien Lorber

@jrullmann Rất hay, điều này hoạt động ngay cả khi thay đổi giá trị với javascript, hãy thử trong bảng điều khiển : document.getElementById('editor_input').innerHTML = 'ssss'. Hạn chế là nó yêu cầu IE11

1
@NadavB không nên, vì đó là những gì "if ($ this.data ('before')! == $ this.html ()) {" dành cho
balupton

52

Cân nhắc sử dụng M mutObserver . Các trình quan sát này được thiết kế để phản ứng với các thay đổi trong DOM và như là một sự thay thế biểu diễn cho Sự kiện đột biến .

Ưu điểm:

  • Hỏa hoạn khi có bất kỳ thay đổi nào xảy ra, rất khó đạt được bằng cách lắng nghe các sự kiện chính như được đề xuất bởi các câu trả lời khác. Ví dụ, tất cả những thứ này đều hoạt động tốt: kéo và thả, in nghiêng, sao chép / cắt / dán thông qua menu ngữ cảnh.
  • Được thiết kế với hiệu suất trong tâm trí.
  • Mã đơn giản, đơn giản. Việc hiểu và gỡ lỗi mã dễ dàng hơn rất nhiều khi nghe một sự kiện thay vì mã nghe 10 sự kiện.
  • Google có một thư viện tóm tắt đột biến tuyệt vời giúp việc sử dụng M mutObservers trở nên rất dễ dàng.

Nhược điểm:

  • Yêu cầu phiên bản Firefox gần đây (14.0+), Chrome (18+) hoặc IE (11+).
  • API mới để hiểu
  • Không có nhiều thông tin có sẵn về thực tiễn tốt nhất hoặc nghiên cứu trường hợp

Tìm hiểu thêm:

  • Tôi đã viết một đoạn nhỏ để so sánh bằng cách sử dụng M mutObserers để xử lý nhiều sự kiện khác nhau. Tôi đã sử dụng mã của balupton vì câu trả lời của anh ấy có nhiều sự ủng hộ nhất.
  • Mozilla có một trang tuyệt vời về API
  • Hãy xem thư viện MutingSummary

Nó không hoàn toàn giống như inputsự kiện HTML5 (được hỗ trợ contenteditabletrong tất cả các trình duyệt WebKit và Mozilla hỗ trợ người quan sát đột biến), vì đột biến DOM có thể xảy ra thông qua tập lệnh cũng như đầu vào của người dùng, nhưng đó là một giải pháp khả thi cho các trình duyệt đó. Tôi tưởng tượng nó có thể gây hại cho hiệu suất nhiều hơn inputsự kiện, nhưng tôi không có bằng chứng cứng cho việc này.
Tim Xuống

2
+1, nhưng bạn có nhận ra rằng Sự kiện Đột biến không báo cáo tác động của nguồn cấp dữ liệu trong một nội dung có thể chỉnh sửa được không? Nhấn enter trong đoạn trích của bạn.
citykid

4
@citykid: Đó là vì đoạn trích chỉ theo dõi các thay đổi đối với dữ liệu ký tự. Cũng có thể quan sát các thay đổi cấu trúc DOM. Xem jsfiddle.net/4n2Gz/1 chẳng hạn.
Tim Down

Internet Explorer 11+ hỗ trợ Trình quan sát đột biến .
Sampson

Tôi đã có thể xác minh (ít nhất là trong Chrome 43.0.2357.130) rằng inputsự kiện HTML5 sẽ kích hoạt trong từng tình huống này (cụ thể là kéo và thả, in nghiêng, sao chép / cắt / dán qua menu ngữ cảnh). Tôi cũng đã thử nghiệm bẻ cong w / cmd / ctrl + b và nhận được kết quả mong đợi. Tôi cũng đã kiểm tra và có thể xác minh rằng inputsự kiện này không kích hoạt các thay đổi theo chương trình (điều này có vẻ hiển nhiên, nhưng có thể trái ngược với một số ngôn ngữ khó hiểu trên trang MDN có liên quan; xem phần dưới cùng của trang ở đây để biết ý tôi là: nhà phát triển .mozilla.org / en-US / docs / Web / Sự kiện / đầu vào )
mikermcneil

22

Tôi đã sửa đổi câu trả lời của luật sư như vậy và điều này hiệu quả với tôi. Tôi sử dụng sự kiện keyup thay vì keypress hoạt động tuyệt vời.

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

Câu trả lời nhanh và bẩn của 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
sự kiện xóa này là gì?
James Cat

7

Hai lựa chọn:

1) Đối với các trình duyệt hiện đại (thường xanh): Sự kiện "đầu vào" sẽ hoạt động như một sự kiện "thay đổi" thay thế.

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

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

hoặc là

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

hoặc (với jQuery)

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

2) Để giải thích cho IE11 và các trình duyệt hiện đại (thường xanh): Đồng hồ này để thay đổi thành phần và nội dung của chúng bên trong div.

https://developer.mozilla.org/en-US/docs/Web/API/MutingObserver

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
Điều này có vẻ thú vị. Ai đó có thể cung cấp một số lời giải thích? 😇
WoIIe

3

Đây là những gì làm việc cho tôi:

   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

Chủ đề này rất hữu ích trong khi tôi đang điều tra chủ đề.

Tôi đã sửa đổi một số mã có sẵn ở đây thành một plugin jQuery để nó ở dạng có thể sử dụng lại, chủ yếu để đáp ứng nhu cầu của tôi nhưng những người khác có thể đánh giá cao giao diện đơn giản hơn để bắt đầu sử dụng các thẻ có thể nội dung.

https://gist.github.com/3410122

Cập nhật:

Do mức độ phổ biến ngày càng tăng, plugin đã được Makesites.org chấp nhận

Sự phát triển sẽ tiếp tục từ đây:

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


2

Đây là giải pháp tôi đã sử dụng và làm việc tuyệt vời. Tôi sử dụng $ (this) .text () thay vì tôi chỉ sử dụng div một dòng có thể chỉnh sửa nội dung. Nhưng bạn cũng có thể sử dụng .html () theo cách này mà bạn không phải lo lắng về phạm vi của biến toàn cục / không toàn cầu và trước đây thực sự được gắn vào div trình soạn thảo.

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

Để tránh bộ hẹn giờ và nút "lưu", bạn có thể sử dụng sự kiện mờ sẽ kích hoạt khi phần tử mất tiêu điểm. nhưng để chắc chắn rằng phần tử đã thực sự thay đổi (không chỉ tập trung và làm mất nét), nội dung của nó nên được so sánh với phiên bản cuối cùng của nó. hoặc sử dụng sự kiện keydown để đặt một số cờ "bẩn" trên thành phần này.


1

Một câu trả lời đơn giản trong JQuery, tôi chỉ tạo mã này và nghĩ rằng nó cũng hữu ích cho những người khác

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

Lưu ý rằng $("div [contenteditable=true]")sẽ chọn tất cả trẻ em của một div, trực tiếp hoặc gián tiếp, có thể nội dung được.
Bruno Ferreira

1

Câu trả lời không của JQuery ...

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

Để sử dụng nó, hãy gọi (nói) một phần tử tiêu đề với id = "myHeader"

makeEditable(document.getElementById('myHeader'))

Phần tử đó bây giờ sẽ được người dùng chỉnh sửa cho đến khi mất tập trung.


5
Ugh, tại sao downvote không có lời giải thích? Điều này không có tác dụng đối với cả người viết câu trả lời và tất cả những người đọc câu trả lời sau đó.
Mike Willis

0

Sự kiện onchange không kích hoạt khi một thành phần có thuộc tính contentEditable bị thay đổi, một cách tiếp cận được đề xuất có thể là thêm một nút, để "lưu" phiên bản.

Kiểm tra plugin này xử lý vấn đề theo cách đó:


cho hậu thế, mười năm tới và không cần nút "lưu", hãy kiểm tra jsfiddle của câu trả lời được chấp nhận
xem lại

0

Sử dụng DOMCharacterDataModified trong M mutEvents sẽ dẫn đến điều tương tự. Thời gian chờ được thiết lập để ngăn gửi các giá trị không chính xác (ví dụ: trong Chrome tôi gặp một số vấn đề với phím cách)

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

Ví dụ về JSFIDDLE


1
+1 - DOMCharacterDataModifiedsẽ không kích hoạt khi người dùng sửa đổi văn bản hiện có, ví dụ như áp dụng chữ in đậm hoặc in nghiêng. DOMSubtreeModifiedlà thích hợp hơn trong trường hợp này. Ngoài ra, mọi người nên nhớ rằng các trình duyệt cũ không hỗ trợ các sự kiện này.
Andy E

3
Chỉ cần lưu ý rằng Sự kiện Đột biến bị w3c phản đối vì các vấn đề về hiệu suất. Nhiều hơn có thể được tìm thấy trong câu hỏi Stack Overflow này .
jrullmann

0

Tôi đã xây dựng một plugin jQuery để làm điều này.

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

Bạn chỉ có thể gọi $('.wysiwyg').wysiwygEvt();

Bạn cũng có thể xóa / thêm sự kiện nếu muốn


2
Điều đó sẽ trở nên chậm và chậm trễ vì nội dung có thể chỉnh sửa sẽ dài hơn ( innerHTMLđắt tiền). Tôi sẽ khuyên bạn nên sử dụng inputsự kiện mà nó tồn tại và quay trở lại một cái gì đó như thế này nhưng với một số loại tranh luận.
Tim Down

0

Trong góc 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

Hãy làm theo các giải pháp đơn giản sau đây

Trong .ts:

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

trong .html:

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

-1

Kiểm tra ý tưởng này ra. http://pastie.org/1096892

Tôi nghĩ rằng nó gần. HTML 5 thực sự cần thêm sự kiện thay đổi vào thông số kỹ thuật. Vấn đề duy nhất là hàm gọi lại đánh giá if (before == $ (this) .html ()) trước khi nội dung thực sự được cập nhật bằng $ (this) .html (). setTimeout không hoạt động, và nó buồn. Cho tôi biết bạn nghĩ gì.


Hãy thử keyup thay vì nhấn phím.
Aen Tân

-2

Dựa trên câu trả lời của @ 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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.