Dropdown menu css / js on Ipad - javascript

I'm working on a website with a simple and pure css dropdown menu. That website is supposed to be used on desktop and on Ipads. My dropdown menu uses :hover pseudo-class and issue appears on the touch screen: the menu expands well but it never collapse. The only way to close it is to open another submenu from the same dropdown menu. My goal is that my menu collapse when I touch anywhere in the DOM (except in the menu of course). After many researches, it appears that we can not do this with css, js is obligatory. I am a beginner and I have no skill in JS, is it possible to do it with only few vanilla js lines ? I don't want to use jquery. Here is my code:
/* ========================================================================= */
/* Global styles */
/* ========================================================================= */
html {
box-sizing: border-box;
font-size: 62.5%;
}
*, *:before, *:after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
body, input {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
img {
border: none;
}
/* ========================================================================= */
/* Layout styles */
/* ========================================================================= */
body > header, body > main {
margin: auto;
}
body > header {
padding-top : 20px;
width: 768px;
}
body > header > img {
width: 150px;
margin-left: 10px;
}
/* ========================================================================= */
/* Nav styles */
/* ========================================================================= */
body > header > nav {
min-width: 768px;
margin: 0 auto;
padding-top: 20px;
font-size: 1.5em;
text-align: center;
}
nav > ul ul {
position: absolute;
display: none;
text-align: left;
}
nav li {
width: 150px;
}
nav > ul > li {
display: inline-block;
}
nav a {
display: block;
text-decoration: none;
}
nav > ul > li > a {
padding: 10px 0;
color: #44546A;
}
nav > ul ul li {
background-color: #333F50;
list-style-type: none;
}
nav > ul ul li a {
padding: 10px 0 10px 30px;
color: #FAFAFA;
font-size: 0.9em;
}
nav > ul li:hover ul {
display: block;
}
nav > ul ul li:hover {
background-color: #51647f;
}
<!DOCTYPE html>
<html>
<head>
<base href="/"/>
<meta charset="UTF-8"/>
<title>Test Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" type="text/css" href="css/normalize.css"/>
<link rel="stylesheet" type="text/css" href="css/styles.css"/>
</head>
<body>
<header>
<img src="img/test.svg" alt="test"/>
<nav>
<ul>
<li>
Menu 1
<ul class="subMenu">
<li>
SubMenu 1.1
</li>
</ul>
</li>
<li>
Menu 2
<ul>
<li>
SubMenu 2.1
</li>
<li>
SubMenu 2.2
</li>
<li>
SubMenu 2.3
</li>
</ul>
</li>
<li>
Menu 3
<ul>
<li>
SubMenu 3.1
</li>
</ul>
</li>
<li>
Menu 4
<ul class="subMenu">
<li>
SubMenu 4.1
</li>
</ul>
</li>
</ul>
</nav>
</header>
</body>
</html>
Edit : That code works well on tablets but not on Ipads

The :hover pseudo-class behaves differently on touch screen devices. When the user touches an element, the browser triggers and keeps the state :hover until another pseudo-class is triggered. Thus, when the user touches another element on the page, a different pseudo-class is triggered by the browser and the drop-down menu becomes hidden. Most of the time, it is the :active pseudo-class that is triggered.
However, as explained on the Safari Developer Library, Mobile Safari doesn't trigger the :active pseudo-class unless a touch event is attached to the element:
On iOS, mouse events are sent so quickly that
the down or active state is never received. Therefore, the :active
pseudo state is triggered only when there is a touch event set on the
HTML element—for example, when ontouchstart is set on the element...
To fix this, you can add a touchstart listener to your document in order to trigger the :active pseudo-class:
document.addEventListener('touchstart', function() {});

Here a solution, ask if you want explanation.
<nav id='nav'>
...
<script>
function hideDropDownMenu(e) {
var element = e.target;
var parent = element.parentNode;
var mustHide = false;
while (parent != null && !mustHide) {
mustHide = element.id === 'nav';
element = element.parentNode;
}
var subMenus = document.getElementsByClassName('subMenu');
var i = 0;
for (i = 0; i < subMenus.length; i++) {
var subMenu = subMenus[i];
subMenu.style = mustHide ? 'none !important' : 'block'; // not sure if the !import is optionnal
}
}
document.body.addEventListener('click', hideDropDownMenu);
</script>

As I posted in another question:
I solved this problem by adding a tabindex to the <body> tag, like this:
<body tabindex="0">
This little trick allowed iPad Safari to focus on the body when it's tapped on, and remove the focus from the dropdown menu.
No Javascript required. 😊

Related

Converting tabs into dropdown menu (not select)

I am currently using Bootstrap to create tabs and the task is for mobile screen (800px <), it should automatically change into a "fake" dropdown. When i say fake, i mean no select box but utilising the the same ul as the tabs, it should automatically use the first item as a trigger.
I can get the tabs working fine: https://codepen.io/wizly/pen/BlKxo
What do i want to achieve?
On mobile view, when the tabs are in more of a dropdown format, i want to try and get the label for the active tab right at the top of the list.
Scenario: Mobile View Default
[ITEM 1 active >]
Scenario: Mobile view Clicked
[ITEM 1]
[ITEM 2]
[ITEM 3]
Scenario: Mobile view item 3 clicked
[ITEM 3 active >]
Scenario: Mobile view item 3 active clicked
[ITEM 1]
[ITEM 2]
[ITEM 3]
Where am i so far:
$('li a').on('click', function() {
const current = $(this);
const ul = current.parent().parent();
const li = current.parent();
if ($(window).width() < 1023.98) {
ul.toggleClass('expanded');
$('li').removeClass('active');
$('.fake-active').removeClass('fake-active');
if (ul.hasClass('expanded')) {
$('li').removeClass('active');
current.addClass('fake-active');
}
li.toggleClass('active');
// ul.prepend(current.parent());
}
});
#charset "UTF-8";
body {
padding: 10px;
}
#exTab2 h3 {
color: white;
background-color: #428bca;
padding: 5px 15px;
}
#media screen and (max-width: 800px) {
ul li {
display: block !important;
float: none !important;
}
ul li:first-child a {
text-align: left !important;
}
ul li:first-child a:after {
font-family: fontello;
content: "";
font-size: 1em;
font-weight: 400;
font-style: normal;
font-variant: normal;
line-height: 1;
min-width: 1em;
margin-left: 1em;
text-decoration: inherit;
text-transform: none;
float: right;
transform: rotate(90deg);
}
ul li:not(:first-child) {
display: none !important;
}
ul.expanded li:not(:first-child) {
display: block !important;
}
ul.expanded li:not(:first-child) a {
text-align: left !important;
}
ul.expanded li:first-child a:after {
content: "";
transform: rotate(270deg);
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"/>
<div id="exTab2" class="container">
<ul class="nav nav-tabs">
<li class="active">
Overview
</li>
<li>Without clearfix
</li>
<li>Solution
</li>
</ul>
<div class="tab-content ">
<div class="tab-pane active" id="1">
<h3>Standard tab panel created on bootstrap using nav-tabs</h3>
</div>
<div class="tab-pane" id="2">
<h3>Notice the gap between the content and tab after applying a background color</h3>
</div>
<div class="tab-pane" id="3">
<h3>add clearfix to tab-content (see the css)</h3>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
I know it might be too late...
based on your source code, i used .active instead of :first-child for css selector and changed JS a little, and it seems work properly.
here's the codepen https://codepen.io/lebniz/pen/zYdLXxJ
js:
$("li a").on("click", function () {
const current = $(this);
const ul = current.parent().parent();
let tab_id = current.attr("href");
if ($(window).width() < 1023.98) {
$(tab_id).addClass("active").siblings().removeClass("active");
ul.toggleClass("expanded");
}
});

Adding and removing classes for switching tabs

I'm working on a tab design using jQuery to switch between each tab with the slide animation. I'll be the first to admit I'm still learning jQuery but I'm having issues with it jumping rather than a smooth animation and then hiding both tabs. The reason for the delay is to add transitions so it's a smooth motion when it slides up and the second layer comes down. The button active class mimics the timing for a nice look.
Here where I am...
jQuery(document).ready(function(){
jQuery('.schedule-tab-links a[href*="#day2"]').click(function(){
jQuery('.schedule-tab-active').slideToggle('450');
jQuery('#day2').delay('450').slideToggle('450');
jQuery('.button-active').delay('450').removeClass('button-active');
jQuery('.schedule-tab-links a[href*="#day2"]').delay('450').addClass('button-active');
jQuery('.schedule-tab-active').delay('450').removeClass('schedule-tab-active');
jQuery('#day2').delay('450').addClass('schedule-tab-active');
});
});
You can see the full code here
.delay(450) has no effect on .addClass()/.removeClass().
It's ONLY for animations. So you got to use .setTimeout() for those.
Then, you want that effect to occur only if that tab is NOT already active, rigth? Else, it slides up and down uselessly.
Here it is updated:
jQuery(document).ready(function(){
jQuery('.schedule-tab-links a[href*="#day2"]').click(function(){
if(!$(this).hasClass("button-active")){
jQuery('.schedule-tab-active').slideToggle('450');
jQuery('#day2').delay('450').slideToggle('450');
setTimeout(function(){
jQuery('.button-active').removeClass('button-active');
jQuery('.schedule-tab-links a[href*="#day2"]').addClass('button-active');
jQuery('.schedule-tab-active').removeClass('schedule-tab-active');
jQuery('#day2').addClass('schedule-tab-active');
},450);
}
});
});
.schedule-tab-links {
margin: 0;
padding: 0;
list-style-type: none;
margin-top: 1em;
}
.schedule-tab-links li {
display: inline-block;
}
.schedule-tab-links li a {
padding: .5em;
background: #e8e8e8;
}
.schedule-tab-links li .button-active {
background-color: #999;
}
.schedule-tabs {
padding: 2em;
margin-top: 1em;
display: none;
}
.schedule-tabs.schedule-tab-active {
display: block;
}
#day1 { background-color: green; }
#day2 { background-color: grey; }
#day3 { background-color: purple; }
#day4 { background-color: orange; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<ul class="schedule-tab-links">
<li>Day 1</li>
<li>Day 2</li>
<li>Day 3</li>
<li>Day 4</li>
</ul>
<div id="day1" class="schedule-tabs schedule-tab-active">Day 1 content goes here</div>
<div id="day2" class="schedule-tabs">Day 2 content goes here</div>
<div id="day3" class="schedule-tabs">Day 3 content goes here</div>
<div id="day4" class="schedule-tabs">Day 4 content goes here</div>
</body>
</html>
Then, I suggest you use a class too for those tabs... In order to just slightly change that click handler... Instead of duplicating it 4 times or more.
The usage of $(this) is a hint...
;)

