这个问题是在我们的后台的CMS系统中发现的,由于编辑人员要大量使用Js对于Textarea和Input Text中的内容作处理,当有Js改变了Input Text的value后,textarea中undo功能就失效了,这个问题是致命的。
经过我们检查,此问题只发现在IE6,而FF中是没有这个问题的,问题的原因,我们猜测是因为IE6的Undo是绑定在Page对象上的,当Js改变DOM结构或者改变了El的value,该对象不知道为什么也被重置了,而FF是将Undo功能绑定在具体每个元素上的,所以要Fix这个问题,只能采用Js模拟FF将Undo的ClipBoard绑定在每个元素上了。
方法如下,我们覆盖了IE默认的Ctrl-Z和Ctrl-Y功能,用一个长度固定的Queue存放文本区每次改变的值,因为输入文本存在连续输入的可能,所以我们作了一个延时器,当1秒后再无任何动作后,才将value值保存,同时IE在每次Undo的时候都会将光标位置保留,所以我们又用了一个同样的Queue纪录光标的位置,具体代码如下
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>test</title>
</head>
<body>
<input id="txt1" type="text" value="" size="44">
<input type="button" value="click me 1" onclick="clickMe1()">
<script>
function clickMe1(){
document.getElementById('txt1').value='4545';
}
</script>
<br/>
<textarea id="txt2" rows="24" cols="57"></textarea>
<input type="button" value="click me 2" onclick="clickMe2()">
<script>
function clickMe2(){
document.getElementById('txt2').value='4545';
}
</script>
<br/>
<input type="button" value="Apply Js Undo Function Fix IE6 Bug" onclick="fixUndo()">
<script>
function fixUndo(){
fixIE6Undo(document.getElementById('txt1'),document.getElementById('txt2'));
}
function fixIE6Undo(){
// Valid when UA is IE and version is less than 7
if (!(/MSIE (\d+\.\d)/.exec(navigator.userAgent)[1] <= 6.0)) {
return false;
}
var debugMode = false;
var debugPrint = function(s){
var d = document.createElement('div');
d.innerHTML = s;
document.body.appendChild(d);
};
// Set global Timer
var _undoTimer = null;
// Set Undo max step
var _maxUndoStep = 20;
var setBookmark = function(e, bookmarks){
e.scrollTop = bookmarks[2];
var end = bookmarks[1];
var start = bookmarks[0];
// Fix IE offset bug with char return&newline
var ose = e.value.substring(0, end).split(/\n/g).length - 1;
var ost = e.value.substring(0, start).split(/\n/g).length - 1;
// Resume Range
var range = e.createTextRange();
range.collapse(true);
range.moveEnd('character', end - ose);
range.moveStart('character', start - ost);
range.select();
e.focus();
};
var applyUndoStep = function(e){
e.value = e._undoQueue[e._undoQueueIndex];
var bookmarks = e._bookmarkQueue[e._undoQueueIndex];
if ('TEXTAREA' == e.tagName) {
setBookmark(e, bookmarks);
}
if (debugMode) {
debugPrint(e._undoQueueIndex + ' - ' + e._undoQueue[e._undoQueueIndex] + '<hr />');
}
};
for (var i = 0; i < arguments.length; i++) {
// Deal with each argument
(function(el){
if (('INPUT' == el.tagName && 'text' == el.type) || 'TEXTAREA' == el.tagName) {
el._undoQueue = new Array();
el._bookmarkQueue = new Array();
el._undoQueueIndex = 0;
el._undoQueue.push(el.value || '');
el._bookmarkQueue.push([0, 0, el.scrollTop]);
el.attachEvent('onpropertychange', function(){
if (window.event && window.event.propertyName == 'value') {
clearTimeout(_undoTimer);
_undoTimer = setTimeout(function(){
var uQue = el._undoQueue;
var bQue = el._bookmarkQueue;
if (el.value != uQue[el._undoQueueIndex]) {
// Value changed
el._undoQueueIndex++;
var len = uQue.length - el._undoQueueIndex;
if (len > 0) {
// Slice undo queue
uQue.splice(el._undoQueueIndex, len);
bQue.splice(el._undoQueueIndex, len);
}
if (uQue.length >= _maxUndoStep) {
// Reached max step
uQue.shift();
bQue.shift();
el._undoQueueIndex--;
}
var bookmarks = (function(e){
e.focus();
var r = document.selection.createRange();
if (!r) {
return [0, 0, e.scrollTop];
}
var re = e.createTextRange();
var rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return [rc.text.length, rc.text.length + r.text.length, e.scrollTop];
})(el);
// Add new value to undo queue
uQue.push(el.value);
bQue.push(bookmarks);
}
}, 300);
}
});
el.attachEvent('onkeydown', function(){
if (!!event.ctrlKey) {
if (event.keyCode == 90) {
// Undo with ctrl + z
if (el._undoQueueIndex > 0) {
el._undoQueueIndex--;
applyUndoStep(el);
}
return false;
}
else {
if (event.keyCode == 89) {
// Redo with ctrl + y
if (el._undoQueueIndex + 1 < el._undoQueue.length) {
el._undoQueueIndex++;
applyUndoStep(el);
}
return false;
}
}
}
});
}
})(arguments[i]);
}
}
</script>
</body>
</html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>test</title>
</head>
<body>
<input id="txt1" type="text" value="" size="44">
<input type="button" value="click me 1" onclick="clickMe1()">
<script>
function clickMe1(){
document.getElementById('txt1').value='4545';
}
</script>
<br/>
<textarea id="txt2" rows="24" cols="57"></textarea>
<input type="button" value="click me 2" onclick="clickMe2()">
<script>
function clickMe2(){
document.getElementById('txt2').value='4545';
}
</script>
<br/>
<input type="button" value="Apply Js Undo Function Fix IE6 Bug" onclick="fixUndo()">
<script>
function fixUndo(){
fixIE6Undo(document.getElementById('txt1'),document.getElementById('txt2'));
}
function fixIE6Undo(){
// Valid when UA is IE and version is less than 7
if (!(/MSIE (\d+\.\d)/.exec(navigator.userAgent)[1] <= 6.0)) {
return false;
}
var debugMode = false;
var debugPrint = function(s){
var d = document.createElement('div');
d.innerHTML = s;
document.body.appendChild(d);
};
// Set global Timer
var _undoTimer = null;
// Set Undo max step
var _maxUndoStep = 20;
var setBookmark = function(e, bookmarks){
e.scrollTop = bookmarks[2];
var end = bookmarks[1];
var start = bookmarks[0];
// Fix IE offset bug with char return&newline
var ose = e.value.substring(0, end).split(/\n/g).length - 1;
var ost = e.value.substring(0, start).split(/\n/g).length - 1;
// Resume Range
var range = e.createTextRange();
range.collapse(true);
range.moveEnd('character', end - ose);
range.moveStart('character', start - ost);
range.select();
e.focus();
};
var applyUndoStep = function(e){
e.value = e._undoQueue[e._undoQueueIndex];
var bookmarks = e._bookmarkQueue[e._undoQueueIndex];
if ('TEXTAREA' == e.tagName) {
setBookmark(e, bookmarks);
}
if (debugMode) {
debugPrint(e._undoQueueIndex + ' - ' + e._undoQueue[e._undoQueueIndex] + '<hr />');
}
};
for (var i = 0; i < arguments.length; i++) {
// Deal with each argument
(function(el){
if (('INPUT' == el.tagName && 'text' == el.type) || 'TEXTAREA' == el.tagName) {
el._undoQueue = new Array();
el._bookmarkQueue = new Array();
el._undoQueueIndex = 0;
el._undoQueue.push(el.value || '');
el._bookmarkQueue.push([0, 0, el.scrollTop]);
el.attachEvent('onpropertychange', function(){
if (window.event && window.event.propertyName == 'value') {
clearTimeout(_undoTimer);
_undoTimer = setTimeout(function(){
var uQue = el._undoQueue;
var bQue = el._bookmarkQueue;
if (el.value != uQue[el._undoQueueIndex]) {
// Value changed
el._undoQueueIndex++;
var len = uQue.length - el._undoQueueIndex;
if (len > 0) {
// Slice undo queue
uQue.splice(el._undoQueueIndex, len);
bQue.splice(el._undoQueueIndex, len);
}
if (uQue.length >= _maxUndoStep) {
// Reached max step
uQue.shift();
bQue.shift();
el._undoQueueIndex--;
}
var bookmarks = (function(e){
e.focus();
var r = document.selection.createRange();
if (!r) {
return [0, 0, e.scrollTop];
}
var re = e.createTextRange();
var rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return [rc.text.length, rc.text.length + r.text.length, e.scrollTop];
})(el);
// Add new value to undo queue
uQue.push(el.value);
bQue.push(bookmarks);
}
}, 300);
}
});
el.attachEvent('onkeydown', function(){
if (!!event.ctrlKey) {
if (event.keyCode == 90) {
// Undo with ctrl + z
if (el._undoQueueIndex > 0) {
el._undoQueueIndex--;
applyUndoStep(el);
}
return false;
}
else {
if (event.keyCode == 89) {
// Redo with ctrl + y
if (el._undoQueueIndex + 1 < el._undoQueue.length) {
el._undoQueueIndex++;
applyUndoStep(el);
}
return false;
}
}
}
});
}
})(arguments[i]);
}
}
</script>
</body>
</html>
测试JS内存性能结果:
1.打开测试网页,内存占用为11,740
2.让页面任意元素获得焦点,内存占用开始小幅波动,峰值约为12,300
3.点击Apply..Js按钮,应用FixIE6Undo功能,内存降为12,624
4.在文本区反复输入长度为6521字符的文本,次数10次,重复执行Ctrl-C,Ctrl-V操作,内存占用为20,388
5.在文本区反复使用Ctrl-Y,Ctrl-Z功能,内存占用逐步增长,到42,700左右,开始微幅下滑,始终在42,650左右浮动,内存并未出现溢出现象,且经过反复测试Ctrl-Z,Ctrl-Y功能正常