Custom Menu collapsing on mobile scroll - javascript

I have read similar questions related to my issue, but have not come across a solution. I am working on a menu created by another developer for a new client of mine. The client does not wish to use a jQuery, which I believe would solve my issue, so I am hoping to find an adjustment to my jQuery to get this bug fixed. I have a 2-level menu, and on a mobile device, the menu collapses upon scrolling. I can not figure out how to prevent this from happening, as it does not occur when I test on my PC even at the same width. The jQuery used is triggered on .mouseenter and .mouseleave events, so I believe it might have something to do with that. Anyone have an idea as to how to prevent the menu from collapsing upon mobile scroll without a plugin?
Below is the jQuery used:
var $navItem = $("#main_nav").children("#nav").children("li").children("a");
var $subItems = $($navItem).siblings("ul");
$(function(){
$($subItems).hide();
var width = $(window).width(), height = $(window).height();
if ((width <= 570))
{
$("#main_nav").children("#nav").hide();
}
else
{
$("#main_nav").children("#nav").show();
}
});
$(window).on('load resize', function(){
var width = $(window).width(), height = $(window).height();
if ((width <= 570))
{
$("#main_nav").children("#nav").hide();
}
else
{
$("#main_nav").children("#nav").show();
}
});
$($navItem).mouseenter(function(e){
if ($(window).width() > 570) {
//show submenu
$(this).siblings("ul").show();
}
});
$($navItem).mouseleave(function(){
if ($(window).width() > 570) {
//hide submenu
$(this).siblings("ul").hide();
}
});
$($subItems).mouseenter(function(){
$(this).closest("ul").show();
$(this).closest("ul").siblings("a").addClass("active");
});
$($subItems).mouseleave(function(){
$(this).closest("ul").hide();
$(this).closest("ul").siblings("a").removeClass("active");
});
$(".drop-down-arrow").click(function(e){
e.preventDefault();
$(this).toggleClass("flip");
$(this).parent().siblings("ul").toggle();
});
$(".drop-down-arrow").each(function(){
if($(this).parent("a").siblings("ul").size() <= 0)
{
$(this).closest('.drop-down-arrow').css('display','none !important;');
}
});
HTML:
<ul id="nav" style="display: none;">
<li>Item 1<span class="drop-down-arrow"><img src="/media/199707/arrow-down.png" alt="drop down arrow"></span>
<ul class="child" style="display: none;">
<li>Transmission Repair</li><li>Brake Repair</li>
<li>Engine Repair</li><li>Air Conditioning</li>
<li>Auto Glass Repair</li><li>Window Tinting</li>
</ul>
</li>
<li>Item 2</li>
</ul>

If I understand your problem correctly, .mouseenter sure can cause this, because a touch/drag on mobile is also recognized as a mouse event in websites.
I see you try to filter mobile devices with checking the window's width. It may be wrong on tablets or phones with bigger resolutions.
Maybe you could try using this method in you mouse events:
What is the best way to detect a mobile device in jQuery?

Related

Code behaves differently depending on screen size

I am experiencing weird behaviour from my jQuery because my laptop screen size/resolution is somewhat smaller than an ordinary PC screen.
What I am trying to do is position a dropdown depending on where it'd fit best: if the event target is too close to the bottom of the window then you show the dropdown above the event target, and vice versa.
As seen here, if too close to the bottom, dropdown appears at the top: http://jsfiddle.net/u7n6tLgo/14/
And here, if too close to the top, dropdown appears at the bottom: http://jsfiddle.net/u7n6tLgo/15/
$(this).on("click", ".selector > *[data-action='open']", function(event) {
const selector = $(this).parent(".selector");
const container = selector.find(".container");
const title = selector.find(".title");
container.toggle();
let windowHeight = $(window).height();
let titleOffsetTop = title.offset().top;
let titleHeight = title.height();
let containerHeight = container.height();
title.toggleClass("selected");
if(windowHeight < (titleOffsetTop + titleHeight + containerHeight) && containerHeight < titleOffsetTop) {
container.css("top", (-containerHeight-titleHeight));
} else {
container.css("top", 50);
}
});
I'm using a 15" Laptop, and for me the dropdown is always at the top, unless I zoom out my browser window to <80% (outside of the fiddle in my private project obviously; in the fiddle it is working as expected). I asked a friend of mine to try the code on his 24" screen and there it works fine.
For some reason the title.offset().top is above 1000, more than my window height, which my friend pointed out as weird. Not sure if that's why it's being weird. HTML and CSS is in the fiddles.
Why is it behaving like this? And more importantly, how do I fix it or work around it?
Edit:
<div class="selector">
<div class="title" data-action="open">Open</div>
<div class="container">
<ul class="itemList">
<li>Hello</li>
<li>Hello</li>
<li>Hello</li>
<li>Hello</li>
<li>Hello</li>
<li>Hello</li>
</ul>
</div>
</div>

