I have completed writing a responsive menu (it uses the HoverIntent plugin) for my WordPress theme and it looks like it is working fine on desktop/tablets/phones.
You can test the code here. Just make sure that you are testing within Chrome/Opera Developer Toolbar (ctrlshiftI) and then toggle device toolbar (ctrlshiftM) in order to see how it works on tablets/phones.
Is my code bad/inefficient?
jQuery( document ).ready( function ( $ ) {
'use strict';
(function () {
var oldTouchPos,
menuTogglePos,
getFirstLink = $( '.nav a:first' ),
getFirstLinkAttr = getFirstLink.attr( 'href' ),
cachedWindowWidth = $( window ).width();
//This function is responsible for toggling menu Visbility ON/OFF
function toggleMenuVisibility() {
var el = $( '.nav' );
if ( ! isDesktop() ) {
if ( el.is( ':visible' ) ) {
el.css( { 'display': 'none' } );
}
} else {
if ( el.is( ':visible' ) ) {
return false;
} else {
el.css( { 'display': 'block' } );
}
}
}
//This function is responsible for complementing slideUp animation
function decorationUp( $this, minus ) {
$this.prev( 'a' ).css( { 'text-decoration': 'none' } ).children( minus ).not( '.menu-item-description' ).hide( 200 );
}
//Simple function checks if we are on desktop machine (based on CSS "float" property)
function isDesktop() {
return $( '.nav li' ).css( 'float' ) == 'left';
}
//This function is responsible for making first level menu item's same height if description is present (on Desktop machines)
function equalizeHeight_If_Description() {
if ( ! isDesktop() ) {
$( '.nav ul li a' ).removeAttr( 'style' );
} else {
var menuItemDescription = $( 'ul li a .menu-item-description:not("ul ul li a .menu-item-description")' );
if ( menuItemDescription.length ) {
var makeEqualHeight = menuItemDescription.parent().height();
$( '.nav ul li a:not("ul ul li a")' ).not( menuItemDescription.parent( 'a' ) ).css( {
'height': makeEqualHeight + 'px',
'line-height': makeEqualHeight + 'px'
} );
menuItemDescription.parent( 'a' ).css( { 'height': makeEqualHeight + 'px' } );
}
}
}
//Function is responsible for complementing slideDown animation (showing/hiding minus sign, adding/removing href's and adding/removing text-decorations)
function manageHref( thisPrev, getHref, minus ) {
if ( undefined === thisPrev.attr( 'href' ) ) {//If attr is NOT here treat it as a link (allow X Button to animate ,do NOT underline)
thisPrev.children( minus ).show( 290, function() {
thisPrev.attr( 'href', getHref );
if ( '#' == getHref || undefined === getHref ) {
thisPrev.css( { 'text-decoration': 'none' } );
} else {
thisPrev.css( { 'text-decoration': 'underline' } );
}
} );
}
}
//This function is responsible for animating width and height of our menu (On anchor hover... on Desktop machines)
var animateMenuWidth = function() {
var activeClass = $( '.active' );
if ( ! isDesktop() ) {
return false;
}
if ( activeClass.next( 'ul.sub-menu' ).length ) {//Check if next submenu exist. If it does exist do:
var nextSubmenu = activeClass.next( 'ul.sub-menu' );
nextSubmenu
.css( {//Add the following Css properties so we can Read it's values in order to make an animation
'visibility': 'hidden',
'display': 'block'
} );
var getWidth = nextSubmenu.outerWidth( true ),//Get the width of menu
getHeightFirstChild = nextSubmenu.children( 'li:first' ).outerHeight( true ),//Get the height of first child of our menu
getSubmenuHeight = nextSubmenu.outerHeight( true );//Get the height of of menu
nextSubmenu
.removeClass( 'sub-menu' )
.addClass( 'jsub-menu' ).css( {
'visibility': '',
'height': getHeightFirstChild + 'px',
'width': '0',
'z-index': '99999'
} );
nextSubmenu.filter( ':not(:animated)' ).animate( { width: getWidth }, 500, function() {
//animate width of menu,callback animate height
if ( nextSubmenu.children( 'li' ).length > 1 ) {
nextSubmenu.animate( { height: getSubmenuHeight }, 500 );
}
} );
}
};
//Dummy function for hoverIntent plugin
function nullFunc() {
return;
}
//This function is responsible for animating slideDown of our menu on Desktop Layout
var animateMenuDropdown = function() {
var $this = $( this );
if ( ! isDesktop() ) {
return false;
}
if ( $this.children( 'ul:first' ).hasClass( 'jsub-menu' ) ) {//Let's check if "jsub-menu" Class is here
return false;//If it is ("jsub-menu" here) don't SlideDown...
} else {//Else slide down if no class
$this.find( 'ul.sub-menu:first' ).not( ':visible' ).slideDown( 500 );
}
};
//This function is responsible for animating slideUp of our menu on Desktop Layout
var animateUp = function() {
if ( ! isDesktop() ) {
return false;
}
var $this = $( this );
$this.find( 'ul:first' )
.slideUp( 500, function() { //Slide Up our open menu/es,execute callback function
var $this = $( this );
$this.removeClass( 'jsub-menu' ).addClass( 'sub-menu' ).removeAttr( 'style' );//Remove our added Jquery classes and add default Wordpress class "sub-menu"
} );
};
//This function is responsible for animating slideDown of our FIRST menu on Tablet Layout
function slideDownTabletsFirst( $this, minus, getHref ) {
if ( ! isDesktop() ) {
return false;
}
if ( $this.next( '.sub-menu' ).length ) {
$this.removeAttr( 'href' );
$this.next( '.sub-menu' ).slideDown( 300, function() {
var thisPrev = $( this ).prev( 'a' );
manageHref( thisPrev, getHref, minus );
} );
}
if ( $( '.nav ul.sub-menu:visible' ).length > 1 ) {
$( '.sub-menu:not(:animated)' ).slideUp( 300, function() {
var $this = $( this ),
minus = $( '.minus' );
decorationUp( $this, minus );
} );
}
}
//This function is responsible for animating slideDown of our SECOND menu (sub-menu) on Tablet Layout
function slideDownTabletsSecond( $this, minus, getHref ) {
if ( ! isDesktop() ) {
return false;
}
if ( $this.next( '.sub-menu' ).length ) {//Prevent link default behaviour (on first touch don't go to other page )
$this.removeAttr( 'href' );
$this.next( '.sub-menu' ).slideDown( 300, function() {
var thisPrev = $( this ).prev( 'a' );
manageHref( thisPrev, getHref, minus );
} );
}
}
//This function is responsible for animating slideUp/slideDown Menu on Phones Layout
function slideDownPhones( $this, minus, getHref ) {
if ( isDesktop() ) {
return false;
}
if ( $this.next( '.sub-menu' ).length ) {//Prevent link default behaviour (on first touch don't go to other page )
$this.removeAttr( 'href' );
$this.next( '.sub-menu' ).slideDown( 300, function() {
var thisPrev = $( this ).prev( 'a' );
manageHref( thisPrev, getHref, minus );
} );
}
}
//This is MAIN Function it is responsible for animating slideDown/slideUp of our menu on Mobile Layout's (Tablets/Phones)
//Uses functions defined above: decorationUp(),slideDownTabletsFirst(),slideDownTabletsSecond(),slideDownPhones()
function animateMobileMenu() {
$( '.nav ul a:not(.nav ul ul a)' ).on( 'touchstart touchmove', function() {//BIG TABLETS (Desktop Layout) checks if first "ul li a:first" is clicked...
var $this = $( this ),
minus = $( '.minus' ),
getHref = $this.attr( 'href' );
slideDownTabletsFirst( $this, minus, getHref );
} );
$('.nav ul ul a').on( 'touchstart touchmove', function() {
var $this = $( this ),
minus = $( '.minus' ),
getHref = $this.attr( 'href' );
slideDownTabletsSecond( $this, minus, getHref );
} );
$( '.nav a .minus' ).on( 'touchstart touchmove', function( e ) {
e.preventDefault();
e.stopPropagation();
var parentsUntil = $( this ).parentsUntil( 'ul' ).find( 'ul' ),
getFirstAnchor = $( 'ul.jnav a:not(ul.sub-menu a)' );
if ( isDesktop() && ! $( this ).parent().is( getFirstAnchor ) ) {//BIG TABLETS (Desktop Layout) checks if "ul ul li a" is clicked...
parentsUntil.slideUp( 300, function() {
var $this = $( this ),
minus = $( '.minus' );
decorationUp( $this, minus );
} );
} else if ( isDesktop() && $( this ).parent().is( getFirstAnchor ) ) {//BIG TABLETS (Desktop Layout) checks if "ul li a" is clicked...
parentsUntil.slideUp( 300, function() {
var $this = $( this ),
minus = $( '.minus' );
decorationUp( $this, minus );
} );
} else if ( ! isDesktop() ) {//MOBILE PHONES (Mobile Layout)
$( this ).parent( 'a' ).next( '.sub-menu' ).slideUp( 300, function() {
var $this = $( this ),
minus = $( '.minus' );
decorationUp( $this, minus );
} );
}
} );
$( '.nav a' ).on( 'touchstart', function() {
oldTouchPos = $( window ).scrollTop();
} ).on( 'touchend', function() {
var newTouchPos = $( window ).scrollTop(),
$this = $( this ),
minus = $( '.minus' ),
getHref = $this.attr( 'href' );
if ( ( Math.abs( oldTouchPos - newTouchPos ) ) < 3 && ! $this.children( '.minus' ).is( ':visible' ) ) {
slideDownPhones( $this, minus, getHref );
}
} );
$( '.menu-toggle' ).on( 'touchstart', function() {
getFirstLink.attr( 'href', '#' );//Remove Href on first menu Item Just in case the touch begins outside the registered area (user touches BOTH .menu-toggle AND first Link )
menuTogglePos = $( window ).scrollTop();
} ).on( 'touchend', function() {
var newMenuTogglePos = $( window ).scrollTop();
if ( ( Math.abs( menuTogglePos - newMenuTogglePos ) ) < 3 ) {
var navelem = $( '.nav' );
if ( navelem.is( ':animated' ) || $( '.search-text' ).is( ':visible' ) ) {
return false;
} else {
navelem.slideToggle( 500, function() {
getFirstLink.attr( 'href', getFirstLinkAttr );
} );
}
}
} );
}
$( '.nav ul:first ,ul.nav' ).removeClass( 'nav' ).addClass( 'jnav' );//Add jquery Class to our menu (If Menu Is Created via Wordpress Backend)
$( '.nav ul.children' ).removeClass( 'children' ).addClass( 'sub-menu' );//Add jquery Class to our menu (Default menu if no menu is created)
// Execute menu hover states and functions(animate functions,width and height properties in order to make our animations on Desktop Machines)
// Uses HoverIntent plugin
var config = {
sensitivity: 6, // number = sensitivity threshold (must be 1 or higher)
interval: 300, // number = milliseconds for onMouseOver polling interval
over: animateMenuWidth, // function = onMouseOver callback (REQUIRED)
timeout: 500,// number = milliseconds delay before onMouseOut
out: nullFunc// function = onMouseOut callback (EMPTY in order to avoid js errors)
};
var config2 = {
sensitivity: 6, // number = sensitivity threshold (must be 1 or higher)
interval: 300, // number = milliseconds for onMouseOver polling interval
over: animateMenuDropdown, // function = onMouseOver callback (REQUIRED)
timeout: 500, // number = milliseconds delay before onMouseOut
out: animateUp // function = onMouseOut callback
};
if ( "ontouchstart" in document ) {
animateMobileMenu();
} else {//On desktop hover
var subMenuAnchor = $( 'ul.jnav ul.sub-menu a' );
subMenuAnchor.hover( function() {
if ( ! isDesktop() ) {
return false;
}
$( this ).addClass( 'active' );
}, function() {
$( this ).removeClass( 'active' ).removeAttr( 'class' );
} );
subMenuAnchor.hoverIntent( config );
$( 'ul.jnav li' ).hoverIntent( config2 );
//If user is viewing page with resised browser show the Mobile Menu and handle the click's
$( '.menu-toggle' ).on( 'click', function() {
var menuContainer = $( '.nav' );
if ( menuContainer.is( ':animated' ) ) {
return false;
}
menuContainer.slideToggle( 500 );
} );
$('.nav a').click( function() {
var $this = $( this ),
minus = $( '.minus' ),
getHref = $this.attr( 'href' );
slideDownPhones( $this, minus, getHref );
} );
$( '.nav a .minus' ).click( function ( e ) {
e.preventDefault();
e.stopPropagation();
$( this ).parent( 'a' ).next( '.sub-menu' ).slideUp( 300, function() {
var $this = $( this ),
minus = $( '.minus' );
decorationUp( $this, minus );
} );
} );
}
toggleMenuVisibility();
equalizeHeight_If_Description();
$(window).on( "resize", function() {
var newWindowWidth = $( window ).width();
if ( newWindowWidth !== cachedWindowWidth ) { //Prevent toggleMenuVisibility() from running on window scroll (Android/IOS)
// Update the window width for next time
cachedWindowWidth = $( window ).width();
//Do menu visibility toggle
toggleMenuVisibility();
if ( isDesktop() ) { //If desktop remove all
$( '.jnav .sub-menu' ).slideUp( 500 );
$( '.nav ul a' ).css( { 'text-decoration': 'none' } );
$( '.searchbox' ).show();
$( '.minus' ).css( { 'display': 'none' } );
}
}
equalizeHeight_If_Description();
} );
})();
});
isDesktop
within an else statement where the previous if saysif( !isDesktop )
? \$\endgroup\$isDesktop
? The only way you would ever get to that if statement is if!isDesktop
istrue
. If!isDesktop
isfalse
, thenisDesktop
istrue
. \$\endgroup\$