记一次 JS 模拟 CSS active 效果的过程

所谓的界面触控效果,就是用户点下去的时候,界面立刻产生变化以反馈给用户知道:“ok,你点击的吩咐,我已晓得罗~”,也有人说“点击高亮的效果”。——毋庸置疑,这是人机交互中非常重要沟通手段,使用得也非常常见的。当时我要实现这效果的时候,自己还满认为,不就是网页上为链接加个 a:hover 效果嘛~链接不行的加个“图片翻转”不就可以了。——孰料,在移动终端上,花费的时间远超想像。首先,我倾向于采用纯样式的方法,大体思路是:普通状态写一种样式;用户点击后归为另外一种状态,写出其状态对应样式。这样,我立刻想到的时“元素:hover”。经测试 PC 上无问题,但手机上尤其列表控件显得过于灵敏,有时与滚动事件冲突,有时出现奇怪的 bug,于是想想,不应使用 hover,应是采用“元素:active”才对(active 意即“击中”那一下,hover 仅仅“移上”意思)。改用 active 后,上述问题立即迎刃而解。简单说,active 效果与 hover 效果相比,就是 active 点击后会撤销手指松开的样式。

/* 普通效果 */
.x_list li {
    padding: 3px;
    width: 100%;
    list-style: none;
    background-color: #999;/*普通效果的背景色*/
    border-bottom: 1px solid #333;
    border-top: 1px solid white;
    clear: both;
    display: inline-table;
    position: relative;
}
/* 点击效果 */
.x_list li:active{
    background-color:red;
}

看来,hover 与 active 意思虽相近,但细作起来还是明显差别。以往讨论桌面的时候,大多用 hover 即可,而现如今在手机上却把两者的区别给凸显出来了。我想,这是什么缘故呢,——为什么以往桌面的做法行得通,而手机上却把问题暴露出来呢。我觉得,执行“事件的媒介”也是重要的原因,因为桌面上输入事件的乃鼠标器居多,而且手机上则围绕手指触控的居多。两者的分野,导致所处于的经验与法则也不尽相同。可以说,承接下来的那个 bug,都有同样的缘故在作祟。

可是,即使使用 active,又遇到浏览器支持的问题:——Android 2.x 仍不支持元素:active,新版的 Android 4.x 和 iOS 则顺利通过。于是我安排 iOS 与 Android 4 就用 CSS 的写法,而 Android 另一边厢安排 js 方法实现之。

既然用 js 方法实现之,我第一时间想到的用 setTimeout() 定时器实现之。点击后添加一样式,timeout 之后撤销样式。

// 用户按下之后,为元素加入高亮样式,然后立刻消褪,——这就是所谓的 active 样式。
// 请注意,这个方法属于“反面教材”
function onActive(el, fn){
	el.addCls('active');
	setTimeout(function(){
		fn && fn();
		el.removeCls('active');
	}, 300);
}

这个貌似可行的办法实则不正确!因为经过领导演示,人家 Sencha Touch 的选中高亮是可以跟随滚动的,我的在首页上的点击高亮是可以但不会响应点击滚动,有反常理!甚幸,恰恰有正确的案例在前,让我可以参照,可以纠正。后来结果通过 touchstart/touchend 事件实现这个界面触控效果。代码如下:

HTMLElement.prototype.onActive = (function () {
	/**
	 * this 应为 CSS Selector
	 * @param {Event} e
	 */
	function highlight(e){
		var targetEl = e.target;
		if(targetEl && targetEl.tagName == this.toUpperCase()){
			targetEl.toggleCls('active');
		}else if(targetEl){
			arguments.callee.call(this, {
				target : targetEl.parentNode
			});
		}
	}

    // 是否支持触摸事件
    var isSupportTouch = 'ontouchstart' in window || "ontouchend" in window.document;
	// 支持触摸式使用相应的事件替代
	var hightEvent_in  = isSupportTouch ? 'touchstart': 'mouseover',
		hightEvent_out = isSupportTouch ? 'touchend' : 'mouseout';

    /**
      * @param {String} highlightCfg
      */
    return function(el_cssSelector){
    	// ONLY FOR Moblie?
    	if (!window.navigator.isAndroid_2) {
    		return false;
    	}
    	var eventHandler = highlight.bind(el_cssSelector || 'LI'); // 默认 li 修改样式
    	// $$.addListener 为跨浏览器的事件添加器
		$$.addListener(this,  hightEvent_in, eventHandler);
		$$.addListener(this, hightEvent_out, eventHandler);
//		$$.addListener(this, 'touchcancel',eventHandler, isUseCapture); /*@todo考察该事件,比较特殊*/
		 return this; // 方便链式调用
	}
})();
上述代码扩展的浏览器原生对象 HTMLElment 原型,因此在任一元素身上都有 onActive 方法,另外 toggleCls(className) 也是通过扩展元素原型来提供的。相应地,样式不经过伪类来实现,而是定义一个 .active 的样式类:

/* 点击效果 */
.x_list li.active{ /* 注意这里冒号被改为点号*/
    background-color:red;
}

函数 highlight 中,因为登记事件时不能触发 userCapture,所以没有使用“事件冒泡”的方式而是递归上报的方式。

2013-4-7 重新整理一下这个函数,请注意这是没有版本检测,你应该自己加一个,因为只针对 android 2.x。

/**
 * 模拟 el:active
 * @param {Element} ul 参数为元素
 */
function onActive(ul){
	// ONLY FOR Android 2.x? Yes!
	// 检测是否 android 2.x 的属性,你可以依赖你自己的方案,我的就不贴了,比较简单的。
	// if (!window.navigator.isAndroid_2) {
		// return ul; //尽管该函数不工作,也要返回 this,以方便链式调用
	// }
	
	// add Highlight
	ul.addEventListener('touchstart', function (e){
		var targetEl = e.target;
		while(targetEl && targetEl.tagName != 'LI'){// 默认为 li 修改样式
			targetEl = targetEl.parentNode; 		// 找到要操作的目标元素为止
		}
		// do sth……
		console.log(targetEl.tagName);
		targetEl.addCls('active');
	});

	// remove Highlight
	ul.addEventListener('touchend',   function (e){
		var targetEl = e.target;
		while(targetEl && targetEl.tagName != "LI"){
			targetEl = targetEl.parentNode; // 找到要操作的目标元素为止
		}
		// do sth……
		console.log(targetEl.tagName);
		targetEl.removeCls('active');
	}); 
	
	return ul; // 方便链式调用
}

CSS 样式如下:

/* 点击效果 */
ul.x_list li:active, ul.x_list li.active {
    background-image: -webkit-gradient(linear, center top, center bottom, from(lightGrey), to(white));
}

2013-04-18 把问题重新整理了一下,给出如下“药方”:

<style type="text/css">
p:active, p.active{
	background-color:red;
}
</style>
<body>
	<p>Welcome to Jdrop
Jdrop provides a place to store JSON data in the cloud. The initial application is for storing performance data gathered from mobile devices. It's hard to analyze large amounts of information (HTTP waterfall charts, HTTP headers, document source, etc.) on a mobile device. Jdrop lets you gather this data on the mobile device but analyze it remotely on a larger screen. </p>
</body>
<script type="text/javascript">
	document.addEventListener('touchstart', function (e) {
		e.target.className = 'active';
	}, false);
	document.addEventListener('touchend', function (e) {
		e.target.className = '';
	}, false);
</script>
使用了事件委托比较合理。

©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页