Cause of sluggish performance in JavaScript collapsing-menu script? - javascript

I have a script that collapses my site navigation on small screens, based on Brad Front's example here: http://codepen.io/bradfrost/full/sHvaz
All the script does is toggle a class on click, and then the CSS uses a transition on the max-height to hide & reveal the menu.
On one of my sites, the script runs just fine, but on others there is a pronounced delay after the "hide menu" click before the CSS transition begins.
The script that functions as intended is here: http://archstglassinc.com/
(the script comes into play when the browser window is narrower than 768px).
Here's the un-minified script:
var $menu = jQuery('#menu-main-nav'),
$menulink = jQuery('.menu-link');
$menulink.click(function() {
$menulink.toggleClass('active');
$menu.toggleClass('active');
return false;
});
Whereas the script that lags is here: https://www.talleyreedinsurance.com/
(Please Note: in order to access the collapsed menu on this site, you need to load or re-load the page when your browser window is narrower than 768px). The first click reveals the menu just fine, but there's a delay after the second click before the menu starts to go away.
This script, un-minified, is a bit longer because it enables some drop-downs that will come in a future phase, but the relevant portion is identical except that it uses preventDefault instead of return false to stop the link:
var $menu = $('#menu-partial-nav-for-launch'),
$menulink = $('.menu-link'),
$menuTrigger = $('.family-link > a, .business-link > a');
$menulink.click(function(e) {
e.preventDefault();
$menulink.toggleClass('active');
$menu.toggleClass('active');
});
$menuTrigger.click(function(e) {
e.preventDefault();
var $this = $(this);
$this.toggleClass('active').next('ul').toggleClass('active');
});
It's probably irrelevant to give you this code, though. I can tell that the class toggle does happen immediately, because the icon next to the "Site Menu" link text switches right away, and that relies on the toggled class.
But then there's a delay of half a second or more before the max-height transition begins.
Can anyone determine (or even speculate upon) what would cause this sort of pause in the transition?

The issue isn't with your javascript but your css.
You set your active max-height to 100em.
However, you don't need this entire height.
The browser is transitioning the entire max-height from 100 back to 0. The first 89ems of the transition doesn't appear do anything, because the content is only roughly 11em tall. In your other example, you set max-height to 15em

Related

Setting an element's focus only if user has tabbed to it

