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

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).

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 on top-of-window mouseover?

I'm seeking an onmouseover event to detect when the mouse hovers the top of the window. I'm only able to find onmouseover events for the whole page. How do I restrict it to just the very top of the window?
There's no "element" on the page I'm trying to attach the event to, as some have suggested. This should work with any webpage - I do not have any control over the HTML on the page.
Need only support modern browsers, simplest method possible, no jQuery.
The onmouseover can be added to any HTML element, that is any div, p, span, img, ul, anything. Do you have an element that covers the portion of the page you are interested in? Then just add the event to that element.
If the area where you want to catch the mouse movement doesn't correspond to an actual element, you can do it by adding the eventlistener to the body and the check where the pointer is:
document.body.addEventListener("mousemove", function(event) {
//Check if we are in the top area of the page.
if(event.pageY < 300) {
//Do something here.
}
});
If you want it to use the top of the page area of the browser as opposed to the page itself (so that scrolling does not affect where the cut off is on the screen) just use clientY instead of pageY.
JSFiddle for pageY.
JSFiddle for clientY.
alerts when mouse is at top of page: demo on jsfiddle: DEMO
JS:
(function (){//alert at top
function getPosition(e){
if (e.pageY < 75) alert('top');//switch to clientY for top of screen
}
document.addEventListener('mousemove', getPosition, false);})();
EDIT:
use: clientY for top of view screen, as page may be longer, this stays with screen
use: pageY for top of your website only
Maybe works...
$('body').on('mouseover', function () { // bind change event to select
if (event.pageY < 10)
alert(event.pageY)
});

Switch tabs based on mouse scroll

I would like to have a widget on a webpage containing a number of tabs. When the user scrolls the page and the widget comes in to view and he keeps scrolling down, the tabs should be activated one by one (without the page scrolling further down). Once the last tab is showing, the page should resume scrolling as usual. Is this doable using JS/jQuery?
UPDATE:
Since this seems too broad a question:
The problem is, I don't know how to use the scroll offset and prevent the page from scrolling down until I decide it can resume its normal behavior
UPDATE 2
I created This fiddle,
$(document).ready(function(){
$('#tabbed').mouseover(function(){
$(this).focus();
}).scroll(function(){
console.log("scrolling tabs");
});
$(window).scroll(function(evt){
var scrollPos = $(this).scrollTop()
console.log(scrollPos);
// BULLETPROOF WAY TO DETECT IF THE MOUSE IS OVER THE
// SCROLLABLE DIV AND GIVE IT FOCUS HERE?
});
});
it contains a long page and a scrollable div among its contents. The only problem is that the div starts catching scroll events only if I move my mouse. If I could find a bulletproof way to activate the scrolling div whenever the mouse is over it I'm there. Any ideas?
You can't prevent scrolling with javascript. Using iframes and divs with scroll will only work if the mouse is over them.
You can cancel the mouse wheel and keys events related to the scrolling, however the user will be able to scroll using the scrollbar (more here).
Another approach is leaving an empty area and fixing your widget inside this area, like in this working example
$(window).bind('scroll', function()
{
var scroll = $(window).scrollTop(),
innerHeight = window.innerHeight || $(window).height(),
fooScroll = $('#fooScroll'),
emptyArea = $('#emptyArea'),
offset = emptyArea.offset(),
fixedClass = 'fixed';
if(scroll > offset.top)
{
if(scroll < offset.top + emptyArea.height() - fooScroll.height())
{
fooScroll.addClass(fixedClass);
fooScroll.css("top", 0);
}
else
{
fooScroll.removeClass(fixedClass);
fooScroll.css("top", emptyArea.height() - fooScroll.height());
}
}
else
{
fooScroll.removeClass(fixedClass);
fooScroll.css("top", 0);
}
});
Then you can change the tabs while the page is scrolling.
You should be able to do this. You can use the jQuery scroll event to run your own code whenever the user scrolls up or down. Also, so long as you call e.preventDefault() whenever the scroll event is fired, you can prevent the whole window from scrolling up or down.

Stop page from scrolling if hovering div [duplicate]

