There are already several questions addressing this issue. I am including mine for two reasons:
- It suggests a possible alternative solution
- The demo code may be useful to others who want to simulate a menu
After a CSS transition
, the user must move the mouse before the element that is now under the mouse will notice that it is in a :hover
state. I have created a menu-like feature that slides open to show different options. The option under the mouse at the end of the opening transition is not the same as the one under the mouse at the start of the transition. I have thus had to find a workaround.
You can find a jsFiddle here and the demo source below. Look for WORKAROUND (in three places) to see what I have done.
To see the issue, move the mouse over the menu and then leave it in place, without moving it. The list item that the browser thinks is :hover
will appear in blue. My workaround overrules the li:hover
rule with an li.ignoreHover
class. To make the workaround invisible, I can simply use the standard background colour. Instead, I am using blue to make the issue visible.
My question: I have noticed that pressing one of the modifier keys (Caps, Caps lock, Ctrl, Option/Alt, ⌘ on Mac, ...) will also force the :hover
state to update. Is there a way to send such an event to the #menu
element?
(My attempts to do so have not been successful, so I prefer to give you my working workaround than one that may not be valid).
<!DOCTYPE html>
<html>
<head>
<style>
#menu {
position: relative;
background: #ccc;
display: inline-block;
}
#wrapper {
margin: 5px;
}
#logo {
width: 150px;
height: 50px;
border: 1px solid #000;
margin: 0px auto;
z-index: 10;
}
nav {
width: 100%;
overflow: hidden;
text-align:center;
height: 2em;
}
ul {
position: relative;
display:inline-block;
margin: 0 auto;
padding: 0;
list-style-type: none;
text-align:left;
}
li {
display: block;
margin: 0;
padding:0.25em 0;
line-height: 1.5em;
}
ul.animated, nav {
transition: all 500ms linear 1s;
}
#menu.hover ul, #menu.hover nav {
transition-delay: 0s;
}
li:hover,
li.hover {
background-color: #999;
}
li.ignoreHover {
background-color: #ccf; /* a touch of blue, so you can see it */
}
.selected {
color: #fff;
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
</head>
<body>
<div id="menu">
<div id="wrapper">
<div id="logo"></div>
</div>
<nav>
<ul>
<li>Note one</li>
<li>Note two</li>
<li>Note three</li>
<li>Not four much longer</li>
<li>Note five</li>
<li>Note six</li>
</ul>
</nav>
</div>
<script>
var test = {}
;(function createMenu() {
var item = 3;
var minPadding = 5;
var hover = "hover" // class
var $li = $("li");
var $ul = $("ul");
var $menu = $("#menu");
var $nav = $("nav");
var itemHeight = parseInt($li.outerHeight(), 10);
var itemCount = $ul.children().length;
var menuWidth = $menu.outerWidth(true);
var padding = (menuWidth - $ul.width()) / 2;
var transitionDone = false;
var mouseOver = false;
var top;
// Pad the list items to fill the width of the menu
if (padding < minPadding) {
// Widen the menu to allow for the minimum padding
menuWidth += (minPadding - padding) * 2;
$menu.width(menuWidth);
padding = minPadding;
}
$li.css({
paddingLeft: padding,
paddingRight: padding
});
// Scroll to the current selected item
selectItem(true);
function selectItem(scroll) {
$ul.children().removeClass("selected");
$ul.children().eq(item).addClass("selected");
if (scroll) {
top = -(itemHeight * item);
$ul.css({
top: top
});
}
}
// Wait until the initial settings are applied
// before animating the transitions
setTimeout(function () {
$ul.addClass("animated");
}, 1);
// Handle interaction with the menu
$menu.on("mouseover", openMenu);
$menu.on("mouseleave", closeMenu);
$menu.on("transitionend", menuIsOpen);
$ul.on("click", treatClickOnItem);
// <WORKAROUND...
var x
var y
// ... WORKAROUND>
function openMenu(event) {
if (mouseOver) {
// This method may be called multiple times as the menu is
// transitioning to its open state
return
}
// <WORKAROUND...
$menu.on("mousemove", function updateXY(event) {
x = event.pageX
y = event.pageY
})
// ... WORKAROUND>
$menu.addClass(hover);
transitionDone = false;
mouseOver = true;
$nav.css({
height: (itemHeight * itemCount)
});
$ul.css({
top: 0
});
}
function menuIsOpen() {
transitionDone = true;
// <WORKAROUND...
var $hover = $("li:hover").addClass("ignoreHover")
var $item = $(document.elementFromPoint(x, y))
if (mouseOver) {
$item.addClass(hover)
}
$menu.on("mousemove", function () {
$item.removeClass(hover)
$hover.removeClass("ignoreHover")
$menu.off("mousemove")
})
//... WORKAROUND>
if (!mouseOver) {
closeMenu()
}
}
function closeMenu() {
mouseOver = false;
if (transitionDone) {
$menu.removeClass(hover)
$nav.css({
height: itemHeight
});
$ul.css({
top: top
});
}
}
function treatClickOnItem(event) {
item = $(event.target).index();
top = -(itemHeight * item);
selectItem();
// DO MORE STUFF WITH THE SELECTION
}
})()
</script>
</body>
</html>
compatible widths
? I'm not sure to get all your code... – Grosberg#menu
block is nicely presented with a margin, regardless of the relative widths of the logo and the text. You can try changing the item names, or the width in the #logo rule if you want to see the effect. The blueish colour is my way of making the worked-around issue visible. The code that needs attention is markedWORKAROUND
. I'd be interested to see your couple of jQuery lines that can handle all the rest : ) – Coolidge:hover
color#999
and the strange.ignoreHover
set to that blueish#ccf
. What is that.ignoreHover
for? Thanks – Grosberg.ignoreHover
, you will see what happens, that shouldn't happen. My question is about sending an modifier key event, to force an update to the:hover
element, just the way it happens when you press a modifier key. – Coolidge