I am trying to make some collapsible accordion containers on my website accessible, but I am running into an issue.
The accordions are controlled by link elements on the page - this way, a keyboard-only user can tab to them and access them. The first issue I ran into was that if a user tabbed to one of the links, the page wouldn't always scroll up to show them which one they had tabbed to. I fixed this issue setting the focus using the following code, which scrolls the link to the top of the viewport:
$(".accordion .accordion-item .accordion-heading a").focus(
function()
{
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top
}, 250);
}
);
The problem I am encountering now is that when a mouse-user clicks on the link, it jumps to the top of the page and does not open the container unless the mouse-user clicks the link again.
Is there a way I can set the focus code above to only fire if the link has been tabbed to? Or, is there a better way of handling the focus issue so that it works for both keyboard-only and mouse users?
Thanks!
Firstly a quick apology, having now seen your accordion is built correctly, links with in-page anchors are actually preferable if the accordion is constructed using javascript on page load and falls back to just a list of in page anchor links and content between them.
I am that used to seeing <a href="#"> on accordion openers and weird accordion implementations I jumped to conclusions, change it back from <buttons>!
Fixing your problem
Probably not the answer you are looking for but remove the .focus() function entirely.
It produces strange behaviour where if I have one accordion item open and i tab back with Alt + Tab quickly scrolling can be really confusing as it jumps around if you tab quicker than the scroll.
One of the golden rules of accessibility is to only adjusted the scroll position on a page if it is expected (i.e. a return to top button or using in-page anchors).
In the example and on your website once I disabled the 'scroll to top on focus' the site actually behaved as expected.
I understand why you did it as occasionally a link that is focused appears off the page, however this remedies itself when you tab again or by scrolling down (your site is logical so that if I tab and my focus is not visible I know it is off the page.)
This tends to happen (items not scrolling into view) when the item is just out of sight, by a px or two, it is common and ironically now falls into 'expected' behaviour (another rule, follow accepted and expected behaviour when designing components and pages).
If you really want to fix it
In your focus function instead of just scrolling to the top of the page whenever an item is focused, check if it is off the page.
Below is an example function I found (not tested) that you can use to check if the item is in the viewport, if it is then don't do anything, if it isn't then do your scroll function.
var isInViewport = function (elem) {
var bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
So roughly (yet again haven't tested that the correct items are passed in, this is just to give you an idea).
$(".accordion .accordion-item .accordion-heading a").focus(
function()
{
if(isInViewport(this) === false){
//item is not in the viewport so scroll it into view
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top //I would perhaps add a couple of hundred pixels here to make the item appear in a more natural area.
}, 250); //remove the animation as a further accessibility improvement, animations can be off putting to people with motion or anxiety disorders.
}
}
);
This fixes your problem as no mouse user will ever be able to click an item that is off the page so they won't ever trigger the scroll event that causes the focus issue.
You can change the event setting: Instead focus() event you will do a click() event: When you click a link, you'll scroll up. This will solve the problem of both keyboard navigation and mouse clicking;And this is also more true in terms of accessibility.
$(".accordion .accordion-item .accordion-heading a").click(
function(e)
{
e.preventDefault();
$('html:not(:animated), body:not(:animated)').animate({
scrollTop: $(this).offset().top
}, 250);
}
);
Don't forget to change the link setting to a button by adding role=button attribute.
and add aria-expanded attribute.

Javascript focusing a large div with fixed header

I'm using a tiny library called '$.scrollTo' to animate a scroll to a div element in my html. at the top of my page I have a fixed navgation bar.
at the end of the animation, I would like to have that div focused (for accessibility). if my div is to large, at the end of the animation, the fact that it gets focus - simply sends it a bit off the screen.
This does not happen with small divs.
here is my code (check jsfiddle below):
$('#buttonid').on("click", function() {
//fixed nav bar height (to compensate when scrolling)
var fixed_navbar_height = $("#navbar-id").height();
//the element to scroll to
var $go_to_selector = $("#topic2");
$.scrollTo($go_to_selector, {
duration: 1000,
offset: -fixed_navbar_height,
onAfter: function() {
//if you comment out this .focus it works as intended.
$go_to_selector.focus();
}
});
});
here is a JSFIDDLE example:
https://jsfiddle.net/dy35obpq/3/
obviously the onAfter messes it up, but i would like both the animation and the focus. Any ideas on how to implement a focus on a large div without letting it change the scroll bar ? suggestions are more than welcome.
Try this.
onAfter: function() {
$go_to_selector.focus();
$(window).scrollTop($($go_to_selector).offset().top - fixed_navbar_height);
}
I have simply added this line in your onAfter callback.
$(window).scrollTop($($go_to_selector).offset().top - fixed_navbar_height);
and it seems to have fixed the problem while still retaining focus. You might want to use css to disable the focus blue highlight.

BUG with href containing Anchor #id - hits top of page for split second before jumping to Anchor