This question already has answers here:
Prevent scrolling of parent element when inner element scroll position reaches top/bottom?
(32 answers)
Closed 9 years ago.
I have a div that is scrollable, but whenever you reach the bottom/top of it, it begins to scroll the entire page. That could be annoying for users who scroll fast, and then the entire page starts scrolling unexpectedly.
I need something where if you are hovering over the div, the page is not scrollable.
I have tried this by adding CSS when I hover the div...
body {
overflow:hidden;
}
...It works but there is one problem. The scrollbar disappears and that looks kind of stupid to have it disappearing/reappearing. Any way to achieve the same effect but keep the scrollbar visible? I have seen it done with Facebook chat.
Here is a very simple way to stop the propagation with no plugins, just jQuery.
Update: The code has been updated to work correctly in IE9+. Have not tested in previous versions.
First, create a class on your <div> to mark it as having this behavior. In my example, I use the class .Scrollable.
<div class="Scrollable">
<!-- A bunch of HTML here which will create scrolling -->
</div>
The jQuery to disable is:
$('.Scrollable').on('DOMMouseScroll mousewheel', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
In essence, what this does is to detect which direction the scrolling is being requested in (based on the originalEvent.wheelDelta: positive = up, negative = down). If the requested delta of the mousewheel event would move scrolling past the top or bottom of the <div>, cancel the event.
In IE, especially, scrolling events which go past a child element's scrollable area then roll up to parent elements, and the scrolling continues regardless of the event being canceled. Because we cancel the event in any case, and then control the scrolling on the child through jQuery, this is prevented.
This is loosely based on the way that this question solves the problem, but does not require the plugin, and is cross-browser compliant with IE9+.
Here is a working jsFiddle demonstrating the code in-action.
Here is a working jsFiddle demonstrating the code in-action, and updated to work with IE.
Here is a working jsFiddle demonstrating the code in-action, and updated to work with IE and FireFox. See this post for more details about the necessity of the changes.
maybe have a look to
How to disable scrolling temporarily?
This is a sample to stop and activate scroll

Jquery Mobile go back button scrolls to top

In my Jquery Mobile website
I am using href for back button like;
<a id='{0}' class='{1}' href='/' data-role=""button"" data-icon=""arrow-l"" data-transition=""slide"" data-direction=""reverse"">
but if I have scroll on first page, back button jumps back to top again.
First page does not stay on same position.
Is there any solution for this?
Solution
I had this issue i fixed it using iSroll
While going from PageA to PageB save the scroll position of PageA in a variable.
to do this modify the iscroll.js and add getScrollY method under scrollTo like this
scrollTo : function(x, y, time, relative) {
var that = this, step = x, i, l;
that.stop();
if (!step.length)
step = [{
x : x,
y : y,
time : time,
relative : relative
}];
for ( i = 0, l = step.length; i < l; i++) {
if (step[i].relative) {
step[i].x = that.x - step[i].x;
step[i].y = that.y - step[i].y;
}
that.steps.push({
x : step[i].x,
y : step[i].y,
time : step[i].time || 0
});
}
that._startAni();
},
getScrollY : function() {
var that = this;
return that.y;
},
Now save the current position before page change like this
curScrollPos = myScroll.getScrollY();
And set the scroll position while going back to that PageA, i am doing this on pagehide event of PageB
myScroll.scrollTo(0, curScrollPos, 1);
myScroll.refresh();
This way i solved my issue, hope this helps.
More info
If you want to find out more about this topic take a look at this article, you will also find working examples.
why don't you directly add data-rel="back" on the anchor tag and set href="#" instead ?
<a id='{0}' class='{1}' href='#' data-rel="back" data-role="button" data-icon="arrow-l" data-transition="slide" data-direction="reverse"/>
Before I describe your available solutions you need to understand, this is not an error nor is there a perfect solution. The issue is that to animate the transition to another page the viewport has to be at the top of the page so that the current page and the page transitioning in are vertically lined-up.
If you were half-way down a long list on one page (say 1000px) and the page you are transferring to is only a few hundred pixels tall then the current page would animate off the screen properly but the new page would not be visible as it would be above the viewport.
There are 2 viable solutions:
iScroll and its jQuery Mobile derivate iScrollview
iScroll homepage: http://cubiq.org/iscroll-4
iScrollview homepage: https://github.com/watusi/jquery-mobile-iscrollview
iScroll is a javascript that can scroll content in a window within a web browser with very similar behaviour to native scrolling on mobile devices such as iPhone and Android. This means you can scroll a window within the browser using native-like scrollbars and physics.
That is also a solution for our current problem. Because of iScroll implementation pages will occupy 100% of page height, no matter how far listview is scrolled. This is also a reason why on return listview will still stay at a same position.
Of course in case you want to implement this solution you should pick iScrollview implementation. You would still be able to implement iScroll, but it would take you much more time.
Silent scroll
Official documentation: http://jquerymobile.com/demos/1.1.0-rc.1/docs/api/methods.html
This jQuery Mobile functionality is also the same reason why we have this problem at the first place. Before a page transition is triggered original page is silently scrolled to the top.
In our case, when listview is selected, its position must be remembered (here you will find solutions of data/parameteres storing during the page transition, just search for the chapter: Data/Parameters manipulation between page transitions) before page is changes. In that case, when we return to the previous page we could use pagebefpreshow event to silently scroll to the bottom before page is shown.
//scroll to Y 100px
$.mobile.silentScroll(100);
And here's a working example of silent scroll: http://jsfiddle.net/Gajotres/2xfrM/
And here's a real life jsFiddle example using large listview and several pages: http://jsfiddle.net/Gajotres/5zZzz/
// Detect click on a li element and store its coordinate, change page to another
$(document).on('pagebeforeshow', '#index', function(){
$('#test-list li').on('click', function(){
storePosition.topCoordinate = $(this).offset().top;
$.mobile.changePage('#second');
});
});
// If there's a stored position use silentscroll function and scroll to correct location
$(document).on('pageshow', '#index', function(){
if(storePosition.topCoordinate !== null) {
$.mobile.silentScroll(storePosition.topCoordinate);
}
});
// Store position location
var storePosition = {
topCoordinate : null
}
Unfortunately like in your example, this solution works only on pageshow. Because of jQM architecture it is only possible to do this during the pageshow event.
Final notes
If you want to find out more about iScroll + iScrollView, how they work with working examples then take a look at this article.
I found a solution here: https://forum.jquery.com/topic/jquery-mobile-scroll-to-top-of-page-on-page-load#14737000005271291
(function($){
$( document ).on( "mobileinit", function() {
var silentScroll = $.mobile.silentScroll;
$.mobile.silentScroll = function( ypos ) {
if ( $.type( ypos ) !== "number" ) {
// FIX : prevent auto scroll to top after page load
return;
} else {
silentScroll.apply(this, arguments);
}
}
})
}(jQuery));

Categories