เหตุการณ์การเปลี่ยนแปลงที่น่าพึงพอใจ


359

ฉันต้องการที่จะเรียกใช้ฟังก์ชั่นเมื่อผู้ใช้แก้ไขเนื้อหาที่divมีcontenteditableแอตทริบิวต์ onchangeเหตุการณ์เท่ากับอะไร

ฉันใช้ jQuery ดังนั้นจึงควรเลือกใช้โซลูชันใด ๆ ที่ใช้ jQuery ขอบคุณ!


document.getElementById("editor").oninput = function() { ...}ฉันมักจะทำสิ่งนี้:
Basj

คำตอบ:


311

ฉันขอแนะนำให้แนบผู้ฟังเข้ากับเหตุการณ์สำคัญที่เกิดขึ้นจากองค์ประกอบที่สามารถแก้ไขได้แม้ว่าคุณจะต้องระวังkeydownและkeypressเหตุการณ์นั้นจะถูกไล่ออกก่อนที่เนื้อหาจะเปลี่ยนไป สิ่งนี้จะไม่ครอบคลุมวิธีการที่เป็นไปได้ในการเปลี่ยนแปลงเนื้อหา: ผู้ใช้ยังสามารถใช้ตัดคัดลอกและวางจากเมนูแก้ไขหรือเมนูเบราว์เซอร์บริบทดังนั้นคุณอาจต้องการจัดการcut copyและpasteเหตุการณ์ด้วย นอกจากนี้ผู้ใช้สามารถวางข้อความหรือเนื้อหาอื่น ๆ ดังนั้นจึงมีกิจกรรมเพิ่มเติมmouseupตัวอย่างเช่น) คุณอาจต้องการสำรวจความคิดเห็นเนื้อหาขององค์ประกอบว่าเป็นทางเลือก

อัปเดต 29 ตุลาคม 2557

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
Tim Down

1
ฉันกำลังทำคีย์อัพกับ debounce
Aen Tan

1
@AndroidDev ฉัน testet Chrome และเหตุการณ์อินพุทถูกวางลงบนคัดลอกและตัดตามที่กำหนดโดยข้อกำหนด: w3c.github.io/uievents/#events-inputevents
ก้างปลา

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 ใช่ครับผมมีปัญหากับรหัสนี้เมื่อลากและวางภาพเพราะมันไม่ได้ฟังโหลดภาพ (มันเป็นปัญหาที่จะจัดการกับการดำเนินการปรับขนาดในกรณีของฉัน)
เซบาสเตียน Lorber

@jrullmann ดีมากงานนี้แม้แล้วเปลี่ยนค่ากับ javascript, document.getElementById('editor_input').innerHTML = 'ssss'ลองในคอนโซล: ข้อเสียคือต้องใช้ IE11

1
@NadavB ไม่ควรเพราะนี่คือสิ่งที่ "ถ้า ($ this.data ('ก่อน')! == $ this.html ()) {" สำหรับ
balupton

52

พิจารณาใช้MutationObserver ผู้สังเกตการณ์เหล่านี้ได้รับการออกแบบมาเพื่อตอบสนองต่อการเปลี่ยนแปลงใน DOM และเป็นการทดแทนนักแสดงต่อเหตุการณ์การกลายพันธุ์กลายพันธุ์เหตุการณ์

ข้อดี:

  • ไฟไหม้เมื่อมีการเปลี่ยนแปลงใด ๆเกิดขึ้นซึ่งเป็นการยากที่จะทำให้สำเร็จโดยการฟังเหตุการณ์สำคัญตามคำแนะนำอื่น ๆ ตัวอย่างเช่นสิ่งเหล่านี้ใช้ได้ดี: ลาก & วาง, ตัวเอียง, คัดลอก / ตัด / วางผ่านเมนูบริบท
  • ออกแบบโดยคำนึงถึงประสิทธิภาพ
  • ง่าย ๆ รหัสตรงไปตรงมา ง่ายต่อการเข้าใจและแก้จุดบกพร่องรหัสที่ฟังเหตุการณ์หนึ่งมากกว่ารหัสที่ฟังเหตุการณ์ 10 เหตุการณ์
  • Google มีห้องสมุดสรุปการกลายพันธุ์ที่ยอดเยี่ยมซึ่งทำให้การใช้ MutationObservers ง่ายมาก

จุดด้อย:

  • ต้องใช้ Firefox เวอร์ชันล่าสุด (14.0+), Chrome (18+) หรือ IE (11+)
  • API ใหม่ที่เข้าใจได้
  • ยังมีข้อมูลไม่มากนักเกี่ยวกับแนวปฏิบัติที่ดีที่สุดหรือกรณีศึกษา

