我设计的网站使用导航菜单,该菜单显示: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,并且仅在打开子菜单(通过truetouchstart)时才设置为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