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

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>
Github<i class="fa fa-github"></i>
</li>
<li>
BitBucket<i class="fa fa-bitbucket"></i>
</li>
<li>
Dropbox<i class="fa fa-dropbox"></i>
</li>
<li>
Google drive<i class="fa fa-google"></i>
</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.

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;
}

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

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.

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

Related

solution for use button with HREF scrolling or modify <a> link to a button

i have 5 button i wanna use them to scroll to section
for the first i make button, i customize button with css , and after when i try to make a scroll , i got problem, dosent work
i try to use a link , the problem is setting button on css dont work very good , the text is on TOP left
i try to use now on css scroll slow with JS ? or HTML with #link
thanks
HTML ( many version of boutton i make for u ONLY FORT TESTING )
<section id="jour">
<div class="container20">
Dimanche
<button onclick="window.location.href='#Lundi'"class="btn1">Lundi</br><b>13</b></button>
<button onclick="window.location.href='#Mardi'"class="btn1">Mardi</br><b>12</b></button>
<button onclick="scrollTo.location.href='#Mercredi'"class="btn1">Mercredi</br><b>13</b></button>
<button onclick="scrollTo.location.href='#Jeudi'"class="btn1">Jeudi</br><b>14</b></button>
<button class="btn1">Dimanche</br><b>12</b></button>
</div>
</section>
CSS :
.container20 {
display: flex;
justify-content: center;
}
.btn1 {
display: block;
height: 80px;
width: 80px;
color : #262552;
font-family: "Montserrat", sans-serif;
font-weight: 900;
font-size: 12px;
letter-spacing: 0.6px;
padding: 1px 1px;
border-radius: 3px;
transition: 0.3s;
margin: 30px 1px ;
background: #9FEDD7;
border: 3px solid #f7ee6f;
}
#jour .btn1:hover {
background-color:#f7ee6f;
border: 2px solid #f7ee6f;
color : #262552 ;
}
// Cache selectors
var lastId,
topMenu = $("#top-menu"),
topMenuHeight = topMenu.outerHeight()+15,
// All list items
menuItems = topMenu.find("a"),
// Anchors corresponding to menu items
scrollItems = menuItems.map(function(){
var item = $($(this).attr("href"));
if (item.length) { return item; }
});
// Bind click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e){
var href = $(this).attr("href"),
offsetTop = href === "#" ? 0 : $(href).offset().top-topMenuHeight+1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 300);
e.preventDefault();
});
// Bind to scroll
$(window).scroll(function(){
// Get container scroll position
var fromTop = $(this).scrollTop()+topMenuHeight;
// Get id of current scroll item
var cur = scrollItems.map(function(){
if ($(this).offset().top < fromTop)
return this;
});
// Get the id of the current element
cur = cur[cur.length-1];
var id = cur && cur.length ? cur[0].id : "";
if (lastId !== id) {
lastId = id;
// Set/remove active class
menuItems
.parent().removeClass("active")
.end().filter("[href='#"+id+"']").parent().addClass("active");
}
});
body {
height: 6000px;
font-family: Helvetica, Arial;
}
#top-menu {
position: fixed;
z-index: 1;
background: white;
left: 0;
right: 0;
top: 0;
}
#top-menu li {
float: left;
}
#top-menu a {
display: block;
padding: 5px 25px 7px 25px;
width: 4em;
text-align: center;
-webkit-transition: .5s all ease-out;
-moz-transition: .5s all ease-out;
transition: .5s all ease-out;
border-top: 3px solid white;
color: #aaa;
text-decoration: none;
}
#top-menu a:hover {
color: #000;
}
#top-menu li.active a {
border-top: 3px solid #333;
color: #333;
}
#foo {
position: absolute;
top: 400px;
}
#bar {
position: absolute;
top: 800px;
}
#baz {
position: absolute;
top: 1200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<ul id="top-menu">
<li class="active">
Top
</li>
<li>
Foo
</li>
<li>
Bar
</li>
<li>
Baz
</li>
</ul>
<a id="foo">Foo</a>
<a id="bar">Bar</a>
<a id="baz">Baz</a>

