I'm building a menu which, on hover of a unique-link will open another menu with links (mega menu).
The issue I'm having is that, on hover of said unique-link anchor, there's a gap between the anchor and the div shown on hover.
To explain, here's a demo:
$(function() {
var delay=3000, setTimeoutConst;
$(".unique-link a").on("mouseenter", function() {
setTimeoutConst = setTimeout(function() {
$('.showOnHover').addClass('show');
});
}).on("mouseleave", function() {
$('.showOnHover').removeClass('show');
});
});
.menu {
height: 100px;
background: lightgrey;
}
.menu .menu__options {
display: flex;
align-items: center;
height: 100%;
}
.menu .menu__options .item {
width: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.menu .menu__options .item.a {
background: green;
}
.menu .menu__options .item.b {
background: red;
}
.showOnHover{
display: none;
height: 200px;
background:blue;
}
.show{
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="menu">
<div class="menu__options">
<div class="item unique-link a">
Hover me
</div>
<div class="item b">
Link
</div>
</div>
</div>
<div class="showOnHover"></div>
In the demo you can see, when you hover over the "hover me" link and try to move your mouse onto the blue showOnHover div, the showOnHover disappears (which is correct, I only want the div to show on that anchor links over).
To work around this, my play was to start a timer once a user overs over the unique-link anchor. The way I'm planning this to work is the following:
User hovers over "hover me" link.
showOnHover div appears (at this point, the user is still hovering over the anchor)
If a user hovers over the "hover me" link and then moves their mouse onto the grey area (anywhere on menu__options), then start the timer a timer. The showOnHover div will be visible until this timer reaches 3 seconds.
If a user hovers over another a tag, then reset the timer (and don't show the showOnHover div).
Hopefully that all made sense. I've also demo'd my current approach in the snippet above. But cannot get the logic to work.
What i would suggest is to use a debounce method
lodash debounce or maybe Ben Alman jQuery throttle / debounce whichever you prefer.
So having a debounce method (i'll assume lodash), you can implement it like this:
var debouncedSetMenuVisible = _.debounce(function(show) {
if(show)
$('.showOnHover').addClass('show');
else
$('.showOnHover').removeClass('show');
}, 500);
$(".unique-link a, .showOnHover").on("mouseenter", function() {
debouncedSetMenuVisible(true);
}).on("mouseleave", function() {
debouncedSetMenuVisible(false);
});
This will ensure that when multiple events are happening, only the last one will be executed. Also it ensures that this will happen after the delay you specify.
You can use the debounced function only for mouseleave, but i would suggest you use it for mouseenter also as it is better UX (avoid showing mega menu for accidental hovers).
edit (one more thing): showOnHover needs also a mouseenter and a mouseleave event calling the same function (updated code).
Related
What I am trying to do:
I have a card that when hovered over expands and does a bunch of different animations i worked on. this is great for desktop and not mobile. So when the screen is a certain res I make a toggle button visible. I want this button in react to when clicked on enable the save:hover State its grabbing from my css.
The Problem
The button is inside the card aleady and everything in the css is setup for the parent div card.
my code structure simplified
<div className="card">
<-- CODE HERE -->
<Button className="myToggleButton" />
<div/>
I cant from what i can tell in css exclusively say myToggleButton:focus do these changes to the following classes since they are parent ones. So i think my only other way to do that is by telling it in react somehow to say that my div is in :hover state, but I can't quite figure out how despite my efforts. Thank you in advance for any help on this.
Instead of activating your animation with the :hover pseudo class, you can simply add or toggle another custom class, which would contain the values to transition or keyframes to animate, to the card element when the button is clicked. In this example I just use a transition, but you could also employ a more complex keyframe animation.
const card = document.querySelector(".card");
const button = document.querySelector(".card > button");
const animate = () => {
card.classList.toggle("small");
card.classList.toggle("big");
}
button.addEventListener("click", animate);
.card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
transition: all 250ms ease-in-out;
}
.small {
width: 200px;
height: 50px;
background-color: gray;
}
.big {
width: 400px;
height: 150px;
background-color: blue;
}
<div class="card small">
My Amazing Card
<button>Animate</button>
</div>
I have a portfolio grid of images and when a user hovers or taps on a mobile a transparent overlay with some text and a button appears
I am using the on click function
It works fine on my touch screen laptop but not on my iOS phone or tablet
The overlay appears on first tap, but when I tap again it does not disappear unless I tap another grid image.
I would like it to disappear on 2nd tap
I have tried various ways of making this work, and the closest I have got it for it to disappear when another grid image is tapped
Here is my code:
HTML
<div class="col-xs-12 col-sm-7"><div class="image-wrap">
<div onclick="on()">
<img src="assets/images/pic.jpg">
<div class="overlay blue">
<h3>Portfolio item 1</h3>
<hr>
<p><strong>Coming Soon</strong><br> some overlay text here</p>
<br>
View Website
</div>
</div>
</div>
</div>
JS
function on() {
document.getElementById("overlay").style.display = "block";
}
function off() {
document.getElementById("overlay").style.display = "none";
}
CSS
.image-wrap {
display: inline-block;
position: relative;
}
.overlay {
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
color:white;
opacity: 0;
transition:opacity .5s ease-out;
text-align: center;
hr {
border: 1px solid #fff;
width: 10%;
}
}
.image-wrap:hover .overlay {
opacity: 1;
}
.red {
background: rgba(102,67,154,0.7);
}
.blue {
background: rgba(23,56,179,0.7);
}
.purple1 {
background: rgba(140,23,179,0.7);
}
.purple2 {
background: rgba(71,13,142,0.7);
}
}
I initially tried this with just CSS which gave me the desired result on all devices apart from iOS!
So I have decided to use the on click function to be more sure it works on all devices. I added the on click function to my existing code which I wrote to be used with CSS, but as I am rather new to JS I am wondering if I have it in the wrong place (the on-click)? I have tried lots of variations but this is the best I can get it to work
Any ideas of suggestions on how I can make the overlay disappear on the 2nd click would be great!
js fiddle
https://jsfiddle.net/49h450g9/14/
Please note: This works fine on touch-screen laptops, just not mobiles!
Thanks!
Your functions on and off on jsfiddle example are not working at all. What happening is your hover effect on normal screen which as the behavior of mobile work like focus on mobile device.
Moreover, from your description here I believe that you have more than one portfolio on your project. So you have several element with the id overlay and multiple use of same id is not validate for html and also will cause JavaScript error.
To let your project work properly follow my list below:
Make sure you have jQuery added on your project (generally before </body>)
Now let us thinks of these portfolio item below
<div class="portfolio">
<img src="images/portfolio-1.jpg" alt="...">
<div class="overlay">Link</div>
</div>
<div class="portfolio">
<img src="images/portfolio-2.jpg" alt="...">
<div class="overlay">Link</div>
</div>
<div class="portfolio">
<img src="images/portfolio-3.jpg" alt="...">
<div class="overlay">Link</div>
</div>
Then give the normal hover css styles inside media query like this. So that it never effect your js styles (I decide medias less than 992px as mobile device):
.portfolio{
background-color: #f1f1f1;
position: relative;
}
.portfolio .overlay{
background-color: rgba(0, 0, 0, 0.7);
display: block;
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: all 0.3s ease;
width: 100%;
height: 100%;
}
#media all and (min-width:992px){
.portfolio:hover .overlay{
opacity: 1;
}
}
Now with jQuery you can use event while user click any of the .portfolio item and toggle a class on it by which we will add further css to it:
$(document).ready(function(){
'use strict';
$(.portfolio).on('click', function(){
$(this).siblings('.portfolio').removeClass('hovered');
$(this).toggleClass('hovered');
});
});
Now it will add hovered class on 1st click and remove the hovered class on 2nd click. Also it will remove .hovered from other portfolio items. Now add the same css to it as the hover effect:
.portfolio.hovered .overlay{
opacity: 1;
}
Try this:
$("*").on("click, touchend", function(e) { $(this).focus(); });
or to achieve the opposite;
$("*").on("click touchend", function(e) { $(this).hover(); });
However the hover event doesn't work well on ios or other mobiles.
Another suggestion is to try replace any css using
:hover with :active.
Red square is the part of a container with class "parent". If I hover mouse over that red square it disappears. But why? I expected that it shouldn't.
Expected behaviour: it does not disappear since red square is a part of ".parent" container and I have clearly stated, that the mouseout event occurs on that container.
There was a suggestion, that this question is a duplicate of
JavaScript mouseover/mouseout issue with child element
In some way - yes, but I think that this question provides value, because it not only provides the solution ("you can try this"), but also explains WHY you should use that and WHY the initial solution is not working as it is supposed to.
<span class="parent">Hover mouse over this text<br></span>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
function removeSquare()
{
$(this).find(".kvadrat").remove();
}
function addSquare()
{
$(this).append("<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>");
$(this).on("mouseout", removeSquare);
}
$(".parent").on("mouseover", addSquare);
</script>
It's normal behaviour of .mouseout() event.
Show the number of times mouseout and mouseleave events are triggered.
mouseout fires when the pointer moves out of the child element as
well, while mouseleave fires only when the pointer moves out of the
bound element.
You should use .mouseenter() and .mouseleave() events,
function removeSquare()
{
$(this).find(".kvadrat").remove();
}
function addSquare()
{
$(this).append ( "<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>" );
}
$ ( ".parent" ).on ( "mouseenter", addSquare );
$(".parent").on("mouseleave", removeSquare);
.parent {
display: inline-block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="parent">Hover mouse over this text<br></span>
As other people have noted, your original problem is that mouseover and mouseout events also fire for child elements. The solution to that issue is either to use jQuery's mouseenter and mouseleave events, or simply to replace the JS code with the CSS :hover pseudo-class.
However, the reason why the other JS and CSS solutions posted here sometimes behave erratically (causing the square to disappear if you move the mouse over it slowly, but not if you move it fast, and not on all browsers even if you move it slowly) is because, depending on your browser and font settings, there may or may not be a small gap between the top line of text and the square below it. If the gap exists, and your mouse cursor hits it while moving from the text to the square, the browser will consider the mouse to have left the parent element, and will thus hide the square.
Setting a (light blue) background color on the parent element shows the issue clearly; depending on what font and line height the browser chooses, the parent element and the box can look like this:
or like this:
Manually setting a particularly large line height makes the problem easily reproducible (CSS example based on Thomas van Broekhoven's answer):
.kvadrat {
display: none;
}
.parent:hover > .kvadrat {
display: inline-block;
background-color: red;
width: 50px; height: 50px;
}
.parent {
line-height: 2.0;
background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'></span></span>
There are two general ways to fix this issue. The simplest option, where practical, is to make the parent element a block, thereby eliminating the gaps between the lines. You may also wish to add position: absolute to the square's style, so that it won't expand its parent element when it appears:
.kvadrat {
display: none;
}
.parent:hover > .kvadrat {
display: inline-block;
position: absolute;
background-color: red;
width: 50px; height: 50px;
}
.parent {
display: block;
line-height: 2.0;
background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'></span></span>
Alternatively, if you really want to stick with an inline parent element (e.g. because you want it to be able to wrap across several lines of text), you can set a negative top margin on the square to make sure it overlaps the line of text above it. If you don't want the square to visibly overlap the text, you can further move all the visible content of the square into an inner element and set a corresponding positive top margin on it, like this:
.kvadrat {
display: none;
}
.parent:hover > .kvadrat {
display: inline-block;
position: absolute;
margin-top: -1em;
border: 1px dashed gray; /* to show the extent of this otherwise invisible element */
}
.kvadrat > .inner {
display: block;
margin-top: 1em;
background-color: red;
width: 50px; height: 50px;
}
.parent {
line-height: 2.0;
background: lightblue;
}
<span class="parent">Hover mouse over this text!<br>
Here's another line of text.<br>
<span class='kvadrat'><span class='inner'></span></span></span>
I know this is not directly answering your JavaScript question, but I would like to open your eyes if you're not bounded to JavaScript. You can easily achieve this with CSS.
.kvadrat {
display: none:
}
.parent:hover > .kvadrat {
display: inline-block;
background-color: red;
width: 50px;height: 50px;
}
<span class="parent">Hover mouse over this text<br>
<span class='kvadrat'></span></span>
You can achieve the same using CSS.
.child {
display: none:
}
.parent:hover > .child {
display: inline-block;
background-color: red;
width: 50px;
height: 50px;
}
<span class="parent">Hover mouse over this text<br>
<span class='child'></span>
</span>
It is because of event bubbling. When you enter the child span, you jQuery will fire mouseout because you've now gone to a child span. If you want to keep it going, use mouseenter and louseleave which does not fire until you leave the actual element, regardless of child elements.
<span class="parent">Hover mouse over this text<br></span>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
function removeSquare()
{
$(this).find(".kvadrat").remove();
}
function addSquare()
{
$(this).append ( "<span style='display:inline-block;width: 50px;height: 50px;background-color:red' class='kvadrat'></span>" );
$(this).on("mouseleave", removeSquare);
}
$ ( ".parent" ).on ( "mouseenter", addSquare );
</script>
What I want to do is to dismiss the sidebar when I press anywhere on the screen.
How can it be done? How can I make it so that when I hover over my image, my text would appear instead of it being visible the whole time?
function openNav() {
document.getElementById("mySidenav").style.width = "300px";
document.getElementById("toggle").style.position = "static";
}
function closeNav() {
document.getElementById("mySidenav").style.width = "0";
document.getElementById("toggle").style.marginRight = "0";
document.getElementById("toggle").style.position = "relative";
}
#toggle {
position: relative;
left: 400px;
font-size: 1.2em;
visibility: visible;
}
#toggle:hover {
color: white;
transition: 0.5s;
}
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
right: 0;
background-color: transparent;
overflow-x: hidden;
transition: 0.5s;
padding-top: 50px;
}
.sidenav a {
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s;
}
.sidenav a:hover,
.offcanvas a:focus {
color: #f1f1f1;
}
.sidenav .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
<div>
<ul id="topBar">
<li id="ixora">Ixora</li>
<li class="lists">2014</li>
<li class="lists">2015</li>
<li id="toggle" onclick="openNav() ">☰</li>
</ul>
</div>
<div id="mySidenav" class="sidenav">
×
<span class="textImage">Outing</span>
<img src="outing.jpg">
<span class="textImage">Prom Night</span>
<img src="prom.jpg">
<span class="textImage">PortDickson Trip</span>
<img src="pd.jpg">
<span class="textImage">Merdeka</span>
<img src="merdeka%20(2).jpg">
</div>
I honestly think you should be using jQuery here as it's fantastic for DOM manipulation. If for some reason you have to use vanilla js then it's going to be a little trickier.
Adding jQuery to your project is easy and well explained on the jQuery site. Here;s a comparison of selecting an element in js/jquery:
Vanilla JS:
document.getElementById("mySidenav")
jQuery:
$('#mySidenav')
To get things to happen when events happen (i.e. button press, or clicking away from a menu) you set up event handlers.
It's hard to picture from your code as there seems to be CSS missing for #topBar so I can't see your site very well (here's your code running in jsfiddle).
But lets say you have a button with ID of #openToggle. You would set up your sideNav in css with the correct width, height, etc, and leave it as display: none. Then we create an event handler to do something when you click that button:
//event handlers go at the bottom of your js file or script tag
$('#openMenu').on('click', openMenu);
That example is basically saying when the element with ID 'openMenu' is 'clicked' the run the 'openMenu' function - simple! :)
The openMenu function would look something like (basic example):
function openMenu() {
var $menu = $('#sideNav');
$menu.show();
};
A better way would be to have a toggle function that toggles the menu based on whether it's already open or closed:
function toggleMenu() {
var $menu = $('#sideNav');
if (($menu).is(':visible')) { //if it's visible
$menu.hide(); //hide the menu
} else { //else it's hidden
$menu.show(); //so show it
}
};
// event handler for menu toggle button
$('#menuToggle').on('click', toggleMenu);
With regards to closing the menu when you click away from it, you could bind an event handler to the body of the page and use jQuery's .one() function (runs only once) which will detect if the body is clicked and then run the menuToggle funtion - you'd end up with 2 handlers for this:
// event handler for menu toggle button
$('#menuToggle').on('click', toggleMenu);
$('body').one('click', toggleMenu);
Alternatively you could have the menu close when your mouse pointer leaves the menu?:
//event handlers
$('#menuToggle').on('click', toggleMenu);
$('#sideNav').mouseleave(toggleMenu);
The mouseleave handler is basically saying, once the mouse pointer leaves the element with ID of 'sideNav' then run the toggleMenu function.
I'm a newb too so my examples may not be great, but I hope I helped at least a little. Hopefully some real javascript devs will be along shortly to add to this or give better examples.
Cheers,
Dave
JQuery
$( document ).onclick(function() {
$("#mySidenav").hide();
}
Reference documentation:
JQuery .on
JQuery .hide
With javascript, you can do the following:
document.onclick = function(e){
if(e.target.id == "mySidenav"){
return false;
}
closeNav();
}
I see that you already have functions to open and close your sidebar,
clicking "anywhere" is the same as "clicking on the body", But I guess that you don't want your sidebar to close when you're clicking on it.
So, here's what you can do :
var myNav = document.getElementById("mySidenav");
myNav.onclick = function(e) {
e.stopPropagation();
}
document.body.onclick = function(e) {
closeNav();
}
So, when you click on your sidebar, you won't click on the body because the event propagation is stopped, and when you click elsewhere, it will close your sidebar.
Hope it helps,
best regards
So I've been working on this for a while, I've tried many examples found here on stackoverflow, and then starting reading up on js/jquery for most of the day, but am still having trouble with this.
Basically I was able to create a hidden menu item that slides on to screen when clicked, and I was able to change the button used to open in by toggling the class, and send it back.
But after the first time through that process when I try to open it again, it opens and then closes automatically.
I built a jsfiddle, but have done it wrong as it's not working the same as on my site.
fiddle: http://jsfiddle.net/VpnrV/1/
site: http://ericbrockmanwebsites.com/dev4/
code - html:
<div id="dashboard">
<nav id="access">
<div class="open"></div>Here's a bunch of content representing the nav bar.
</nav>
</div> <!-- dashboard -->
css:
#dashboard {
font-size:30px;
float:left;
position: absolute;
right: -653px;
z-index: 100;
}
#access {
display: block;
float: right;
margin: 10px auto 0;
width:730px;
}
.open {
background: #cabd32 url(images/open.png) top left no-repeat;
float:left;
padding:13px 30px 16px;
}
.close {
background: #cabd32 url(images/close.png) top left no-repeat;
float:left;
padding:13px 30px 16px;
}
my laughable js:
$(document).ready(function() {
$('.open').click(function() {
$('#dashboard').animate({ right: '0' });
$(this).toggleClass('close');
$('.close').click(function() {
$('#dashboard').animate({right: '-653'});
}
);
});
Any help is greatly appreciated!
You are actually binding the class .close to multiple callback every time you click the button.
you can try this:
$('.open').bind('click',function(){
$('#dashboard').stop().animate(
{
right: $(this).hasClass('close') ? '-653px' : '-400px'
});
$(this).toggleClass('close');
});
Having stop() infront of animate() will cancel the current animation and do the next animation without queueing multiple animations up.
You can actually do this in one click event. All this does is every time you click open it looks for the close class, if it's there, close the menu, if not open it. Then toggle the close class:
$(document).ready(function () {
$('.open').on("click", function () {
if ($(this).hasClass('close')) {
$('#dashboard').animate({
right: '-653'
});
} else {
$('#dashboard').animate({
right: '0'
});
}
$(this).toggleClass('close');
});
});
I don't know if this is the most effecient but it works for your example. I updated it as well: http://jsfiddle.net/VpnrV/3/