เรียนรู้เพิ่มเติม:

  • ผมเขียนเล็ก ๆ น้อย ๆตัวอย่างข้อมูลเพื่อเปรียบเทียบการใช้ MutationObserers ถึงการจัดการความหลากหลายของกิจกรรม ฉันใช้รหัส balupton ตั้งแต่คำตอบของเขามี upvotes มากที่สุด
  • Mozillaมีเพจที่ยอดเยี่ยมบน API
  • ลองดูที่ห้องสมุดMutationS บทสรุป

มันไม่เหมือนกับinputเหตุการณ์HTML5 (ซึ่งรองรับcontenteditableในเบราว์เซอร์ WebKit และ Mozilla ทั้งหมดที่รองรับผู้สังเกตการณ์การกลายพันธุ์) เนื่องจากการกลายพันธุ์ของ DOM อาจเกิดขึ้นผ่านสคริปต์เช่นเดียวกับการป้อนข้อมูลของผู้ใช้ แต่เป็นโซลูชั่นที่ทำงานได้สำหรับเบราว์เซอร์เหล่านั้น ฉันคิดว่ามันอาจเป็นอันตรายต่อการแสดงมากกว่าinputเหตุการณ์ด้วย แต่ฉันก็ไม่มีหลักฐานที่ชัดเจนสำหรับเรื่องนี้
Tim Down

2
+1 แต่คุณทราบหรือไม่ว่าเหตุการณ์การกลายพันธุ์ไม่ได้รายงานผลกระทบของฟีดไลน์ในเนื้อหาที่น่าพึงพอใจ กด Enter ในตัวอย่างของคุณ
citykid

4
@citykid: นั่นเป็นเพราะข้อมูลโค้ดนั้นคอยดูการเปลี่ยนแปลงข้อมูลตัวละครเท่านั้น สามารถสังเกตการเปลี่ยนแปลงโครงสร้าง DOM ได้เช่นกัน ดูjsfiddle.net/4n2Gz/1ยกตัวอย่างเช่น
Tim Down

Internet Explorer 11 + สนับสนุนการกลายพันธุ์ผู้สังเกตการณ์
Sampson

ฉันสามารถตรวจสอบได้ (อย่างน้อยใน Chrome 43.0.2357.130) ว่าinputเหตุการณ์HTML5 เริ่มขึ้นในแต่ละสถานการณ์ (โดยเฉพาะการลาก & วาง, ตัวเอียง, คัดลอก / ตัด / วางผ่านเมนูบริบท) ฉันยังทดสอบการใช้ตัวควบคุม w / cmd / ctrl + b และได้ผลลัพธ์ที่คาดหวัง ฉันยังตรวจสอบและสามารถตรวจสอบได้ว่าinputเหตุการณ์ไม่ได้เกิดขึ้นจากการเปลี่ยนแปลงเชิงโปรแกรม (ซึ่งดูเหมือนชัดเจน แต่มีเนื้อหาที่ตรงกันข้ามกับภาษาที่สับสนในหน้า MDN ที่เกี่ยวข้องดูที่ด้านล่างของหน้านี่เพื่อดูว่าฉันหมายถึงอะไร: นักพัฒนา .mozilla.org / en-US / เอกสาร / เว็บ / กิจกรรม / ข้อมูลเข้า )
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')});

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

นี่เป็นวิธีแก้ปัญหาที่ฉันใช้และทำงานได้อย่างบ้าคลั่ง ฉันใช้ $ (นี่). text () แทนเพราะฉันแค่ใช้ div หนึ่งบรรทัดที่สามารถแก้ไขเนื้อหาได้ แต่คุณอาจใช้. html () ด้วยวิธีนี้คุณไม่ต้องกังวลเกี่ยวกับขอบเขตของตัวแปรโกลบอล / ไม่ใช่โกลบอลและก่อนหน้านั้นแนบกับ div editor จริงๆ

$('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 ไม่ว่าโดยตรงหรือโดยอ้อม
Bruno Ferreira

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
เอ่อทำไมการโหวตที่ไม่มีคำอธิบาย? การทำเช่นนี้เป็นการก่อความเสียหายแก่ทั้งผู้ที่เขียนคำตอบและทุกคนที่อ่านคำตอบในภายหลัง
Mike Willis

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มีความเหมาะสมมากกว่าในกรณีนี้ นอกจากนี้ผู้คนควรจำไว้ว่าเบราว์เซอร์ดั้งเดิมไม่รองรับกิจกรรมเหล่านี้
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เหตุการณ์ที่มีอยู่และย้อนกลับไปที่สิ่งนี้ แต่มีการโต้แย้งบางอย่าง
ทิมลง

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 จำเป็นต้องเพิ่มเหตุการณ์การเปลี่ยนแปลงไปยังข้อมูลจำเพาะ ปัญหาเดียวก็คือฟังก์ชั่นการโทรกลับจะประเมินว่า (ก่อน == $ (นี้) .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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.