IE and firefox behave unpredictably updating location.hash on scroll() - javascript

I'm trying to update the location.hash by checking what div is currently active in a long scrolling site. This works fine is chrome, but is failing in Firefox and IE. I have tested with console.log and
I am able to see the id in console, but as soon as I try to feed this into the location hash the scrolling ceases to work on the page, or jumps around unpredictably!
$(window).scroll(function () {
$('div').each(function(){
if (
$(this).attr('class')=='article' && $(this).offset().top < window.pageYOffset + 10
&& $(this).offset().top + $(this).height() > window.pageYOffset + 10
) {
window.location.hash = $(this).attr('id')
}
});
});

First you need to understand that the scroll event fires many times a second. Combine that with the methodology you are using to search the DOM... look for every div, then filter all those div's for the ones you want and do this all many times a second...you are overloading the browser needlessly.
Scroll the window in this simple demo and see how often your script is firing; http://jsfiddle.net/tRx2P/
If you are going to search the DOM for the same elements repeatedly, caching them into variables will give a big performance boost. Searching the DOM is a lot more expensive than searching a cached variable containing elements
/* use jQuery selector that already filters out all the other `div` in page*/
var $articles= $('.article');
/* now use the variable inside your functions*/
$( window).scroll(function(){
$articles.each(.....
/* use the same cache principles for "$(this)" to help avoid needless function calls*/
})
Now the really important part is you should throttle back the number of times a second these need to be checked. There is no benefit in updating the hash multiple times while the user is still scrolling...and overloading the browser to do it
This modification of the demo only fires the code when user hasn't scrolled for 300ms which could likely be increased to 1/2 second or even more. It does this by constantly setting a timeout delay
http://jsfiddle.net/tRx2P/2/
You should be able to now adapt these concepts to the code you have

Related

getBoundingClientRect during throttled scroll event handling

I faced an issue during my project developing, it's related to a difference between getBoundingClientRect values with and without preventive break points during debugging. Trying to minimize repro, I got following.
const scrollHandler = () => {
setTimeout(() => {
const top = document.getElementById('test').getBoundingClientRect().top;
console.log(top);
});
};
document.getElementById('viewport').addEventListener('scroll', scrollHandler);
where the viewport is just scrollable div with fixed height. The content of the viewport is big enough to be able to fire at least one scroll event. I also created Plunker demo. All magic happens inside of setTimeout callback.
Now the steps. Success scenario.
Set break point at the beginning of the setTimeout callback.
Fire scroll event.
Make "step over" to obtain top value.
Execute document.getElementById('test').getBoundingClientRect().top in the console.
The results of 3 and 4 are the same.
Failed scenario.
Set break point at the end of the setTimeout callback.
Fire scroll event.
Get the value of top variable (no action is needed).
Execute document.getElementById('test').getBoundingClientRect().top in the console.
The results of 3 and 4 are not the same.
To avoid any misunderstanding with this repro, I recorded a short demo movie with the above steps.
In my project I'm doing calculations in a similar environment (throttled scroll events) and getting wrong results that don't match expectations. But when I debug it, I'm getting different picture; a preventive break point fixes calculations.
Is it a bug or known issue? Or did I miss something? should I refuse to use getBoundingClientRect in such a situation?
I'm not sure what it is that you are looking for but assume your animation on scroll isn't working as expected.
There is a tip on throttling scroll here that is incorrect. Let's say you throttle to every 30 milliseconds. If page stops scrolling 25 milliseconds after last animation then you're stuck with a scroll position 25 milliseconds before it stopped scrolling.
Maybe a better way to do it is this (can test in the console on this page):
var ol = document.querySelectorAll("ol")[2];
window.onscroll = ((lastExecuted)=>{
const expensiveHandler = e => {
lastExecuted=Date.now();
//do some animation maybe with requestAnimationFrame
// it may prevent overloading but could make animation
// glitchy if system is busy
console.log("1:",ol.getBoundingClientRect().top);
};
var timerID
return e=>{
if(Date.now()-lastExecuted>19){
//only animate once every 20 milliseconds
clearTimeout(timerID);
expensiveHandler(e);
}else{
//make sure the last scroll event does the right animation
// delay of 20 milliseconds
console.log("nope")
clearTimeout(timerID);//only the last is needed
timerID=setTimeout(e=>expensiveHandler(e),20);
}
}
})(Date.now())

Chrome ignoring hashes in URL

I've spent quite a while trying to find answers for this issue, but haven't had any success. Basically I need to scroll the user to the contact portion of the website when they go to healthdollars.com/#contact. This works just fine in Safari, but in Chrome I haven't had any luck. I've tried using jQuery/Javascript to force the browser to scroll down, but I haven't been able to.
Does anyone have any ideas? It's driving me crazy - especially since it's such a simple thing to do.
Not a full answer but in Chrome if you disable Javascript I believe you get the desired behavior. This makes me believe that something in your JavaScript is preventing default browser behavior.
It looks to me like the target element doesn't exist when when page first loads. I don't have any problem if I navigate to the page and then add the hash.
if (window.location.hash.length && $(location.hash)) {
window.scrollTo(0, $(location.hash).offset().top)
}
check for a hash, find the element's page offset, and scroll there (x, y).
edit: I noticed that, in fact, the page starts at #contact, then scrolls back to the top. I agree with the other answerer that there's something on your page that's scrolling you to the top. I'd search for that before adding a hack.
You can do this with JS, for example` if you have JQuery.
$(function(){
// get the selector to scroll (#contact)
var $to = $(window.location.hash);
// jquery animate
$('html'/* or body */).animate({ scrollTop: $to.offset().top });
});
The name attribute doesn't exists in HTML 5 so chrome looks to have made the name attribute obsolete when you use the DOCTYPE html.
The other browsers have yet to catch up.
Change
<a name="contact"></a>
to
<a id="contact"></a>
Maybe this workaround with vanilla javascript can be useful:
// Get the HTMLElement that you want to scroll to.
var element = document.querySelector('#contact');
// Stories the height of element in the page.
var elementHeight = element.scrollHeight;
// Get the HTMLElement that will fire the scroll on{event}.
var trigger = document.querySelector('[href="#contact"]');
trigger.addEventListener('click', function (event) {
// Hide the hash from URL.
event.preventDefault();
// Call the scrollTo(width, height) method of window, for example.
window.scrollTo(0, elementHeight);
})

scrollTop not working automatically

I know there are a couple of questions to scrollTop already out there but I haven't really seen anything resembling my problem.
Using jquery 1.7.2 on an IE9 we have a page with three Tabs (JqueryUI).
The Data is connected and that resulted in us only having the current tab on the page. Changing tabs will remove the unseen one and reload the one we jump into.
The Scroll-Positions are stored correctly in variables on the base page but trying to set that position in the document-ready-function does not work.
An alert shows the correct number, so the function is actually called but the scrollbar does not move.
Calling the same function with a button on the page afterwards however works perfectly.
The document-ready-function on the tab's jsp is quite simple:
<script type="text/javascript">
jQuery(document).ready(function(){
setAhaScrollbar();
});
</script>
and the called function is quite simple as well:
function setAhaScrollbar() {
var scrollWert = $('#scrollbarAnhaengeartikel').val();
alert(scrollWert);
$('#anhaengeGridScrollable').scrollTop(scrollWert);
}
Called from document-ready it does nothing. Called from a button later on it works fine.
The div where the scroll position is supposed to be set is defined with overflow: auto and a fixed height
crollTop( value )
Description: Set the current vertical position of the scroll bar for each of the set of matched elements.
.scrollTop( value )
value
Type: Number
An integer indicating the new position to set the scroll bar to.
More Information
As the documentationsaid value should be number.
Try
var scrollWert = Number($('#scrollbarAnhaengeartikel').val());
or
var scrollWert = parseInt($('#scrollbarAnhaengeartikel').val());
Apparently it was primarily a timing problem. Maybe there were still things going on when document ready fired.
Changing that function to
jQuery(document).ready(function(){
setTimeout("setAhaScrollbar()", 500);
});
did the trick so my problem is solved.

Javascript scroll function slow, lots of "Timer Fired: onloadwff.js:310" weasel events in Chrome

I'm trying to debug a page which is acting a little slow in Chrome, think it might be an issue with the following javascript code:
$(document).ready(function() {
function navScroll(distance){
$(window).scroll(function() {
var scrollTop;
if(distance){
scrollTop = distance;
}else{
scrollTop = 150;
}
if($(window).scrollTop() >= scrollTop) {
if(!($('#mainNav').hasClass('showNav'))) {
$('#mainNav').addClass('showNav');
}
} else {
if($('#mainNav').hasClass('showNav')) {
$('#mainNav').removeClass('showNav');
}
}
});
}
if($('.header-image-base').length){
var windowHeight = $(window).height();
$('.header-image-base').css('height', windowHeight);
navScroll(windowHeight);
}else{
navScroll();
}
});
When I look in Chrome's console's 'timeline' panel, and press record, this is what I see:
Any ideas what is happening here? I can't find any references to this on google and no idea how to remedy it.
Your page is slow most likely because you’re attaching a handler to the window scroll event—this is not a good practice as explained below:
It’s a very, very, bad idea to attach handlers to the window scroll event. Depending upon the browser the scroll event can fire a lot and putting code in the scroll callback will slow down any attempts to scroll the page (not a good idea). Any performance degradation in the scroll handler(s) as a result will only compound the performance of scrolling overall. Instead it’s much better to use some form of a timer to check every X milliseconds OR to attach a scroll event and only run your code after a delay (or even after a given number of executions – and then a delay). (source)
Your screenshot shows that onloadwff.js is located at chrome-extension://hdokiejnpimakedhajhdlcegeplioahd which means it’s part of the LastPass extension — as seen below. So it’s probably not related to your performance issue.
(archived screenshot)
Link - https://chrome.google.com/webstore/detail/lastpass-free-password-ma/hdokiejnpimakedhajhdlcegeplioahd

Trigger event on animation complete (no control over animation)

I've a scenario that requires me to detect animation stop of a periodically animated element and trigger a function. I've no control over the element's animation. The animation can be dynamic so I can't use clever setTimeout.
Long Story
The simplified form of the problem is that I'm using a third party jQuery sliding banners plugin that uses some obfuscated JavaScript to slide banners in and out. I'm in need of figuring out a hook on slideComplete sort of event, but all I have is an element id. Take this jsfiddle as an example and imagine that the javascript has been obfuscated. I need to trigger a function when the red box reaches the extremes and stops.
I'm aware of the :animated pseudo selector but I think it will need me to constantly poll the required element. I've gone through this, this, and this, but no avail. I've checked jquery promise but I couldn't figure out to use that in this scenario. This SO question is closest to my requirements but it has no answers.
P.S. Some more information that might be helpful:
The element isn't created by JavaScript, it is present on page load.
I've control over when to apply the plugin (that makes it periodically sliding banner) on the element
Most of the slideshow plugins I have used use changing classes at the end of the animation... You could extend the "addClass" method of jQuery to allow you to capture the class change as long as the plugin you use is using that method like it should:
(function($){
$.each(["addClass","removeClass"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
oldmethod.apply( this, arguments );
this.trigger(methodname+"change");
return this;
}
});
})(jQuery);
I threw together a fiddle here
Even with obfuscated code you should be able to use this method to check how they are sending in the arguments to animate (I use the "options" object when I send arguments to animate usually) and wrap their callback function in an anonymous function that triggers an event...
like this fiddle
Here is the relevant block of script:
(function($){
$.each(["animate"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
var args=arguments;
that=this;
var oldcall=args[2];
args[2]=function(){
oldcall();
console.log("slideFinish");
}
oldmethod.apply( this, args );
return this;
}
});
})(jQuery);
Well since you didn't give any indication as to what kind of animation is being done, I'm going to assume that its a horizontal/vertical translation, although I think this could be applied to other effects as well. Because I don't know how the animation is being accomplished, a setInterval evaluation would be the only way I can guess at how to do this.
var prevPos = 0;
var isAnimating = setInterval(function(){
if($(YOUROBJECT).css('top') == prevPos){
//logic here
}
else{
prevPos = $(YOUROBJECT).css('top');
}
},500);
That will evaluate the vertical position of the object every .5 seconds, and if the current vertical position is equal to the one taken .5 seconds ago, it will assume that animation has stopped and you can execute some code.
edit --
just noticed your jsfiddle had a horizontal translation, so the code for your jsfiddle is here http://jsfiddle.net/wZbNA/3/

Categories