Use a Click Event to Add and Remove a Class to individual loop items

I want to add a CSS class to a nav menu item each time it's clicked (so i can then style that menu item so people know this is the page they are on). I know I need to loop through the .menu-item class and add / remove a new CSS class using a loop, but I can't seem to get it to play ball.
I'm guessing I need to make the .menu-item the 'this' object and use a boolean to add or remove the CSS class, dependent on whether the currentItem variable is set to true or false.
I can't seem to get this to play though and I'm not 100% sure I'm using the event listener in the correct way.
Any help would be awesome.
codepen: https://codepen.io/emilychews/pen/eeKPoo?editors=1010
JS
var navlinks = document.getElementsByClassName('menu-item')
var currentItem = false;
for (i = 0; i<navlinks.length; i+=1) {
function addCurrentItemToMenu() {
if (currentItem === false) {
navlinks = this
this.classList.add('current-item')
currentItem = true
} else {
this.classList.remove('current-item')
currentItem = false
}
}
}
navlinks.addEventListener("click", function(){
addCurrentItemToMenu()
}, false)
CSS
body, ul {padding: 0; margin: 0}
#main-header {width: 100%; height: 100px;}
#mobile-menu-button {display: none;}
#main-navigation {
position: relative;
width: 100%;
height: 100px;
background: red;
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 10px 5% 10px 5%;
align-items: center;
}
ul#nav-menu-items {
display: flex;
margin-left: auto;
}
#main-navigation ul li {list-style-type: none;}
ul#nav-menu-items li a {
padding: 10px 15px;
margin: 0 5px;
box-sizing: border-box;
background: yellow;
text-decoration: none;
color:#000;
}
#main-navigation ul#nav-menu-items li a:hover {
color:blue;
transition: color .25s;
}
HTML
<header id="main-header">
<nav id="main-navigation">
<ul id="nav-menu-items">
<li class="menu-item">News</li>
<li class="menu-item">About</li>
<li class="menu-item">Contact</li>
</ul>
</nav>
</header>
Here is a modified codepen for your problem: https://codepen.io/anon/pen/RjJEWe?editors=1010
The code simply add current-item class to the clicked anchor, so you can style it however you want in css. You can also see event.preventDefault() in the code to prevent anchor from following your link, as it will reload the page and js won't do anything. It depends on the stack that you are using. If you have server backed website, the current link will be handled by the server and html returned will already have the class set appropriately, if you have frontend js framework (Angular, VueJS, ReactJS), you must handle it appropriately.
Just for your example you can see the code below:
var navlinks = document.querySelectorAll('li.menu-item > a');
// Loop through all the links and add event listener
navlinks.forEach(function(item) {
item.addEventListener('click', function(event) {
event.preventDefault();
// Remove the class from all elements
navlinks.forEach(function(item) {
item.classList.remove('current-item');
})
// add the class to the current one
this.classList.add('current-item');
});
});