Show div on scroll down effect in a page that occupies full height

I have the following structure:
<body>
<div id="first-div">
<div id="second-div"></div>
<div id="third-div"></div>
</div>
</body>
#first-div {
height: 100%;
}
#second-div, #third-div {
display: none;
}
Upon detecting the first scroll down event I need the #second-div to be displayed, If a second scroll down event is detected, I need #third-div to be displayed and #second-div to be hidden. My problem is: a single scroll down on the touchpad might trigger multiple events thus showing the #second-div and #third-div immediately.
$(body).on('DOMMouseScroll mousewheel', function (event) {
if (event.originalEvent.detail > 0 || event.originalEvent.wheelDelta < 0) {
//scroll down
console.log('Down');
} else {
//scroll up
console.log('Up');
}
//prevent page fom scrolling
return false;
});
How Can I detect the scroll down event, stop the scroll event and show #second-div. Then detect another scroll down event, stop the scroll event and hide #second-div and show #third-div?
Also, Afterwards, I detect the scroll up event, stop the scroll event and hide #third-div and show #second-div. Then detect another scroll up event, stop the scroll event and hide #second-div?
Thank you.
This snippet uses the scroll method of JQuery
(there's a similar question: Differentiate between scroll up/down in jquery?)
$(window).scroll(function(e) {
var target = e.currentTarget,
$self = $(target),
scrollTop = window.pageYOffset || target.scrollTop,
lastScrollTop = $self.data("lastScrollTop") || 0,
scrollHeight = target.scrollHeight || document.body.scrollHeight,
scrollText = "";
if (scrollTop > lastScrollTop) {
scrollText = "<b>scroll down</b>";
} else {
scrollText = "<b>scroll up</b>";
}
$(".test").html(scrollText +
"<br>innerHeight: " + $self.innerHeight() +
"<br>scrollHeight: " + scrollHeight +
"<br>scrollTop: " + scrollTop +
"<br>lastScrollTop: " + lastScrollTop +
"<br>" + scrollText);
if (scrollHeight - scrollTop === $self.innerHeight()) {
console.log("► End of " + scrollText);
}
//saves the current scrollTop
$self.data("lastScrollTop", scrollTop);
});
#first-div {
height: 100%;
}
#second-div,
#third-div {
display: none;
}
.test {
margin: 200px auto 200px auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="first-div">
<div class="test">
scroll info</div>
</div>
<div id="second-div">
<div class="test"></div>
</div>
<div id="third-div">
<div class="test"></div>
</div>
I would encourage you to use fullPage.js to save time and have a much better result.
Do not reinvent the wheel.
It is currently the most used plugin for this kind of sites.
Works in modern and old browsers, touch devices and has a good respond to kinetic scrolling in Apple laptops.
It deals with URL hashes, returning links (anchor links), resizing...
Plenty of configurable options, methods and callbacks.
Highly tested in different devices and browsers by thousands of persons (Windows Phone, Adroid, iOS, touch screen computers, Opera, Safari...)
Compatibility with touch devices.
Compatibility with kinetic scrolling (Apple laptops, magic mouse...).
Compatibility with old browsers (IE 8, Opera 12...).
Good performance for modern browsers and touch devices (css3).
Recalculate of sections and slides when resizing the viewport.
Returning anchors in the URL.
Responsive mode.
Accessibility features (keyboard, scroll bar, browser history).
Use of callbacks to fire actions.
Plenty of methods and options.
And you get all of that for just 7Kb gzipped if you use fullPage.js.

jQuery - on window scroll run a function without any delay

I have an issue with a jquery function. You can see a working demo here - http://dev.sreejesh.in/menuissue/ . As you can see when the user scrolls down to the page, I have written a jQuery function(which will triger on scroll) to check scroll pixel. When the browser scrolls to a certain pixel(height of the sidemenu block), the Menu block will stay fixed & rest of the content scrolls as normal.
The functionality is working now, however the problem is menublocks makes a jumps when this function runs. I think this is because of the delay in running the function. Hope you guys have any nice trick to fix this.
I used an if/else function to check the scroll pixel, so when the scrolled pixel is greater than menublock height it will add a class "fixed" .
I use the following code.
HTML
<div id="globalwrapper">
<div id="menubar">
---- Menu List items-----
</div>
<div id="mainblock">
----Main content area----
</div>
</div>
jQuery
$(document).ready(function(){
$(window).scroll(function() {
adjustScroll();
});
});
function adjustScroll(){
var windowHeight = $(window).height();
var menublockHeight = $('#menubar').height();
var scrollValue = $(document).scrollTop();
var posValue = menublockHeight - windowHeight;
var menuStatus = $('#menubar').css('left');
$('#menubar').css('minHeight', windowHeight);
$('#menubar').css('height', menublockHeight);
console.log(menuStatus);
$(document).scroll(function() {
if(menuStatus == '0px') {
if(scrollValue > posValue){
$('#menubar').addClass('fixed');
$('#menubar').css('marginTop', -posValue);
}else {
$('#menubar').removeClass('fixed');
$('#menubar').css('marginTop', '0px');
}
}
});
}
I think only CSS can solve this issue, add this style:
#menubar{
position: fixed;
}
just test on Google Chrome,you can have a try.