Issue with ToDO list app and list item length

I'm working on a Q/A bare bones todolist app and notice that when a list item that is really long is added to the list, it pushes the button out.
Is there a way I can make the LI element larger when the textnode hits the button margin instead of pushing the button out of the LI element. Below is a screenshot. I'll post my source code below, but maybe this is a question that is a quick fix?
My source code can be found here - Issue with floating buttons right of my to do list
A) If I understood you well, you can easily fix it with CSS-Grid:
li {
display: grid;
grid-template-columns: 3fr 100px;
grid-template-areas: 'text button';
}
li > span {
grid-area: text;
}
li > button {
grid-area: button;
height: 30px;
}
https://jsfiddle.net/axqwhj29/
Play with the example linked above resizing the result area to check if that's what you are looking for.
B) Also, but I don't recommend you, if you really don't wanna change your li hight and you have a maximum text width (ex: 25 characters), you can clip parts of your message in a phone vertical view and if the user flips to horizontal show the whole text automatically.
https://jsfiddle.net/qfy3mz01/
Hope this help :)
Okay I have wrapped the text inside the li with span element and and added I add grid display to li and give every element inside the li a width and then I have added word-break: break-word; so the line will break when the text of the span reach the width limit and don't affect the delete button and I've deleted height from li so the li will grow with the lines on it
var addItemButton = document.getElementById('addItem')
var onEnter = document.getElementById('newNote')
//below event listener adds an item to the list on click
addItemButton.addEventListener('click', function() {
let item = document.getElementById('newNote').value
let node = document.createElement("li")
let span = document.createElement("span")
let textnode = document.createTextNode(item)
span.appendChild(textnode)
node.appendChild(span)
if (item) {
document.getElementById('list-body').appendChild(node)
}
let node2 = document.createElement('BUTTON')
let textnode2 = document.createTextNode('Delete')
node2.appendChild(textnode2)
node.appendChild(node2)
node2.addEventListener('click', function() {
node2.parentNode.parentNode.removeChild(node)
});
document.getElementById('newNote').value = ''
});
onEnter.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
// Cancel the default action, if needed
event.preventDefault();
// Trigger the button element with a click
addItemButton.click();
}
})
function applyButton() { //onload for dummy data or data from db
let getListObjects = document.querySelectorAll("li")
for (let i = 0; i < getListObjects.length; i++) {
let node2 = document.createElement('BUTTON')
let textnode2 = document.createTextNode('Delete')
node2.appendChild(textnode2)
getListObjects[i].appendChild(node2)
let y = getListObjects[i].querySelector('button')
y.addEventListener('click', function() {
y.parentNode.parentNode.removeChild(getListObjects[i])
});
}
}
.container {
height: 100%;
width: 40%;
margin: 0 auto;
}
.container2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
background-color: grey;
border: 1px solid grey;
}
#main-grid {
width: 100%;
}
#newNote {
height: 25px;
}
#inputIdForGrid {
justify-content: left;
align-items: center;
display: flex;
padding-left: 0.3em;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
button {
padding: 10px 18px;
background-color: green;
border: none;
color: white;
font-size: 14px;
align-self: center;
justify-self: end;
}
#addItem {
margin-left: 1em;
padding: 0.5em;
color: white;
font-size: 1.5em;
float: right;
}
ul {
list-style-type: none;
padding: 0px;
margin: 0px;
}
li {
padding: 5px 15px;
display: grid;
grid-template-columns: 2.5fr .5fr;
}
span {
word-break: break-word;
grid-column: 1 / 2;
display: flex;
align-items: center;
}
li:nth-child(2n) {
background-color: grey;
}
li>button {
background-color: red;
}
h1 {
text-align: center
}
<body onload="applyButton()">
<h1>Vanilla JS ToDo List - No Jquery, No Bootstrap</h1>
<div class='container'>
<div id='main-grid'>
<div class="container2">
<div id='inputIdForGrid'>
<input type='text' placeholder="Enter List Items Here" id='newNote'>
</div>
<div>
Hi
</div>
</div>
<ul id='list-body'>
<li><span>run all around town. walk all around town. drive all around town</span></li>
<li><span>Buy Apples</span></li>
<li><span>Hit Gym and Lift Bro</span></li>
<li><span>Stretch</span></li>
</ul>
</div>
</div>
</body>
P.S. I've edited your js code so it will generate span and add the text inside it