I have a bug in my site and I’m having trouble finding an elegant solution. I bet there’s a simple way to fix this .. I’m just not seeing it. I would appreciate any suggestions.
example: http://robbroadwell.com/portfolio/ios-apps/rainylectures/
The detail pages on my portfolio have a main nav and then below that a sub nav. If the user has scrolled down below the main nav where it’s out of sight, I’m using jQuery to append the href of the left/right arrows with an anchor tag so that the main nav is hidden on the page they navigate to.
The problem is about 25% of the time the browser hits the destination page at the TOP for a SPLIT SECOND before it jumps down to the anchor tag, so you see the top nav for just a second. It looks buggy and bad.
Thoughts… should I use CSS transitions to hide it? Should I pass a value in the URL and then pick up on it on the destination page to set the main nav to display: none, and then if the browser is at the top of the window and the user scrolls up, add it back in?
Any help would be appreciated!
You already pass a value in the url: #local-nav
I didn't test it... But I think this could work:
if(location.hash){
$(".navbar-absolute-top").css("visibility","hidden");
setTimeout(function(){
$(".navbar-absolute-top").css("visibility","visible");
},500);
}
-------
EDIT based on comment
Okay then...
What if you set the non-visibility in CSS?
.navbar-absolute-top{
visibility=hidden;
}
Then we decide when to set it visible.
If there a hash in the url ==> wait... If not ==> Don't wait!
;)
if(location.hash){
// Holds on before setting the main nav visible
setTimeout(function(){
$(".navbar-absolute-top").css("visibility","visible");
},500);
}else{
// Sets the main nav visible right now
$(".navbar-absolute-top").css("visibility","visible");
}
500ms may need to be adjusted
;)
-------
EDIT based on comment
About "choppy effect"... Maybe animate() will give a smoother effect using opacity:
body{
opacity=0;
}
if(location.hash){
// Holds on before setting the main nav visible
setTimeout(function(){
$("body").animate({"opacity":1},200);
},50);
}else{
// Sets the main nav visible right now
$("body").animate({"opacity":1},200);
}
to complete the effect... You should add a $("body").animate({"opacity":"0"},200); in a paddle-nav-item a .click() handler that will redirect on .animate callback:
$(".paddle-nav-item a").click(function(e){
// Hold the click event
e.preventDefault();
// Opacity effect
$("body").animate({"opacity":"0"},200,function(){
// Callback retreive the href and redirect AFTER the animation has completed
redirectTo = $(this).attr("href");
location.assign(redirectTo);
});
});
;)

How to force a drop-down to move down on IE 11?

I have the following drop-down :
<select>
<option></option>
<option>Closed</option>
<option>Open</option>
</select>
with the associated style:
select {
font-family: Cursive;
width:200px;
position: relative;
z-index: 100;
padding-right: 25px;
}
My problem is that the drop-down is moving upward on IE 11:
Where as on chrome it is working fine.
Any idea ?
Like mentioned in the comments, select menus are very browser specific and hard to style. Some even send the options into the twilight zone where they are seemingly not even a part of the window and any events will return null. It might not be worth trying to get this to look the same across browsers, also because of the mobile implementations, but I happened to be making something like this for no apparent reason. As it coincides with your question I might as well post it.
It's not the prettiest thing when it comes to HTML and CSS because it requires four additional elements - one wrapper (commonly used for styling select boxes with overflow hidden but I took a slightly different approach because I thought it looked better) and three absolutely placed elements. One is a styled button, another will hide the scrollbar that appears and the third is a minor hack.
Most important thing is that the user will not be able to click the select menu itself. When this happens, most is lost because after that it's limbo. For that the third element will be used. It will be put on top of the select box. Then when it's clicked, instead of directly opening the menu it will be faked by changing the size of the select element. The div covering the right side also serves another purpose. It's initially placed at the bottom and by getting it's offset we'll know the height of the box. This will be used to resize the button and set the correct size for the overlaying div.
Looks to be behaving quite predicatbly on all major Windows desktop browsers. For the mobile implications this script uses a touch support feature test and reverts to normal layout if that is the case. Could probably be tweaked (with a screen size check) to not exclude trackpad users.
Demo
Not too much relevant CSS. But important to style body background the same as the bar on the right. Transparency is used so the actual menu isn't visible before the image for the button loads.
$(function() {
var hub = $('#box'), list = $('select'),
impel = $('#open'), commit = $('#mark'), tract = $('#refer'),
zenith = tract.position().top,
extent = list.children().length, active;
if (touch()) {
impel.add(commit).add(tract).remove();
hub.fadeTo(0,1);
return;
}
impel.add(commit).height(zenith);
tract.addClass('bar');
hub.fadeTo(0,1).on('mouseup click', function(e) {
e.stopPropagation();
});
commit.mouseup(function() {
flip();
show();
active = true;
});
list.add(impel).click(function() {
flip();
active = !active;
if (active) show();
else hide();
});
$(window).click(function() {
if (active) {
flip();
hide();
active = false;
}
});
function show() {list.attr('size', extent)}
function hide() {list.removeAttr('size')}
function flip() {commit.toggle()}
function touch() {
return 'ontouchstart' in window
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0;
}
});

