I currently have bound my textarea to a couple of events which seems to work. However, the problem is that the events overlap and fire several times, which in turn reduces performance by a factor of too much.
What I want to do is pretty much catch any change to the textarea: clicking, paste, keyup, keydown, right click context menu editing (right click, cut/delete/paste), drag and drop, etc. This has to work cross-browser and at least down to IE8. The events have to fire when you move the caret around in the textarea using arrowkeys or similar (I handle changes based on caret position, among other things).
I can't use any major delays. As soon as you do something with the textarea, the events have to fire and execute whatever code I have there immediately.
I am currently using jQuery to bind the event, but I am fine with a pure javascript solution as long as it works cross browser and does what I want.
Here's the code I currently use:
var deadKeycodes = [16, 17, 18, 19, 20,
27, 33, 34, 35, 36,
38, 40, 44, //37 = left arrow and 39 = right arrow removed, it needs to trigger on those
45, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 144, 145];
$(original).bind('propertychange keyup keydown input click', function(e) {
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) { // prevent execution when pressing a 'dead' key
//do stuff here
}
});
If anything is unclear just ask and I'll clarify it for you :)
I currently have bound my textarea to a couple of events which seems to work. However, the problem is that the events overlap and fire several times, which in turn reduces performance by a factor of too much.
What I want to do is pretty much catch any change to the textarea: clicking, paste, keyup, keydown, right click context menu editing (right click, cut/delete/paste), drag and drop, etc. This has to work cross-browser and at least down to IE8. The events have to fire when you move the caret around in the textarea using arrowkeys or similar (I handle changes based on caret position, among other things).
I can't use any major delays. As soon as you do something with the textarea, the events have to fire and execute whatever code I have there immediately.
I am currently using jQuery to bind the event, but I am fine with a pure javascript solution as long as it works cross browser and does what I want.
Here's the code I currently use:
var deadKeycodes = [16, 17, 18, 19, 20,
27, 33, 34, 35, 36,
38, 40, 44, //37 = left arrow and 39 = right arrow removed, it needs to trigger on those
45, 112, 113, 114, 115,
116, 117, 118, 119, 120,
121, 122, 123, 144, 145];
$(original).bind('propertychange keyup keydown input click', function(e) {
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) { // prevent execution when pressing a 'dead' key
//do stuff here
}
});
If anything is unclear just ask and I'll clarify it for you :)
Share Improve this question edited Aug 13, 2021 at 8:25 Brian Tompsett - 汤莱恩 5,89372 gold badges61 silver badges133 bronze badges asked Jan 3, 2013 at 21:51 Firas DibFiras Dib 2,6211 gold badge20 silver badges40 bronze badges 4- Already answered stackoverflow./questions/2823733/textarea-onchange-detection – bchhun Commented Jan 3, 2013 at 21:57
- keydown and click are all you really need, maybe the paste and cut events too if click doesn't cover that. – Kevin B Commented Jan 3, 2013 at 22:01
- 1 Keyup and keydown are fired at different times. Hold a key down in a textarea and see (and look when the markdown preview updates). In general keyup and keydown are not great for determining when input has changed (though you can use both for something acceptable). – Alex Churchill Commented Jan 3, 2013 at 22:03
- keydown paired with a setTimeout set to 0 works very well for detecting changes instantly on keydown rather than waiting for the keyup. – Kevin B Commented Jan 3, 2013 at 22:07
4 Answers
Reset to default 1I don't think you can really "solve" the problem in the sense of stopping multiple events from firing. Keyup and keydown really are different events and happen at different times. If you want to respond to both (which you probably do, since keying down and keying up will both potentially change the textarea), both events need to be included. However, most of the time they will fire almost simultaneously (and many times in a row), which as you point out can pose a performance problem.
Instead, you should probably consider firing a throttled or debounced callback. A throttled callback only will fire once every n milliseconds (good for functions that might get called too much). A debounced callback will only fire after a stream of events is done; after n milliseconds have elapsed since the last callback.
You can easily acplish this using underscore's debounce and throttle functions.
Something like:
debouncedFn = _.debounce(function(e) {
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) { // prevent execution when pressing a 'dead' key
//do stuff here
}
}, 100);
$(original).bind('propertychange keyup keydown input click', debouncedFn);
Your original is overkill. All you need are the input
and propertychange
events.
2016 Update
The originally linked page has now disappeared. Here's a slightly suboptimal snapshot of it:
http://web.archive/web/20140810185102/http://whattheheadsaid./2010/09/effectively-detecting-user-input-in-javascript
Here's an answer demonstrating using the input
event and falling back to propertychange
in IE <= 8:
Catch only keypresses that change input?
To prevent the event overlap, you could store the time of the last call. Before you execute an event's callback, you test if enough time has passed to make sense to fire again.
$(original).bind('propertychange keyup keydown input click', (function () {
var lastCallTime = 0;
return function (e) {
if (Date.now() - lastCallTime < 50) { return; } // Too little time has passed--exit
if (!Array.prototype.indexOf || deadKeycodes.indexOf(e.keyCode) == -1) {
lastCallTime = Date.now();
// your code...
}
};
}()));
This seems to solve it in IE7-9 and Chrome, haven't tested the rest. Only one console.log happens per change regardless of what the change was. If there were no changes, nothing is logged: http://jsfiddle/SJN6J/2/
var timer;
$("textarea").on("keydown paste cut", function(){
clearTimeout(timer);
var origvalue = this.value, self = this;
timer = setTimeout(function(){
if ( origvalue !== self.value ) {
console.log("it changed!");
// do stuff because content changed
}
},0);
});
Note: my IE7-8 testing was with IE9 changing browser mode, so you may want to do real IE7-8 testing.