Remove element at specific screen width with jquery?

I'm building a responsive site with a mobile first approach where i need to add a html element when the screen is larger than 641px and remove when less. The problem i'm having is when i resize my screen larger than 641px the code produces infinite numbers of said element and when i reduce the screen size there is masses amounts of space when they are removed.
my code looks like this:
<script>
$(function () {
$(window).resize(function () {
if ($(window).width() > 641) {
$('.project_nav').append('<li><a class="work_grid" href="#"><img src="images/noun_project_5193.svg"/> </a></li>');
} else {
$('.work_grid').remove();
}
});
});
</script>
And here's the html i want to append:
<div class="project_nav">
<ul>
<li><a class="up_arrow" href="#"><img src="images/noun_project_6978.svg"/> </a></li>
<li><a class="prev_arrow" href="#"><img src="images/noun_project_6976.svg"/> </a></li>
<li><a class="next_arrow" href="#"><img src="images/noun_project_6977.svg"/> </a></li>
</ul>
</div>
Any help is greatly appreciated!
You need to keep track of if the element has already been added or removed. Easiest way without pounding the dom is to store this in a variable:
$(function () {
var isAdded = false;
$(window).resize(function () {
if (!isAdded && $(window).width() > 641) {
isAdded = true;
$('.project_nav').append('<li class="work_grid_container"><a class="work_grid" href="#"><img src="images/noun_project_5193.svg"/> </a></li>');
} else if (isAdded && $(window).width() <= 641) {
isAdded = false;
$('.work_grid_container').remove();
}
});
});
This way it will only add the element if it hasn't already been added and it will remove it only if it currently exists on the page.
Note: You need to have a selector for the li instead of the a! Otherwise you will keep adding lis. You need to fully remove what you added.
What is happening is that when you resize, you are actually resizing a whole bunch, adding way too many elements. What you want to do is add some sort of Timeout. Here is an example.
$(window).resize(function () {
clearTimeout(window.refresh_size);
window.refresh_size = setTimeout(function () {
//Do stuff
}, 100);
This way, it will only do your code after you have stopped resizing the page for a least 100 ms.

Positioning jQuery dropdown menu so that it displays completely on the screen on a mobile device

I have a basic jQuery dropdown menu and am testing it on mobile devices, specifically phones.
One issue I've encountered is that submenus on the right side sometimes go off the edge of the screen, because the text in one or more of the items is too long. The user can, of course, scroll slightly to the right, but that's not a great solution. There is a similar question here, but I couldn't get the proposed answers to work (plus I'm fumbling around since I'm still totally new to JS/jQuery).
The general idea seems to be to get the left position+width of the current submenu (making sure they are shown first, then hide them again or show them and shift them when the user taps the menu) and see if that value is greater than the screen width. If it is, then it should be shifted to the left by the difference. However, my attempts don't seem to do anything to the elements and the menus keep appearing partially off-screen. Currently, I have this (runs after the body loads):
var menus = $('#navbar').find('.subMenu');
menus.show();
menus.each(function(index) {
var offsetMenu = $(this).offset();
var diff = screen.width - ($(this).outerWidth + offsetMenu.left);
if(diff < 0) {
$(this).offset({top:offsetMenu.top, left:offsetMenu + diff});
}
});
menus.hide();
The jQuery version is 1.7.2 and the HTML structure is as follows (simplified version):
<div id="navbar">
<ul>
<li class="dropdown">Link A</li>
<li class="dropdown">Link B
<ul class="subMenu">
<li>Link 1</li>
<li>Link 2</li>
</ul>
</li>
</ul>
</div>
How can I get this to work?
Edit: Here's the relevant CSS, too.
#navbar ul.subMenu {
position:absolute;
display:none;
background-color:#FFAF1E;
}
#navbar ul.subMenu li {
clear:both;
}
#navbar a {
text-decoration:none;
color:#0C1F6E;
display:block;
padding-right:10px;
padding-left:10px;
}
I finally got the following to work:
function adjustSubMenus()
{
var menus = $('#navbar').find('.subMenu');
menus.each(function(){
$(this).css("left","");
adjustSingleMenu($(this))
} );
}
function adjustSingleMenu(element)
{
element.show();
var thisMenuWidth = element.outerWidth();
var thisMenuPos = element.offset();
var diff = (thisMenuWidth + thisMenuPos.left) - window.outerWidth;
if(diff > 0) {
element.css("left", function() { return thisMenuPos.left - diff; });
}
element.hide();
}
$('body').ready(function() {
$(window).resize(adjustSubMenus());
if(window.outerWidth <= 720) {
$('html').mousedown(function() {
$('#navbar').find('.subMenu').each(function(index) {
if($(this).is(":visible") ) {
$(this).css("left","");
$(this).hide();
}
} );
});
$('#navbar').mousedown(function(event) {
event.stopPropagation();
});
$('.dropdown').mousedown(function() {
var ele = $(this).find('.subMenu');
$('#navbar').find('.subMenu').each(function(index) {
if(!$(this).is(ele) && $(this).is(":visible") ) {
$(this).css("left","");
$(this).hide();
}
} );
if(ele.is(":visible")) {
ele.css("left","");
ele.hide();
}
else {
adjustSingleMenu(ele);
ele.show();
}
});
}
else
{
$('.dropdown').hover(
function() { $(this).find('.subMenu').stop(true,true).slideDown("fast"); },
function() { $(this).find('.subMenu').stop(true,true).hide(); }
);
}
});
The only minor caveat is that if you have a menu open as you change the orientation of the phone, the menu may appear off the screen. Closing and reopening the menu will fix this issue. Everything else works perfectly.
You could try creating CSS that works just for the mobile (tied to a CSS class name), and then upon detection of either a narrow screen or a mobile device, add the CSS class to your elements.
Javascript:
if (is_mobile)
{
$(".subMenu").addClass('mobile');
}
CSS:
/* CSS */
.mobile { width:100%; left:0 !important; position:absolute; }
shouldn't the function check the width of the submenu plus its offset left and then if this is greater than the screenwidth then set the left value at 0 and its width as the screen width?
e.g.
var menus = $('#navbar').find('.subMenu'),
screenWidth = $(window).width();
//screenWidth = screen.width; //I assume this calculates the screens width properly
menus.show();
menus.each(function(index) {
var thisMenu = $(this),
thisMenuWidth = thisMenu.outerWidth(),
thisMenuLeft = thisMenu.offset().left;
if(screenWidth < (thisMenuWidth + thisMenuLeft)) {
thisMenu
.width(screenWidth)
.css({ //could replace this with .addClass("yourClass") and add this styling to that "yourClass" instead
"left": "0",
"position": "absolute"
});
}
});
menus.hide();
Update:
I assumed screen.width was some special calculation you had already devised for a mobile device. i have changed this line now. the above should work. i see you already have a solution although that doesnt fix the width of the menu. if the menu is wider than the screen the above will fix its width.

Categories

Resources