当前位置: 代码迷 >> Web前端 >> IE sucks, mousedown事件与focus举动分析
  详细解决方案

IE sucks, mousedown事件与focus举动分析

热度:419   发布时间:2012-11-05 09:35:12.0
IE sucks, mousedown事件与focus行为分析
首先这个问题hax也有相关分析,受益良多。

问题描述(如果你只想看结论,可以跳过此部分)
近些时候发现一个bug:在IE下,当ExtJs中的菜单处于显示状态时,点击TextField或TextArea,发现它的emptyText未移除。(emptyText应该在只有为空,并未激活时显示)
然后对此bug进行了调试追踪,发现TextField并未收到focus事件。
继续制作简单模拟场景以定位排除,最终发现:
当在mousedown事件中改变页面焦点目标时,就会发生问题:界面光标已经在text input中了,但并未发出focus事件;当前的document.activeElement不为text input,而是JS中修改的值。
Ext中Menu会自动将焦点设置为自己(实际上是它下面的一个a元素),然后点击页面其它位置时,它会隐藏。
隐藏的元素是无法获得焦点的,从而导致焦点变动。

这是简单的bug重现案例:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>测试 - oldFocusTarget判断</title>
</head>

<body>
<input type="text" id="test" value="init value" /> <a id="sucks" href="#">sucks</a>
<script type="text/javascript">
var text = document.getElementById("test"),
	a = document.getElementById("sucks");
// 监听text focus状态
var handleFocus = function(){
	document.title = "focus! "+new Date();
	text.value = "";
};
var handleBlur = function(){
	document.title = "blur! "+new Date();
	text.value = "empty text";
};
text.attachEvent("onfocus", handleFocus);
text.attachEvent("onblur", handleBlur);

// text点击时更改focus对象
text.attachEvent("onmousedown", function(){
	a.focus();
});
// 最终,text有输入框,但焦点在a上,并且text未触发focus
</script>
</body>
</html>


简单理解
开始一直不能理解,看完hax的分析后才恍然大悟,其实就是焦点状态不同步。

当点击鼠标时(mousedown),IE的UI控件(即hax所说的windows基础控件所模拟的)会获得焦点,但在Dom层(即hax所说的html层)如果有在mousedown事件中修改焦点目标,会使得它与UI控件层失去同步。
这时,UI控件层的焦点是点击的控件,而Dom层则是JS操作聚焦后的元素。
就出现了文本框内有输入符,但Dom层焦点又不在它身上的bug。
如果不在mousedown中处理焦点,是没有任何问题的,为什么修改后就会失去同步呢?

探索深层原因
这里尝试分析IE对UI控件层与Dom层焦点同步的逻辑,目前有了一个猜测:
1. 当鼠标点击时,先记录Dom层焦点(document.activeElement)为oldFocusTarget
2. UI控件层内部处理点击行为 (注:2、3不区分先后次序)
3. Dom层发出mousedown事件,并执行相关事件句柄
4. 如果现在的Dom层焦点与oldFocusTarget一致,则修改Dom层焦点为当前UI控件。

这是一个比较给力的证明(IE6下测试通过):
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>测试 - oldFocusTarget判断</title>
</head>

<body>
<input type="text" id="test" value="init value" /> <a id="sucks" href="#">sucks</a> <a id="f_uck" href="#">f_uck</a>
<script type="text/javascript">
var text = document.getElementById("test"),
	a = document.getElementById("sucks"),
	b = document.getElementById("f_uck");
// 初始focus对象设置为a
a.focus();
// text点击时更改focus对象,但最终改回来
text.attachEvent("onmousedown", function(){
	b.focus();
	a.focus();
});
// text最终还是focus


// 监听事件触发次序,以对照。因为IE中事件句柄后注册先触发,所以这里在最后注册
var sequence = [];
function record(eventName, domName){
	return function(){
		sequence.push(domName + ' ' + eventName);
	}
}
var toTest = ["mousedown", "mouseup", "focus", "blur"], i, n;
for(i=0; i<toTest.length; i++){
	n = toTest[i];
	text.attachEvent("on"+n, record(n, "text"));
	a.attachEvent("on"+n, record(n, "a"));
	b.attachEvent("on"+n, record(n, "b"));
}
// 5秒后弹出记录下来的次序
setTimeout(function(){
	alert(sequence.join("\n"));
}, 5000);

</script>
</body>
</html>

在这个案例中,初始focus对象为a(注意IE中按F5刷新会保留上次focus目标,所以请F6+回车进行刷新)
然后,点击text input后依次触发:
(点击) text mousedown
(b.focus) a blur, b focus
(a.focus) b blur, a focus
(mousedown完成,对比焦点通过) a blur, text focus

可以看到最后还是触发了text focus,而如果将以上代码的初始focus设置(a.focus())注释掉,就会发现text focus不会发出了。

结束
IE sucks...
  相关解决方案