I have created the following application using iScroll: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-0
As the user flicks left and right or clicks the arrows in the bottom corners, the application moves the content sections it updates the history by changing the hash so that the user can move back and forth to other sections and bookmark them etc.
However! If you access a hash like: http://preview.na-software.co.uk/Demo/FutureLearning4/#/section-2 and then navigate a few sections and then use the back buttons two issues happen:
1.) It scrolls to the first screen (even though currentSection is correct, and iScroll has been told the correct section).
2.) If you click the back or forward button multiple times, you stop the animation and cause it to become confused and stick in between two sections.
Looking into the code, and seeing that the correct indexes and elements are being passed to iScroll on hashchange, and console logging out the offsets, I've discovered the issue is cause because the offsets are incorrectly set... however just doing refresh() won't fix the issue, as it will then reset the position.
Can anyone see where the problem is or see a way to fix this?
I should note that this bug ONLY happens if you come into the application on a URL that isn't section 0 and then scroll around the application. This is because the offsets will be created correctly by your interactions. But if you come into a URL like section 3, then the offsets will be incorrect and so the hashchanges don't work correctly, if that makes sense.
The hashchange method looks like:
// handle hashchange events
$(window).hashchange( function(){
// read the hash to find out what the new section number is
var nums = location.href.match(/(section)-\d+/g).map(
function(x){ return +x.replace(/\D/g,"") }
);
// set currentSection
currentSection = nums[0];
// if the hashchange was called by user scrolling
if(hashCalledByScroll){
// no need to anything as they have already updated hash and scrolled
hashCalledByScroll = false;
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true );
}
// hide the menu on hashchange
hideMenu();
});
Testing your site, I noticed the following: Whenever I access the site via section-3 and then enter the url for section-2, the navigation would instead send me to section-0.
I believe this is the same behaviour as you are experiencing in 1).
So I investigated and came to the following analysis:
In the function horizontal.scrollToElement( '#' + sectionToScrollTo, null, null, true )
iScroll retrieves the utils.offset(el) [iScroll.js#772] for the given el-ement. This offset tells it, where the element to scroll to is.
iScroll goes through the element and all of its offsetParents to add up their offsets. This is where things are breaking: <div class="sections"> has a negative offset to its parent, which imho it should not have.
This, in turn, messes up the scrollTo-coordinates.
To see what I am talking about: document.querySelector('.sections').offsetLeft
This has all just been analysis. My approach to fix this would be to avoid scrollToElement() and instead use scrollTo():
...
} else {
// find the section to scrollTo
sectionToScrollTo = $('#horizontal > .sections > .section').eq(currentSection).attr('id');
// tell iscroll to scroll to the section
var posLeft = -$('#' + sectionToScrollTo)[0].offsetLeft;
var posTop = -$('#' + sectionToScrollTo)[0].offsetTop;
horizontal.scrollTo(posLeft, posTop, 1000);
}
// hide the menu on hashchange
hideMenu();
});
Thus, just calculate the location of the section you want to go to yourself.
About 2) I am not sure if there is much one can do about it. Jumping around quickly breaks a lot of carousels. Maybe a delayed callback to scrollEnd, verifying the validity of the current state.
Another thing I noticed is that you can accidentally stop the transition. Try to click, hold and release the cursor midway a transition - you need to be quick.
Hope this helps.
Found not best solution and it doesn't solve main problem, but it works.
$(window).hashchange(function () {
if (hashCalledByScroll) {
hashCalledByScroll = false;
} else {
var hpage = window.location.hash;
var hpage = hpage.replace('#/section-', ''); //get number of target page
var cpage = currentSection; //number of current page
var count = parseInt(hpage) - parseInt(cpage); //difference
while (count > 0) { //if difference positive: go forward count-times
horizontal.next();
count--;
}
while (count < 0) { //if difference negative: go backward count-times
horizontal.prev();
count++;
}
}
hideMenu();
});
FIDDLE
Related
First and foremost, everything I say is only applicable for mobile version of my website - https://wtstest1.weebly.com so while troubleshooting please use the mobile view.
Secondly I have included the particular JavaScript ( Jquery ) codes relevant to the subject for your reference:
jQuery(window).load(function(){
var wW = $(window).width();
$(document).off('click','.wsite-menu-item');
$(document).on('click','.wsite-menu-item', function(){
var _this = $(this);
var _href = _this.attr('href');
var _top;
if(wW > 992){
_top = $(_href).offset().top - 64;
$('body').removeClass('nav-open')
}else{
_top = $(_href).offset().top - 50;
$('body').removeClass('nav-open')
$('.boo_onepage').removeClass('show')
}
$('html,body').stop().animate({scrollTop:_top},500)
})
When you will load my website in mobile view, you will find a arrow at top having some options, tapping on each of them scrolls to particular sections within my webpage.
Although the arrow didn't minimized on its own after an option was tapped which was inconvenient hence I added the second last line you can see in code:
$('.boo_onepage').removeClass('show')
Now adding this line made the arrow to minimize on its own when a option was chosen
But it lead to another issue - my main reason of posting this.........
Now The arrow minimizes on its own after chosing an option but it happens way too quickly and in the process it skips the active-tab/hover effect which was there originally.
It helped to let the user know where they have tapped by displaying the tab in yellow color for like close to less than 1 second then proceeded to scroll to requested section.
Can someone adjust the jQuery code to make the arrow minimize on its own after choosing an option and still retaining the active tab/hover effect.
What I tried from my end - I tried using setTimeout(function() to delay function - $('.boo_onepage').removeClass('show') by 1 second but it didn't work as well as expected.
( In case someone wants to see how adding that line made the arrow to minimize on its own but removed the hover/active option effect, you can check it here - https://wtstest.weebly.com )
I had read your post...!
You can follow my code below
// When click arrow navbar it's increase size follow 1s
$(document).on('click','.boo_onepage_toggle', function(){
$('.boo_onepage_menu').css({'font-size':'20px','transition':'1s'})
})
// When choose one option the arrow will decrease size follow 1s
// The user when navigation to arrow It's screen point similar button for user understand "Click"
$(document).on('click','.wsite-menu-item', function(){
$('.boo_onepage_menu').css({'font-size':'10px','cursor':'pointer','transition':'1s'})
$('.boo_onepage').removeClass('show')
})
// Hover when user move to arrow
$('.boo_onepage_menu').mouseover(function(){
$(this).css({'font-size':'20px','cursor':'pointer','transition':'1s'})
}).mouseout(function() {
$(this).css({'font-size':'10px','cursor':'pointer','transition':'1s'})
})
Have a nice day...!
Below is my JS code. I have a page with anchor links scrolling. I need the user to go to a specific section on the page when the link is clicked, without covering the basic heading of the section.
Here's what I have been able to accomplish (of course, with a bit of help from Stackoverlfow sleuthing).
// The function actually applying the offset
function offsetAnchor() {
if(location.hash.length !== 0) {
window.scrollTo(window.scrollX, window.scrollY - 100);
}
}
// This will capture hash changes while on the page
window.addEventListener("hashchange", offsetAnchor);
// This is here so that when you enter the page with a hash,
// it can provide the offset in that case too. Having a timeout
// seems necessary to allow the browser to jump to the anchor first.
window.setTimeout(offsetAnchor, 1); // The delay of 1 is arbitrary and may not always work right (although it did in my testing).
The above code seems to work, but the problem is it works only from the second time I click any anchor. Why doesn't it fire on the first tie itself? I know it is related to hashchange, so the javascript is picking up from the second links onwards only because there is a hashchange. Can you give me a solution?
window.onload = function () {
function offsetAnchor() {
if (location.hash.length !== 0) {
window.scrollTo(window.scrollX, window.scrollY - 100);
}
}
// This will capture hash changes while on the page
window.addEventListener("hashchange", offsetAnchor);
// This is here so that when you enter the page with a hash,
// it can provide the offset in that case too. Having a timeout
// seems necessary to allow the browser to jump to the anchor first.
window.setTimeout(offsetAnchor, 1); // The delay of 1 is arbitrary and m
};
Hello
<div style="height: 300px;background:#ccc">spacer</div>
<div id="hello">Hello there!</div>
<div style="height: 300px;background:#ccc">spacer</div>
Hope this helps
I needed to be able to load a particular page in an iframe on demand, so I used a simple wrapper:
function updateFrame(url) {
frames[0].location = url;
}
Then I was asked to load the page to a particular point, which was non-trivial, since the pages were not within my control and there weren't always <a name> anchors to rely on. So some poking around showed that IDs could be used as anchors.
That is to say, you can scroll to <div id = "somewhere-down-the-line"> with:
updateFrame("http://host/page#somewhere-down-the-line");
except this call also scrolls the entire viewport up so that the above <div> goes to the top and everything in the parent page above it scrolls out of view.
How do I modify updateFrame(url) so that it scrolls the page within the <iframe> but leaves the rest of the page as it is?
This hack worked for me on Firefox 20.0.1/Windows. Essentially, I load the page first, then jump to the target:
function updateFrame(url) {
if (url.indexOf('#') > -1) {
mainPage = url.split('#')[0];
frames[0].location = mainPage;
}
frames[0].location = url;
}
I would like to be able to use this in other browsers as well. I have been trying to get it to work in Chrome. Maybe I'll even try Internet Explorer...
If a hack is ok, and what you're looking for is cross-browser try using scrollTop to reset where you were.
E.g. if it is the body that scrolls
function updateFrame(url) {
//save where you were
var oldScroll = document.body.scrollTop;
//this moves our body!
frames[0].location = url;
//move it back
document.body.scrollTop = oldScroll;
}
Of course if it doesn't actually scrolls the entire viewport and instead modifies a parent div or something, the scrollTop property will be on that element too.
Let me know if this works, but screws up the scrolling on the frame, because I can modify this to account for a difference between the two scrollTops
You could try turning the bolts yourself by detecting the height of the element you want, and forcing the scrollTop of the frame.
function updateFrame(url) {
//get the parts
var parts = url.split('#');
//go to the url
frames[0].location = parts[0];
//if there was an anchor
var anchor;
if (parts.length > 0 && parts[1].length > 0) {
//may want to account for a[name="PARTS[1]"] too
anchor = frames[0].document.getElementById(parts[1]);
//set the scroll of it yourself, using some sort of library to get "fullTop"
frames[0].document.body.scrollTop = anchor.fullTop();
}
}
Where "fullTop" is equivalent to the distance between the top of the iframe, and the element.
Like jQuery's .offset() or YUI's getXY(el).[1]
What worked for me on Firefox 20.0.1/Windows. Essentially, I load the page first, then jump to the target:
function updateFrame(url) {
if (url.indexOf('#') > -1) {
mainPage = url.split('#')[0];
frames[0].location = mainPage;
}
frames[0].location = url;
}
On Chrome 28.0/Windows, calling updateFrame(url) followed by setting document.body.scrollTop = 0 (thanks to this answer) had the desired effect, though only in the console. I am still testing on other browsers; a more elegant solution is always appreciated :)
As mentioned in the question, though, I would like to be able to use this in other browsers as well. Maybe I'll even try Internet Explorer...
I'm trying to use the browser scroll event to place a block of html based on the amount a user has scrolled. The code works but it is causing a huge performance issue which basically forces my browser to freeze.
Any insight as to why and what I could do to resolve this would be greatly appreciated.
<script type="text/javascript">
$('#content').scroll(function () {
var scroll = $('#content').scrollTop();
var $controls = $(".controls").clone();
if (scroll > 200) {
$(".controls").remove();
$('#header').append($controls);
}
else {
$(".controls").remove();
$('.banner').append($controls);
}
});
</script>
First, discovering elements in the DOM is an expensive activity, so cache your jQuery objects.
Second, .append() moves elements around so .clone() and remove() should be unnecessary.
This gives :
var $$ = {//cache of jQuery objects
content: $('#content'),
controls: $(".controls"),
header: $("#header"),
banner: $('.banner')
};
$('#content').scroll(function() {
$controls.appendTo(($$.content.scrollTop() > 200) ? $$.header : $$.banner);
});
Now, you can work on reducing the frequency at which the handler is called, which can be achieved as follows :
var $$ = {//cache of jQuery objects
content: $('#content'),
controls: $(".controls"),
header: $("#header"),
banner: $('.banner')
};
var scrollHandling = {
allow: true,
reallow: function() {
scrollHandling.allow = true;
},
delay: 50 //(milliseconds) adjust to the highest acceptable value
};
$('#content').scroll(function() {
if(scrollHandling.allow) {
$controls.appendTo(($$.content.scrollTop() > 200) ? $$.header : $$.banner);
scrollHandling.allow = false;
setTimeout(scrollHandling.reallow, scrollHandling.delay);
}
});
The scroll function is called for every movement of the scrollbar. That can potentially be a lot of times, so you need to be careful how much code you are running and certainly how much manipulation of the DOM you are doing.
In this case, you'll be repeating a lot of the same actions (clone, append, remove) as the scrolling is occurring, but it appears that you only want to flip between two states as you cross back and forth over that 200 scroll value. You could potentially solve most of the performance issues with something like this:
<script type="text/javascript">
var thresholdCrossed = false;
$('#content').scroll(function () {
var scroll = $('#content').scrollTop();
var threshold = 200;
if (scroll > threshold && !thresholdCrossed) {
var controls = $(".controls").clone();
$(".controls").remove();
$('#header').append(controls);
} else if (scroll <= threshold && thresholdCrossed) {
var controls = $(".controls").clone();
$(".controls").remove();
$('.banner').append(controls);
}
thresholdCrossed = scroll > threshold;
});
</script>
You can do additional work that some of the other answers describe to help reduce wasted resources, but this should give you a general idea of how to hopefully help the main performance concern of constantly modifying the DOM as you scroll. I would probably suggest some combination of this along with the answer provided by #Kolink so that you are truly limiting the DOM manipulation to the least amount necessary.
You are cloning all .controls elements every tick of scrolling, even when it is not needed.
I would suggest cloning the controls on ready and setting it to display:none. Then, just toggle the display based on the scroll position.
On re-reading your question, it looks like you're just moving the controls element from the header to the banner? In that case, you don't even need a clone. However I strongly suggest adding id="controls" to the controls element, and id="banner" - use IDs instead of classes in general if there is only one.
document.getElementById('content').onscroll = function() {
document.getElementById(this.scrollTop > 200 ? "banner" : "header")
.appendChild(document.getElementById('controls'));
};
Every time the scroll bar moves jQuery has to go into the DOM to get the variables you references. To start cache the variables so jQuery doesnt have to do twice the work.
var content = $('#content');
content.scroll(function(){
});
UPDATE:
I was able to get my scroller working as desired but I feel like I have hacked around the actual issue and would love it if anyone has a more solid answer, I've updated and noted in the snippets below the new jQuery I'm using.
I'm using iScroll-4 (http://cubiq.org/iscroll-4) for an iPad/Android web app, everything's working perfectly with the swipes and scrolling but I have a table of contents at the beginning of the app that allows users to jump to specific areas of the scroller --
I'm using the iScroll function scrollToElement(element, duration) in order to jump to the different areas. Also using scrollToPage(page, duration) to allow the user to manually navigate forward and backward one page at a time.
While watching the console logs the currPageX variable updates when I navigate with the scrollToPage function and when I swipe, but when using the scrollToElement the currPageX variable does not update.
Therefore if I jump to an element and then navigate forward with scrollToPage('next', 0) it will go backwards and navigate me to the next page after the table of contents.
I have tried using the scroll.refresh() function after scrollToElement, before, putting the function inside a timeout, etc. and I can't figure out why the currPageX is not updating.
Here's a snippet of the jQuery code that I'm using the two different functions:
// TO NAVIGATE FORWARD AND BACKWARDS
$('span.control').on('click', function() {
var slideDir = $(this).attr('data-dir');
if (slideDir == 'prev') {
var tehPg = tehScroll.currPageX-1;
} else if (slideDir == 'next') {
var tehPg = tehScroll.currPageX+1;
}
tehScroll.scrollToPage(tehPg, 0);
return false;
});
// TO JUMP FROM CONTENTS
$('li[data-page="toc"] span').on('click', function() {
var toPage = $(this).attr('data-page');
tehScroll.scrollToElement('li[data-page="'+toPage+'"]', 800);
// ADDED THE FOLLOWING LINE TO MANUALLY SET currPageX after scrolling!
tehScroll.currPageX = $('#slides li[data-page="'+toPage+'"]').index();
return false;
});
Did you consider using jquery-mobile-iscrollview widget plug-in? - there is a function scrollToPage(pageX, pageY, time), works well for me...
best
M