:hover
上的子菜单。最初的站点未使用任何响应式设计:它仅针对桌面环境。我现在正在使用响应式设计技术来定位移动设备和平板电脑,其中许多基于触摸而不是基于鼠标。 我面临的最大问题(许多其他人似乎也曾遇到过)是基于悬停的导航菜单:它在鼠标环境中工作良好,但是在触摸设备上却没有触发悬停的可靠方法,使页面难以使用。
目标是这样的:
当鼠标悬停菜单时,显示子菜单。
单击菜单时用鼠标打开锚标记的链接。
第一次触摸菜单时,显示子菜单。
第二次触摸菜单时,打开锚标签的链接。
无缝连接具有鼠标设备的平板电脑的开关功能。
我的团队不愿意牺牲基于鼠标的计算机的悬停效果。他们喜欢它,不想使菜单基于所有设备单击。我同意这一点。
在网上浏览后,我找不到任何解决方案。我将其中的一些放在一起,并开发了在Android上经过良好测试的产品,完全获得了我想要的功能。有什么可以改进的地方,和/或您认为这种方法有什么问题吗?
jQuery(document).ready(function() {
var touched=false;
jQuery(".nav").on('touchstart', 'li .has_children', function (e) { touched=true; });
jQuery("html").on('mousemove', function (e) { touched=false; });
jQuery("html").on('click', updatePreviousTouched );
jQuery(".nav").on('click', 'li .has_children', function (e) {
updatePreviousTouched(e);
if( touched ) {
if (jQuery(this).data('clicked_once')) {
jQuery(this).data('clicked_once', false);
return true;
} else {
e.preventDefault();
jQuery(this).trigger("mouseenter");
jQuery(this).data('clicked_once', true);
}
}
touched=false;
});
var previous_touched;
function updatePreviousTouched(e) {
if( typeof previous_touched != 'undefined' && previous_touched != null && !previous_touched.is( jQuery(e.target) ) ) {
previous_touched.data('clicked_once', false);
}
previous_touched=jQuery(e.target);
}
}
如果可以改进此解决方案,请提供任何建议。如果您发现它不适合您,请也告诉我。我将其发布在此处,以尝试获取任何改进建议并发布该想法,以便其他人可以从中受益。
这是到目前为止我的测试结果:
在主要5种浏览器上的Windows 7:有效。
默认浏览器上的Android平板电脑:可以运行。
在IE和Chrome上带有触摸屏的Windows 7笔记本电脑:工作量排序。该机器正在运行两个浏览器的桌面版本,因此它不会触发“ touchstart”事件。在触摸屏和浏览器之间的某个位置,某些东西将触摸转换为鼠标事件。但是,由于鼠标是该设备不可移动的一部分,因此我不认为这是失败的。我希望它能像平板电脑一样工作,但是似乎还没有可用的技术。
iOS iPad:在编写上面的javascript之前,它可以按要求工作。看来ios在幕后为我们做了一些这种悬停/点击的魔术。我有一个团队成员正在检查我的JavaScript是否破坏了iOS功能,一旦得到答案,我将编辑这篇文章。
其他说明:
这只是台式机和平板电脑站点的菜单。移动网站有一个不同的基于单击的菜单。我不建议在手机大小的设备上使用此解决方案。
#1 楼
首先,让我们清理代码以真正利用.on()
的功能:$(function() {
var touched = false,
previous_touched;
function updatePreviousTouched(e){
if(typeof previous_touched !== 'undefined' && previous_touched !== null && !previous_touched.is($(e.target))){
previous_touched.data('clicked_once', false);
}
previous_touched = $(e.target);
}
$(".nav").on({
touchstart:function(e) {
touched=true;
},
click:function(e);
var $this = $(this);
updatePreviousTouched(e);
if(touched) {
if ($this.data('clicked_once')) {
$this.data('clicked_once', false);
return true;
} else {
e.preventDefault();
$this.trigger("mouseenter").data('clicked_once', true);
}
}
touched = false;
}
},'li .has_children');
$("html").on({
mousemove:function(e){
touched=false;
},
click:updatePreviousTouched
});
});
注意以下内容:
.on()
的对象使用,而不是单纯的字符串使用(仅使用一个绑定而不是多个绑定)在适当的地方缓存
在顶部声明的变量和函数
现在我们有了原始的代码都非常漂亮和高效,我将提供另一种替代方法,该替代方法应轻巧得多。
根据您当前使用的CSS来制作
:hover
。相反,您可以考虑将CSS设为类,然后在代码中动态添加/删除该类。这保持了CSS的快速利用,同时避免了CSS和JS事件相互争夺的情况。我在许多项目上都做了类似的事情,而且看起来效果很好。关键是自定义事件;通过将其分开,您可以控制它何时被触发,而不必依靠一些疯狂的if
逻辑来确定是否应将其触发。这是一个有效的示例:
这就是CSS ...
.header-nav-menu ul li:hover > ul { display: inline-block; }
成为......这是您正在使用的jQuery ...
.header-nav-menu ul li > ul.MenuActive { display: inline-block; }
这应该以更简洁的方式管理您想要的内容: br />
menuActive
变量默认为false
,并且仅在打开子菜单(通过true
或touchstart
)时才设置为mouseenter
防止实际点击动作,并进行验证以确定菜单是否适当地激活
如果菜单处于活动状态,则触发自定义事件以转到链接的目标。
如果用户触摸屏幕上未触摸的某处,触摸事件不会通过使用
html
达到e.stopPropagation();
子菜单,它将关闭子菜单并将
menuActive
设置为false 我将几个动作分离为可重用的函数,并分离了事件以避免
if
检查。该代码的执行速度比原始代码快得多,更重要的是,它是防弹的。触摸和悬停事件是真正分开的,菜单的显示/隐藏仍然利用CSS,并且可以解决浏览器不一致的问题(例如:使用回调的原因是因为Safari 5.1在添加类之间存在300ms的延迟并且它会显示在屏幕上。)我知道这段代码比原始代码要长,但可以保证它将运行得更快并且更易于维护。希望对您有所帮助。
评论
\ $ \ begingroup \ $
这是一个很好的答案。谢谢。这里接受答案的礼节是什么?我将对其他代码审查感兴趣,因为从来没有任何“正确”的答案。
\ $ \ endgroup \ $
–约书亚
2013年12月30日0:38在
\ $ \ begingroup \ $
根据其他CSS相关评论的经验,较新的答案通常涵盖较旧的答案所遗漏的内容。通常,所有答案都需要增加一些有价值的东西,因此这只是选择增加值最大的人的一种情况。
\ $ \ endgroup \ $
–cimmanon
13年12月30日在1:25