Exception for hashchange function - browser history back? - javascript

I have no idea how I should even call this problem … the title of this question doesn't make any sense at all, I know!
Following case: I have a single-page layout where users scroll downwards. I have sections .layer which, when inside the viewport should change the hash in the addressbar to its id. So e.g. the .layer#one is inside the viewport the url in the addressbar looks like this www.whatever.com/#!/one
$(window).scroll(function() {
hash = $('.layer:in-viewport').attr('id');
top.location.hash = "!/" + hash;
});
This works just fine and is exactly like I want it. The reason I have this syntax with the !/ is that if I would simply set the location to hash only the scroll-behaviour would be buggy because the browser tries to stick to the hash position.
The problem is now, that I want to be able to make browser history-back button working!
This would normally be rather simple with the hashchange function that comes with jQuery like so…
$(window).bind( 'hashchange', function( event ) {
//query the hash in the addressbar and jump to its position on the site
});
The only problem I have with this is that the hashchange function would also be triggered if the hash is changed while scrolling. So it would again jump or stick to the current position in the browser. Any idea how I could solve this? I could probably unbind the hashchange while scrolling, right? But is this the best solution?

Sure, you could just unbind and rebind whenever the hash changes on scroll. For example:
var old_hash;
var scroller = function() {
hash = $('.layer:in-viewport').attr('id');
if ( old_hash != hash ) {
$(window).off('hashchange', GoToHash); // using jQuery 1.7+ - change to unbind for < 1.7
top.location.hash = "!/" + hash;
setTimeout(function() { // ensures this happens in the next event loop
$(window).on('hashchange', GoToHash);
}, 0);
old_hash = hash;
}
}
var GoToHash = function() {
//query the hash in the addressbar and jump to its position on the site
}
$(window).scroll(scroller);

Related

animated scrolling script prevents local anchor positions to be accessed from external links

I have a onepager site where I use scrollmagic plus all its necessary plugins/libraries (and jQuery) for different effects where animation, pinning, fading processes etc. are triggered by scroll positions.
I also use it for animated scrolling to the anchor points on the page (from the menu and from other local links) - see the according part of the script below.
The problem is that this script suppresses the default behaviour of "jumping" directly to an anchorpoint when a local link is clicked, and apparently also when the page is accessed from outside via a direct link or bookmark with an anchor appended to the URL (like http://www.example.com/index.php#part3). Altough this behaviour is desired when clicking a local link, it obviously prevents the browser from displaying the anchor position when an anchor is linked from somewhere else.
Is there any way to make the browser directly display that anchor position when a link like in the above example is clicked?
var sm_controller_1 = new ScrollMagic.Controller();
sm_controller_1.scrollTo(function(anchor_id) {
TweenMax.to(window, 2.0, {
scrollTo: {
y: anchor_id
autoKill: true
},
ease: Cubic.easeInOut
});
});
jQuery(document).on("click", "a[href^=#]", function(e) {
var id = jQuery(this).attr('href');
if(jQuery(id).length > 0) {
e.preventDefault();
sm_controller_1.scrollTo(id);
if (window.history && window.history.pushState) {
history.pushState("", document.title, id);
}
}
});
(It doesn't make sense to create a fiddle/codepen since the problem lies in calling the original URL from an external source).
Well assuming scroll magic doesnt have extra functionality that is not posted here that would get in the way of my answer you could try this:
Add a data-attribute to your links which you want to use default behavior:
<a href="example.com/index.php#part3.php" data-default="true">
Check if that data attribute exists and if it does return true in your click handler to continue with the default behavior:
var sm_controller_1 = new ScrollMagic.Controller();
sm_controller_1.scrollTo(function(anchor_id) {
TweenMax.to(window, 2.0, {
scrollTo: {
y: anchor_id
autoKill: true
},
ease: Cubic.easeInOut
});
});
jQuery(document).on("click", "a[href^=#]", function(e) {
if(e.currentTarget.dataset.default){
return true;
}
var id = jQuery(this).attr('href');
if(jQuery(id).length > 0) {
e.preventDefault();
sm_controller_1.scrollTo(id);
if (window.history && window.history.pushState) {
history.pushState("", document.title, id);
}
}
});
You can try and use this code:
jQuery(function ($) {
$(document).ready(function() {// if needed, use window.onload which fires after this event
if(window.location.hash) {
var hash = window.location.hash;
$( 'a[href=' + hash + ']' ).click();
}
});
});
It will wait till the DOM (or the page) is loaded and then simulate the user click on the nav item.
After I've read your question one more time, I am not sure anymore if you want your page loaded on the position of the element which is listed in the anchor or the scroll wasn't working when coming from an external source?
If the scroll was working but you wanted to display the page at the right place, like a jump, then I propose 2 solutions:
a) use the CSS opacity:0; on the body and after the scroll is finished, set it back to opacity:1;
b) try to jump on the proper place on the page before you load ScrollMagic