Add Trailing Ellipsis To Breadcrumb Depending On Device Width

On my user interface I have a breadcrumb of which shows on the top bar. Upon the device width being below a defined width, it'll drop below the top bar and be it's own bar, however what I do not know how to do is add a trailing ellipsis upon the breadcrumb length being larger than the device width.
Example Breadcrumb:
<nav>
<ul>
<li>Home</li>
<li>>></li>
<li>User</li>
<li>>></li>
<li>Inbox</li>
<li>>></li>
<li>Mail_ID</li>
</ul>
</nav>
Note: >> represents a FontAwesome icon in an i tag
Upon the breadcrumb being larger than the device width, the best I can describe what I would like to happen is demonstrated below:
Home >> User >> Inbox >> Mail_ID
... User >> Inbox >> Mail_ID
... Inbox >> Mail_ID
... Mail_ID
This is still a partial code but might help you.
Idea
On load, call a function that checks for with of ul and its parent container.
If ul has greater width, hide first 2 visible li. Also add an li for ellipsis and make it hidden initially and make it visible only if any of other divs are hidden.
Repeat this process recursively and you will get what you are looking for.
Sample
$(function() {
$(".content").resizable();
$(".content").on("resize", function() {
var ul = $(this).find('ul');
if (ul.width() > $(this).width()) {
var lis = ul.find('li:not(.hide):not(.ellipsis)');
for (var i = 0; i < 2; i++) {
$(lis[i]).addClass("hide");
}
if ($(".ellipsis").not(":visible"))
$(".ellipsis").removeClass("hide")
}
})
});
.content {
text-align: left;
border: 1px solid gray;
width: 300px;
height: 40px;
max-height: 40px;
}
.content ul {
padding: 0px;
position: fixed;
overflow: hidden;
white-space: nowrap;
}
.content ul li {
display: inline-block;
text-decoration: none;
}
.hide {
display: none!important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div class="content">
<ul>
<li class="hide ellipsis">...</li>
<li>Home</li>
<li>>></li>
<li>User</li>
<li>>></li>
<li>Inbox</li>
<li>>></li>
<li>Mail_ID</li>
</ul>
</div>
You can try to use the CSS-only ellipsis, but I don't know if it also works with <ul><li>. For sure it works with simple strings:
Use this HTML:
<ul class="ellipsis">
And this CSS:
ul.ellipsis
{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

cannot make list item visible with > CSS selector

I'm unable to make the popups 'redItem', 'blueItem' and 'greenItem' below visible again after setting their display to 'none'. I'm using a CSS selector to get them visible again when the mouse hovers over a node higher up in the nested list to no avail.
Here's the code:
<ul class="popups" style="vertical-align: bottom; padding: 0px; margin: 0px;">
<li style="width: 165px"><a id="topmostBox" href="#">One_high-up_item</a>
<ul class="popups">
<li>First-lower-item
<ul class="popups">
<li name="redItem" >Red</li>
<li name="blueItem">Blue</li>
<li name="greenItem">Green</li>
</ul>
</li>
</ul>
</li>
</ul>
.popups:hover > li {
display: block;
}
.popups {
background-color: white;
font-family: sans-serif;
font-size: 13.5px;
list-style: none;
position: relative;
border: 1px solid lightgray;
border-width: .05em;
border-top-color: rgb(165,165,165);
line-height: 1.2em;
display: inline-table;
}
function setTopColorAndVis(theNestedPopupAnchor)
{
var theColorName = theNestedPopupAnchor.innerHTML;
var topMenuBox = document.getElementById('topmostBox');
topMenuBox.innerHTML = theColorName ;
theNestedPopupAnchor.parentNode.style.display = "none";
}
What happens is this:
1) I select the color 'Red' (the 1st list item)
2) my call to setTopColorAndVis(this) makes the popup disappear (because the user selected an item, the color "Red", and now the popup is not needed for now)
3) but when I later hover the mouse over the "First-lower-item" list item, the child li that has the ul containing 'redItem', 'greenItem', 'blueItem' does not appear.
So my experience here is that I'm successfully able to hide the list items named 'redItem', 'blueItem' and 'greenItem' -- but when I hover over the "First-lower-item", despite my CSS code:
.popups:hover > li {
display: block;
}
The 'redItem', 'greenItem' and 'blueItem' do NOT reappear.
What am I missing here?
The inline style overrides you style in your css code. you should use onmouseover event and onmouseout instead.
Try
<li name="redItem" >Red</li>
function show(elem){
elem.parentNode.style.display = "block";
}
function hide(elem){
elem.parentNode.style.display = "none";
}
You cannot :hover over an element with display:none as it has no size...
instead of working with display, you can work with visibility - which will leave an area to hover over.
like so:
theNestedPopupAnchor.parentNode.style.visibility = 'hidden'
.popups:hover > li {
visibility: visible;
}
http://www.w3schools.com/cssref/pr_class_visibility.asp

Categories

Resources