Translating jQuery to Vanilla JS issue

I was watching a tutorial that used jQuery and wanted to turn it into JS, but my code is broken - was hoping someone could help me with this:
Tutorial JS:
$(function() {
var btn = $('button');
var progressBar = $('.progressbar');
btn.click(function() {
progressBar.find('li.active').next().addClass('active');
})
})
Taken from URL:http://www.kodhus.com/kodity/codify/kod/mGXAtb
Here is my failed attempt at rewriting the jQuery using JavaScript DOM:
var btn1 = document.getElementsByTagName('BUTTON');
var progBar = document.getElementsByClassName('progressbar');
function clickMe1() {
var elm = progBar.querySelectorAll("li");
var emlClass = elm.querySelector(".active");
return emlClass.nextElementSibling.addClass('active');
}
btn1.addEventListener("click", clickMe1, false);
where did I go wrong?
Working fiddle.
Your code will work after several changes check the notes below :
You've missed addClass() there it's a jQuery function, for vanilla JS use .classList.add() instead:
return emlClass.nextElementSibling.classList.add("active");
querySelectorAll(); will return a list of nodes you have to loop through them and add class, use :
var emlClass = progBar.querySelectorAll("li.active");
Instead of :
var elm = progBar.querySelectorAll("li");
var emlClass = elm.querySelector(".active");
Then loop and add active class:
for(var i=0;i<emlClass.length;i++){
emlClass[i].nextElementSibling.classList.add("active");
}
getElementsByTagName() and getElementsByClassName() will also returns a list of nodes with given name, you have to specify which one you want to pick (selecting the first in my example) :
var btn1 = document.getElementsByTagName('BUTTON')[0];
var progBar = document.getElementsByClassName('progressbar')[0];
Hope this helps.
var btn1 = document.getElementsByTagName('BUTTON')[0];
var progBar = document.getElementsByClassName('progressbar')[0];
function clickMe1() {
var emlClass = progBar.querySelectorAll("li.active");
for(var i=0;i<emlClass.length;i++){
emlClass[i].nextElementSibling.classList.add("active");
}
}
btn1.addEventListener("click", clickMe1, false);
.container {
width: 100%;
}
.progressbar {
counter-reset: step;
margin: 0;
margin-top: 50px;
padding: 0;
}
.progressbar li {
list-style-type: none;
float: left;
width: 33.33%;
position: relative;
text-align: center;
}
.progressbar li:before {
content: counter(step);
counter-increment: step;
width: 30px;
height: 30px;
line-height: 30px;
border: 2px solid #ddd;
display: block;
text-align: center;
margin: 0 auto 10px auto;
border-radius: 50%;
background-color: white;
}
.progressbar li:after {
content: '';
position: absolute;
width: 100%;
height: 2px;
background-color: #ddd;
top: 15px;
left: -50%;
z-index: -1;
}
.progressbar li:first-child:after {
content: none;
}
.progressbar li.active {
color: green;
}
.progressbar li.active:before {
border-color: green;
}
.progressbar li.active + li:after {
background-color: green;
}
button {
position: relative;
border: none;
padding: 10px 20px;
font-size: 16px;
border-radius: 2px;
left: 50%;
margin-top: 30px;
transform: translate(-50%);
cursor: pointer;
outline: none;
}
button:hover {
opacity: 0.8;
}
<div class="container">
<ul class="progressbar">
<li class="active">Step 1</li>
<li>Step 2</li>
<li>Step 3</li>
</ul>
</div>
<button>Next step</button>
.querySelectorAll("li") will return an array (or an array-like object) with one or more <li> tags. So you need to either:
loop through every <li> in that list and do the rest,
or just take the first item from that list if you don't want to worry about there being more than one li in the page,
or use .querySelector (not .querySelectorAll) to just take the first <li> for you.
MDN