How do I navigate to hashtag after page load?

I want to do the inverse of what I've been finding so far. I'm setting a lot of heights with js and I want to navigate to the hashtag in the url after the page has loaded. I'm guessing this is simple but I'm not seeing the obvious answer... for an example, check here...
http://alavita.rizenclients.com/#story
Attempted this using the code...
$(window).load(function() {
var hashTag = window.location.hash;
window.location = '/' + hashTag;
});
doesn't actually take me to the top of the tagged section...
If you simply want to change the hash after page loads:
window.onload = function (event) {
window.location.hash = "#my-new-hash";
};
If you want to navigate to the URL with new hash:
window.location.href = "http://website.com/#my-new-hash";
If you want to listen for changes in the hash of the URL; you can consider using the window.onhashchange DOM event.
window.onhashchange = function () {
if (location.hash === "#expected-hash") {
doSomething();
}
};
But it is not supported by every major browser yet. It now has a wide browser support. You can also check for changes by polling the window.location.hash on small intervals, but this is not very efficient either.
For a cross-browser solution; I would suggest Ben Alman's jQuery hashchange plugin that combines these methods and a few others with a fallback mechanism.
EDIT: After your question update, I understand you want the page to scroll to a bookmark?:
You can use Element.scrollTop or jQuery's $.scrollTop() method.
$(document).ready(function (event) {
var yOffset = $("#my-element").offset().top;
$("body").scrollTop(yOffset);
});
See documentation here.
For some reason both MS Edge 42 and IE 11 will not scroll to the new bookmark for me, even when doing a window.location.reload(true) after setting the new bookmark. So I came up with this solution: insert this script on the page you're loading (requires jquery)
$(document).ready(function() {
var hash = window.location.hash;
if (hash) {
var elem = document.getElementById(hash.substring(1));
if (elem) {
elem.scrollIntoView();
}
}
});
Using scrollTo or scrollIntoView will not respect any offset created by the :target css selector, which is often used to make the page scroll to just above the anchor, by setting it to position: relative with a negative top.
This will scroll to the anchor while respecting the :target selector:
if (location.hash) {
window.location.replace(location.hash);
}
You could just set the current location:
window.location = 'http://alavita.rizenclients.com/#story';
Or set the hash (if it isn't already), then reload:
window.location.hash = hashTag;
window.location=window.location.href;
You changed your question.
Check out this solution. https://stackoverflow.com/a/2162174/973860 so you understand what is going on and how to implement a cross browser solution.
NOTICE: At the bottom he mentions a jquery plugin that will do what you need.
http://benalman.com/projects/jquery-hashchange-plugin/
This plugin will allow you to do something like this. This will work for your current page. But you may want to modify it to be more robust.
$(function(){
// Bind the event.
$(window).hashchange( function(){
// get the hash
var hash = window.location.hash;
// click for your animation
$('a[href=' + hash + ']').click();
})
// Trigger the event (useful on page load).
$(window).hashchange();
});

"Skip Navigation" Link without anchors

I am trying to create a "skip navigation" link without being able to use anchors. The site is built in a peculiar way, where anchor link formatting has been re-purposed. So, I am attempting to allow people to skip the navigation by using focus. However, it isn't working.
HTML code for the skip navigation link itself:
<!-- Start Top Left in Nav Bar -->
<aside>
Skip Navigation
</aside>
<!-- End Top Left in Nav Bar -->
Code to Change the Focus
var nav = document.getElementById('#skipNav');
nav.onclick=skipNav();
function skipNav(){
document.activeElement.blur();
if ($('#linkHome').hasClass('current')==true)
{
$('#homeFocus').focus();
}
if ($('#linkTeam').hasClass('current')==true)
{
$('#teamFocus').focus();
}
if ($('#linkTraining').hasClass('current')==true)
{
$('#trainingFocus').focus();
}
if ($('#linkTesting').hasClass('current')==true)
{
$('#testingFocus').focus();
}
if ($('#linkRemediation').hasClass('current')==true)
{
$('#remediationFocus').focus();
}
if ($('#linkContact').hasClass('current')==true)
{
$('#contactFocus').focus();
}
};
Script to Change Pages and Mark Current Page
var FluidNav = {
init: function() {
$('a[href*="#"]').click(function(e) {
e.preventDefault();
if($(this).attr("href").split("#")[1]) {
FluidNav.goTo($(this).attr("href").split("#")[1]);
}
});
this.goTo("home");
},
goTo: function(page) {
var next_page = $("#"+page);
var nav_item = $('nav ul li a[href=#'+page+']');
$("nav ul li").removeClass("current");
nav_item.parent().addClass("current");
FluidNav.resizePage((next_page.height() + 40), true, function() {
$(".page").removeClass("current"); next_page.addClass("current");
});
$(".page").fadeOut(500);
next_page.fadeIn(500);
document.activeElement.blur();
$('#'+page+'Focus').focus();
FluidNav.centerArrow(nav_item);
},
centerArrow: function(nav_item, animate) {
var left_margin = (nav_item.parent().position().left + nav_item.parent().width()) + 24 - (nav_item.parent().width() / 2);
if(animate != false) {
$("nav .arrow").animate({
left: left_margin - 8
}, 500, function() { $(this).show(); });
} else {
$("nav .arrow").css({ left: left_margin - 8 });
}
},
resizePage: function(size, animate, callback) {
if(size) { var new_size = size; } else { var new_size = $(".page.current").height() + 40; }
if(!callback) { callback = function(){}; }
if(animate) {
$("#pages").animate({ height: new_size }, 400, function() { callback.call(); });
} else {
$("#pages").css({ height: new_size });
}
}
};
$("nav select").change(function() {
if(this.options[this.selectedIndex].value != "#") {
var page = this.options[this.selectedIndex].value.split("#")[1];
FluidNav.goTo(page);
$("html,body").animate({ scrollTop:$('#'+page).offset().top }, 700);
}
});
Any ideas?
Sounds like you're trying to do a 'dynamic skiplink', where you determine the target at runtime?
Skip Navigation
The problem is that when you click a link, navigation happens. A href="" doesn't prevent navigation, it just means that you end up navigating to the current page - and that's going to reset the focus. This happens after your click event handler. So even though you may correctly set the focus where you want to, it ends up being 'lost' when the page reloads.
There's a couple of ways to prevent this: one is to use href="javascript:void(0)" - href specifies where to navigate, and if it evaluates to void, the browser won't navigate at all.
A somewhat cleaner way is to tell the browser not to carry out the default action in the event handler:
function skipNav(e)
{
e.preventDefault(); // prevent default action - link navigation - from taking place
...
--
Couple of other issues with the code:
Don't bother with role="link" on the A - save these attributes for when you are doing something out of the ordinary. The key thing to know is that screenreaders already know how to deal with all the standard HTML elements when they are used in a standard way. So if you are using an as a link, or a as a button, then you don't need to add a role.
But if you are creating a new control of of plain DIVs, or if you are repurposing a HTML element for a different use, then you need a role attribute to tell the screenreader how you are actually using the element.
For example, a DIV that's had an onclick handler and is behaving like a button would need role="button", otherwise a screenreader might ignore it or just say something generic like 'element'. Or if you are creating a button from an A tag, and it ends up behaving like and looking like a button from a user's point of view, then you'd need role="button" so that a screenreader will announce it as a button rather than as a link.
--
Watch for mixing plain DOM vs jQuery conventions - only jQuery uses # to find elements by ID, so use either:
var nav = document.getElementById('skipNav'); // plain DOM way, no #
or
var nav = $('#skipnav'); // jQuery way using selector
--
Watch for functions as values vs calls:
nav.onclick=skipNav();
This will actually call skipNav(), and assign the return value - null! - to onclick. Don't use ()'s when you're setting a callback. You don't need this code anyhow, since you're setting the handler using onClick in the tag anyhow.
Also, note that as your code stands, when skipNav() is called, it tries to call document.activeElement.blur() - but at that point in time - document is still loading - there's no activeElement, so calling blur() on null generates an exception - which you should see in your browser's console/debugging window.
--
Don't use .blur() - there's no need to do this:
document.activeElement.blur();
Instead, just focus the element you want to have focus. The danger with doing .blur() is that if the code after it fails to set the focus somewhere reasonable, focus will end up getting 'lost', which is very inconvenient for a keyboard user, since they have to tab from the start of the page.
--
Javascript coding practice: don't bother with == true in the if() expressions; and unless you expect you expect more than one of the elements to have the 'current' class, use else if instead of plain if: this makes it clear in the code that you're expecting only one branch to be used.
--
Finally, make friends with the browser's debugger (F12 in most): you'll learn some of the above by putting breakpoints in the event handler and your initialization code, and stepping through it to ensure it's behaving as you expect.

Reload the page just once if a div is above certain position

I want to reload the page only once if a given div is positioned higher than position:absolute; top:15%.
I think this could be done with jQuery's .css method, something like:
if ('#mydive').css('top') > '15%' {
//reload the page
}
Could someone suggest a simple solution, preferably jQuery or pure JavaScript?
If what you meant is the top of the document, you can probably try:
var percent = .15; // 15%
if ($('#yourdiv').offset().top > ($(document).height() * percent)) {
window.location.reload();
}
// if by pixels
var pixels = 10; // 10px
if ($('#yourdiv').offset().top > pixels) {
window.location.reload();
}
You can check the current position of a div using the Computed Style
If you are have a like an animation or a drag and drop, you can use the onmousemove event to track the position of the div. but be careful the mousemove will be trigger for every pixel it moves and it my use lots of process time, so be wise on how you use it :)
Well, it is rather hard to say how you should determine the position of the div since you presented no code, but basically you should write a function that fetches the window height and the offset of the div relative to the top of the window to determine whether it is higher than 15%. You then need to call this function every time using the window.onscroll event listener. When the function returns true, trigger trigger window.location.reload(true) to reload the page. I imagine this could be done fairly easily in jQuery as well.
the above answers point you in the right way, but in order to "reload the page only once", you need an extra ingredient. you need a way to store a flag that points out whether the page has already been reloaded or not.
Say you follow tradyblix's code.
You should check for that flag before reloading your page :
if (hasReloaded() && $('#yourdiv').offset().top > ($(document).height() * percent)) {
reload();
}
where hasReloaded is a function that determins if the page has been reloaded, and it can be either a function that sends an ajax request to a server, that checks a cookie or even the localStorage object:
function hasReloaded(){
return !!localStorage['reloaded'];
}
In order to set that flag, your reload function needs to access the localStorage (or cookie or ajax server response) :
function reload(){
localStorage['reloaded'] = true;
window.location.reload();
}
This is just a sketch of how you should write this functionality.

How can I update window.location.hash without jumping the document?

I have a sliding panel set up on my website.
When it finished animating, I set the hash like so
function() {
window.location.hash = id;
}
(this is a callback, and the id is assigned earlier).
This works good, to allow the user to bookmark the panel, and also for the non JavaScript version to work.
However, when I update the hash, the browser jumps to the location. I guess this is expected behaviour.
My question is: how can I prevent this? I.e. how can I change the window's hash, but not have the browser scroll to the element if the hash exists? Some sort of event.preventDefault() sort of thing?
I'm using jQuery 1.4 and the scrollTo plugin.
Many thanks!
Update
Here is the code that changes the panel.
$('#something a').click(function(event) {
event.preventDefault();
var link = $(this);
var id = link[0].hash;
$('#slider').scrollTo(id, 800, {
onAfter: function() {
link.parents('li').siblings().removeClass('active');
link.parent().addClass('active');
window.location.hash = id;
}
});
});
There is a workaround by using the history API on modern browsers with fallback on old ones:
if(history.pushState) {
history.pushState(null, null, '#myhash');
}
else {
location.hash = '#myhash';
}
Credit goes to Lea Verou
The problem is you are setting the window.location.hash to an element's ID attribute. It is the expected behavior for the browser to jump to that element, regardless of whether you "preventDefault()" or not.
One way to get around this is to prefix the hash with an arbitrary value like so:
window.location.hash = 'panel-' + id.replace('#', '');
Then, all you need to do is to check for the prefixed hash on page load. As an added bonus, you can even smooth scroll to it since you are now in control of the hash value...
$(function(){
var h = window.location.hash.replace('panel-', '');
if (h) {
$('#slider').scrollTo(h, 800);
}
});
If you need this to work at all times (and not just on the initial page load), you can use a function to monitor changes to the hash value and jump to the correct element on-the-fly:
var foundHash;
setInterval(function() {
var h = window.location.hash.replace('panel-', '');
if (h && h !== foundHash) {
$('#slider').scrollTo(h, 800);
foundHash = h;
}
}, 100);
Cheap and nasty solution.. Use the ugly #! style.
To set it:
window.location.hash = '#!' + id;
To read it:
id = window.location.hash.replace(/^#!/, '');
Since it doesn't match and anchor or id in the page, it won't jump.
Why dont you get the current scroll position, put it in a variable then assign the hash and put the page scroll back to where it was:
var yScroll=document.body.scrollTop;
window.location.hash = id;
document.body.scrollTop=yScroll;
this should work
I used a combination of Attila Fulop (Lea Verou) solution for modern browsers and Gavin Brock solution for old browsers as follows:
if (history.pushState) {
// IE10, Firefox, Chrome, etc.
window.history.pushState(null, null, '#' + id);
} else {
// IE9, IE8, etc
window.location.hash = '#!' + id;
}
As observed by Gavin Brock, to capture the id back you will have to treat the string (which in this case can have or not the "!") as follows:
id = window.location.hash.replace(/^#!?/, '');
Before that, I tried a solution similar to the one proposed by user706270, but it did not work well with Internet Explorer: as its Javascript engine is not very fast, you can notice the scroll increase and decrease, which produces a nasty visual effect.
This solution worked for me.
The problem with setting location.hash is that the page will jump to that id if it's found on the page.
The problem with window.history.pushState is that it adds an entry to the history for each tab the user clicks. Then when the user clicks the back button, they go to the previous tab. (this may or may not be what you want. it was not what I wanted).
For me, replaceState was the better option in that it only replaces the current history, so when the user clicks the back button, they go to the previous page.
$('#tab-selector').tabs({
activate: function(e, ui) {
window.history.replaceState(null, null, ui.newPanel.selector);
}
});
Check out the History API docs on MDN.
This solution worked for me
// store the currently selected tab in the hash value
if(history.pushState) {
window.history.pushState(null, null, '#' + id);
}
else {
window.location.hash = id;
}
// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
$('#myTab a[href="' + hash + '"]').tab('show');
And my full js code is
$('#myTab a').click(function(e) {
e.preventDefault();
$(this).tab('show');
});
// store the currently selected tab in the hash value
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
var id = $(e.target).attr("href").substr(1);
if(history.pushState) {
window.history.pushState(null, null, '#' + id);
}
else {
window.location.hash = id;
}
// window.location.hash = '#!' + id;
});
// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
// console.log(hash);
$('#myTab a[href="' + hash + '"]').tab('show');
I'm not sure if you can alter the original element but how about switch from using the id attr to something else like data-id? Then just read the value of data-id for your hash value and it won't jump.
When using laravel framework, I had some issues with using a route->back() function since it erased my hash. In order to keep my hash, I created a simple function:
$(function() {
if (localStorage.getItem("hash") ){
location.hash = localStorage.getItem("hash");
}
});
and I set it in my other JS function like this:
localStorage.setItem("hash", myvalue);
You can name your local storage values any way you like; mine named hash.
Therefore, if the hash is set on PAGE1 and then you navigate to PAGE2; the hash will be recreated on PAGE1 when you click Back on PAGE2.

Categories

Resources