event.preventDefault() Causing :hover pseudo class to stick

I am trying to implement a document navigation, similar to that on the Bootstrap site for their documentation. I've got it working on the whole, but there is one little aspect of it which is bugging me.
Like on Bootstrap, I am using a combination of the affix.js and scrollSpy.js, and this is working. See the following
JSFiddle.
$('#doc_nav').on( "click", "a", function( e ){
// e.preventDefault();
var target = this.hash;
var $target = $(target);
var offset = $target.offset().top;
console.log(offset);
offset = offset - 100;
console.log(offset);
$('html, body').scrollTop( offset );
});
With the e.preventDefault() commented out, the behavior of the navigation menu is as expected. Scrolling down the window results in the displayed div being highlighted on the menu. Clicking on an item in the menu list, takes you directly to corresponding div on the page and when you then scroll away from that section on the page, the menu updates accordingly.
On my 'real' page, I have a fixed header of height 100px. So currently, when I click on a menu link, the pages jumps to the required section and places it at the top of the page where the header obscures it slightly. The scrollTop part of the code above doesn't seem to work unless I use e.preventDefault(). If you uncomment this line in the fiddle and run it again, click on 'Heading 3' link on the menu, and you will see that it now puts the Heading 3 content in the page offset by 100px from the top. Perfect.
However, now scroll back up the page towards the top. You will see that the 'Heading 3' list item, remains in its :hover state, even when the mouse is no where near it. Its as though the e.preventDefault() has prevented the browser from detecting that the mouse is no longer hovering on the item.
Mouseclicking anywhere outside the browser window, corrects the problem.
Can anyone shed any light on what I'm doing wrong here? How can I prevent the default behavior of the anchor click so I can control the page scroll placement, without stopping the correct CSS painting in the process?
The problem arises because I am preventing the browsers default behavior, using e.preventDefault(), as I want to control the scroll to the anchored element.
I've tested this in Firefox and IE10.
The issue is not the :hover state, but the :focus state.
When you click on a link, that link gains focus so you apply the :focus styling (that is the same as the :hover styling in your code). By preventing the default behavior, that link stays active and doesn't lose the focus.
One quick solution would be to unfocus/blur the element by using: $(this).blur().
In your code it would look like this:
$('#doc_nav').on( "click", "a", function( e ){
e.preventDefault();
var target = this.hash;
var $target = $(target);
var offset = $target.offset().top;
console.log(offset);
offset = offset - 100;
console.log(offset);
$(this).blur();
$('html, body').scrollTop( offset );
});
You can see a demo here: https://jsfiddle.net/z32rpe3b/30/
Why did it work fine with e.preventDefault() but incorrectly without it?
The answer to this has to do with the order of execution. The onclick event happens before the href redirection happens in the browser:
Without the e.preventDefault(), the code is executed, and the browser scrolled correctly to the target offset - 100 position (in the onclick), but then it executed the link href and scrolled to the target offset. It just happens so fast that it seems that it goes directly to the target position.
With e.preventDefault(), the code is executed (scrolling to offset - 100), and the browser doesn't execute the href action (so it stays at offset - 100).

Categories