Google map doesn't show right in nav-tab

Google Maps doesn't show in a nav-tab.
It doesn't show the place, but when I click on the map; it's correct in google.
the problem
HTML:
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade" id="locatie"><?php if ($object->hasLocation): ?>
<div>
<h2>Kaart</h2>
<div id="google-map"></div>
<h2>Street View</h2>
<div id="google-streetview"></div>
</div>
<?php endif; ?></div>
</div>
JS
(function($, exports) {
'use strict';
var GoogleMap = exports.GoogleMap = function(options) {
this.map = null;
this.markers = [];
this.bounds = null;
this.info = null;
this.options = $.extend({
container: '#google-map',
mapContainer: null,
map: {}
}, options || {});
this.$container = null;
};
CSS
.single-post-tst {
.entry-content {
width: 100%;
margin: 2% 0 10%;
display: inline-block;
p {
font-size: 16px;
}
}
.container-content-single {
ul, ol {
margin: 0 0 1.5em 0;
}
.nav-tabs {
border: 1px solid #ddd;
li {
margin-bottom: 0;
border-right: 1px solid #ddd;
}
li:last-child {
border-right: none;
}
li a {
border-radius: 0;
line-height: 2em;
margin-right: 0;
}
li a:hover {
background-color: #F29902;
color: #fff;
}
li.active > a {
padding-bottom: 12px;
}
a.nav-link.active {
background-color: #F29902;
color: #fff;
}
}
.nav-tabs>li.active>a, .nav-tabs>li.active>a:focus, .nav-tabs>li.active>a:hover {
border: none;
background-color: #F29902;
color: #fff;
}
}
}
#google-map,
#google-streetview {width:100%; height:500px;}
I think the problem is with this line in JS:
container: '#google-map',
When I put the map out off the tab, it works correct.
But I can't find any solution to show the map correct in the tab.
It shows now but not full width, I add this to CSS:
.tab-content.tab-pane,
.tab-pane {
/* display: none; */
display: block;
visibility: hidden;
position: absolute;
}
.tab-content.active,
.tab-content .tab-pane.active,
.tab-pane.active {
/* display: block; */
visibility: visible;
position: static;
}
It look like this now
When I say width: 100%; it doesn't make full width
If the map is on a secondary tab it wont get the proper sizing, if you can place him on the active tab the problem will be solved.
If you want to maintain the map on an hidden tab, please check how to force the refresh / resize of the map when it becomes visible.
How do I resize a Google Map with JavaScript after it has loaded?
Sample code:
google.maps.event.trigger(map, "resize");
EDIT, another option:
You can also set a fixed size for the map, you can check the code sample here ;)
how to deal with google map inside of a hidden div (Updated picture)
I found a solution:
.tab-content > .tab-pane {
display: block;
height:0;
overflow:hidden;
}
.tab-content > .active {
display: block;
height:auto;
}

jQuery loop pausing in a particular spot

I have a strange issue with a script that I've created. My jQuery/Javascript skills aren't great (I'm still learning) and am hoping someone could help me understand why this is happening.
I'm developing an online store and have a strip of 4 divs floated next to each other across the top with notices I'd like to highlight for my customers.
The site is responsive, so for mobile I wanted to reduce this to one notice at a time, and fade out and fade in each notice.
I also didn't want to simply use CSS media queries to show and hide a desktop and mobile version as I feel that might work against me, SEO-wise, if I was to repeat the content twice. Therefore I've put together a jQuery script to grab the content of the first set of divs, put them into an array, and fade in and out each notice in a loop.
I thought I'd done it however noticed something strange in both Firefox and Chrome: it loops through once fine, but then stops completely when displaying "100% happiness guarantee" the second time, and I'm at a loss as to why.
I've created a JSFiddle with the code I'm using here:
http://jsfiddle.net/qewwmnge/
$(document).ready(function() {
// Transform the highlights div into a 1 line bar for mobile devices
// Read the highlights div content into an array
var highlights = new Array();
$("#highlights").find("div").each(function(){
highlights.push($(this).html());
});
$text = $('#highlights-mobile div'),
delay = 5;
// Set the initial highlight item on page load
$text.html( highlights[0] );
// Loop through the array and fade in each highlight
function loop ( delay ) {
$.each( highlights, function ( i, elm ){
if ($text.html() != highlights[i]) { // Skip the first fade in on the first loop so it doesn't repeat itself
$text.delay( delay*1E3).fadeOut();
$text.queue(function(){
$text.html( highlights[i] );
$text.dequeue();
});
$text.fadeIn();
$text.queue(function(){
if ( i == highlights.length -1 ) {
loop(delay);
}
$text.dequeue();
});
}
});
}
loop( delay );
});
If anyone could tell me what I'm doing wrong I'd really appreciate it!
Your jQuery code contain queue/dequeue logic, there is no need to do that.
It's better to use simple jquery for same thing. See this demo JSFiddle
$(function () {
var $highlights = $("#highlights-mobile div");
var divsHTML = new Array();
$("#highlights").find("div").each(function(){
divsHTML.push($(this).html());
});
var position = -1;
!function loop() {
position = (position + 1) % divsHTML.length;
$highlights.html(divsHTML[position])
.fadeIn(1000)
.delay(1000)
.fadeOut(1000,loop);
}();
});
#highlights, #highlights-mobile {
background-color: #E8E8E8;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
position: relative;
display: block;
box-sizing: border-box;
overflow: auto;
text-align: center;
margin-bottom: 20px;
}
#highlights h4, #highlights-mobile h4 {
text-align: center;
font-weight: bold;
padding: 0;
margin: 0;
font-size: 1.2em;
}
#highlights-mobile {
padding: 10px;
}
#highlights-mobile a, #highlights a {
color: #444;
text-decoration: none;
}
#highlights div {
text-align: center;
padding: 10px;
border-right: 1px solid #CDCDCD;
color: #444;
overflow: auto;
display: inline-block;
}
#media (min-width: 768px) {
#highlights {
display: block;
}
#highlights div:last-child {
border-right: none;
}
}
#media (min-width: 768px) {
#highlights div {
text-align: center;
padding: 10px;
border-right: 1px solid #CDCDCD;
color: #444;
overflow: auto;
display: inline-block;
}
#highlights div:last-child {
border-right: none;
}
#highlights h4 {
text-align: center;
font-weight: bold;
padding: 0;
margin: 0;
font-size: 1.2em;
}
#highlights a {
color: #444;
text-decoration: none;
}
#highlights-mobile {
display: none;
}
}
#media (max-width: 767px) {
#highlights {
display: none;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="highlights-mobile">
<div style="display: block;">
</div>
</div>
<div id="highlights">
<div>
<h4>Professionally refurbished</h4>Old school gear, good as new
</div>
<div>
<h4>Free shipping</h4>On orders over $250, nation wide
</div>
<div>
<h4>100% happiness guarantee</h4>Easy returns and a 60 day warranty
</div>
<div>
<h4>5% off your order</h4>When you pay with Bitcoin
</div>
</div>

Categories