Rex Garcia
Rex Garcia

Reputation: 83

Hold ul visible when parent loses hover (pure css if possible)

I'm quite new with css. I want hold the ul visible when hovering from parent to ul. I don't know how do it.

HTML Markup

<drop-down class="dropdown">
    <span>Dropdown menu<i class="fa fa-cog"></i></span>
    <ul>
        <li>
            <a href="#">Github<i class="fa fa-github"></i></a>
        </li>
        <li>
            <a href="#">BitBucket<i class="fa fa-bitbucket"></i></a>
        </li>
        <li>
            <a href="#">Dropbox<i class="fa fa-dropbox"></i></a>
        </li>
        <li>
            <a href="#">Google drive<i class="fa fa-google"></i></a>
        </li>
    </ul>
</drop-down>

CSS

drop-down {
    background-color: #e9e9e9;
    border: 1px solid #d2c2c2;
    border-radius: 2px;
    display: flex;
    flex-flow: column nowrap;
    height: 40px;
    justify-content: center;
    position: relative;
    width: 160px;
}
drop-down:hover { cursor: pointer; }
drop-down > span {
    align-items: center;
    color: #555;
    display: flex;
    font-family: 'segoe ui';
    font-size: .9rem;
    justify-content: space-between;
    padding: 0px .75rem;
    pointer-events: none;
}
drop-down > span > i {
    color: inherit;
}
drop-down ul {
    background-color: #e9e9e9;
    border: 1px solid #d2c2c2;
    border-radius: 2px;
    box-shadow: 0px 2px 5px 1px rgba(0,0,0,.15);
    display: block;
    right: 10%;
    list-style: none;
    padding: .5rem 0;
    position: absolute;
    opacity: 0;
    pointer-events: none;
    visibility: hidden;
    top: 160%;
    transition: all .2s ease-out;
    width: 100%;
    z-index: 999;
}
drop-down ul > li {
    color: #555;
    display: block;
}
drop-down ul > li:hover {
    background-color: #007095;
    color: rgba(255,255,255,.9);
}
drop-down ul > li > a {
    align-items: center;
    color: inherit;
    display: flex;
    font-family: 'segoe ui';
    font-size: .95rem;
    justify-content: space-between;
    padding: .5rem .75rem;
    text-decoration: none;
}
drop-down ul > li > a > i {
    color: inherit;
}
drop-down:focus {
    outline: none;
}
drop-down:hover ul {
    pointer-events: auto;
    opacity: 1;
    top: 120%;
    visibility: visible;
}

You can see it running at this fiddle: http://jsfiddle.net/vt1y9ruo/1/

I can do it with javascript, but I don't want use it for something small.

Upvotes: 0

Views: 159

Answers (4)

Rex Garcia
Rex Garcia

Reputation: 83

Well, pure css solution (many thanks @JBux) is a little dirty (mark up). I finally go for JS solution and for this, created a custom tag:

const helper = new Helper(); // helper functions
var ddProto = Object.create(HTMLElement.prototype);
ddProto.properties = {
    list: null,
    options: null,
    value: null,
    icon: null,
    index: -1,
};
ddProto.initEvents = function() {
    var self = this;
    // mouse over button
    this.addEventListener('mouseover', function(e) {
        if(!helper.hasClass(this, 'dropdown-active'))
            helper.addClass(this, 'dropdown-active');
    });
    // mouseleave over button
    this.addEventListener('mouseleave', function(e){
        var rect = this.getBoundingClientRect();
        var left = e.pageX;
        var bottom = e.pageY;
        // if mouse is out of X axis of button and if mouse is
        // out (only of top) of Y axis of button, hide ul
        if(left < rect.left || left >= rect.right || bottom < rect.top) {
            helper.delClass(this, 'dropdown-active');
        }
    });
    // list loses hover
    this.properties.list.addEventListener('mouseleave', function(e) {
        if(helper.hasClass(self, 'dropdown-active'))
            helper.delClass(self, 'dropdown-active');
    });
    // elements click
    [].forEach.call(this.properties.options, function(e) {
        e.addEventListener('click', function(event){
            event.preventDefault();
            // set the text of selected value to button
            helper.text(self.properties.value, e.innerText);
            // set the position of selected value
            self.properties.index = helper.position(e.parentNode);
            // set the <i> class name to the button (fontawesome)
            self.properties.icon.className = this.children[0].className;
            // hide ul
            helper.delClass(self,'dropdown-active');
        },true);
    });
};
ddProto.value = function() {
    return this.properties.value;
};
ddProto.index = function() {
    return this.properties.index;
}
ddProto.createdCallback = function() {
    this.properties.list = this.querySelector('ul');
    this.properties.options = this.querySelectorAll('ul > li > a');
    this.properties.value = this.querySelector('span');
    this.properties.icon = this.querySelector('span > i');
    this.initEvents();
};
document.registerElement('drop-down', {prototype: ddProto});

Working fiddle: http://jsfiddle.net/m2dtmr24/2/

Thank you so much.

Upvotes: 0

JBux
JBux

Reputation: 1394

Here's a working fiddle: http://jsfiddle.net/vt1y9ruo/8/

It works by inserting an invisible bridge between the button and the list.

drop-down:hover ul, #ulwrap:hover ul {
    pointer-events: auto;
    opacity: 1;
    top:120%;
    visibility: visible;
}
#ulwrap {
    display: block;
    height:0;
    width:100%;
    position:absolute;
}
drop-down:hover #ulwrap, #ulwrap:hover {
    height:100px;
}

Upvotes: 4

bruceyyy
bruceyyy

Reputation: 439

if you want to do this using the hover feature of css, the gap between the button and the list is what's killing you. either remove this gap or use js

on a side note there is no harm in using js for something small, this is what its used for, just make it nice and reusable

Upvotes: 1

Piotr Kruczek
Piotr Kruczek

Reputation: 2390

The thing you could check is the + selector (more here)

In short it lets you add styles to elements right next to each other. The actual css might look something like this:

.dropdown{
  display: none;
}    
.button:hover+.dropdown{
  display: block;
}

This will only work when .dropdown is directly below .button in the DOM

The animation might be harder, but you could achieve something similar by for example using transition on opacity, and toggle opacity instead of display

Upvotes: -1

Related Questions