Scrolling not working on page reload - Angular ng-repeat bug? - javascript

I'm having a weird bug with Angular.
Here's the context: The page loads with everything fitting in the window. The user clicks on something and a bunch more content loads. An ng-repeat pushes the document height below the fold, the vertical scrollbar appears, and the user can scroll down to view the rest of the content. Everything works.
If I scroll to the bottom, and then refresh the page, the scroll position jumps back to the top of the page and everything is reset. If I click to load the additional content again, it shows up, but the scroll is 'locked'. The scrollbar still appears, but scrolling with the scrollwheel is impossible.
All the site functionality works, but I cannot scroll down with the mouse (or using the arrow keys). If I click and drag the scrollbar down, it fixes the issue and scrolling is restored.
To make matters worse, the bug only happens about 10% of the time. The rest of the time, the page happily scrolls just fine.
Some clues I've found:
Inside the Angular controller, I added the following:
console.log( $(window).scrollTop() );
This snippet logs whatever the scrollTop value was before the page load. It logs this every time, even the times when scrolling works fine.
Even more confusingly, in the HTML itself, I added the following event listener:
$(window).on("scroll", function(e) {
console.log("Scroll event logged. Current scroll position: ", $(window).scrollTop());
})
This consistently logs, on page load, that a scroll event was detected, and that the scrollTop is now 0.
Even more confusingly, sometimes, when the scrolling does work and the additional content is added, it jumps me straight to the bottom, as if I started scrolling from the bottom.
What I think is happening
I think when the page reloads, it retains its now-impossible scrollTop value. Since the scrollTop is below the document height, the browser tries to scroll back up to zero, but this mechanism is buggy and unpredictable.
Does anyone know a surefire way to reset the scroll position to the top of the page, on page load? Simply using $(window).scrollTop(0) isn't working. I'm hoping someone understands the lower levels of this mechanism (or knows another way around it).
I've tried a number of dirty hacks (like scrolling the window by 1px when the additional content gets loaded), and nothing seems to be working. I've been stuck on this for a couple hours now and it's driving me a bit insane.
Also, I'd love to put this in a JSFiddle, but since you can't refresh a JSFiddle page without losing all the code (or having it reset to a non-running state), it wouldn't work. Sorry!

Related

Executing JavaScript when a div content is changed (or t he div is resized)

This will be a long post, sorry for that, I'd just like to explain what I'm trying to do and why, and what I've tried so far.
I was asked to put a banner (legal disclaimer) on top of a page that stays on top no matter how the page is scrolled. After going down the rabbit hole of trying to make a <div> with position: sticky work, I gave up and went with position: fixed.
(Long story short, there's too much going on the page in terms of css styles that I don't have control over/don't have the resources to investigate and fix, that's causing the sticky to not work and the <div> to scroll with the rest of the page.)
position: fixed made the <div> stick to the top of the page, but it caused another problem: The div now overlaps the top of the scrollable page content. In other words, even when scrolled all the way to the top, a bit of the content is covered by the div. Important content. The top menu bar.
To get around this I put another empty div on the top of the page just to take up space. I effectively made the scrollable content of the page a bit taller. This works fine, as long as the user doesn't resize the window. If they do that, the disclaimer div changes height, but the padding div doesn't. So I turned to JavaScript:
$(window).on('resize', function () {
$("#disclaimer-padding").height($("#disclaimer-container").height());
});
I realize I'm well into the workaround/clumsy hack territory, but what was I supposed to do? The damn sticky thing wouldn't work. Anyway, this works, except for one last usecase:
If the user loads the page while not in fullscreen, or they have a smaller screen, or a device in portrait mode, the padding div height won't match the height of the disclaimer when the page loads.
I naturally tried putting the same code in $(window).on('load') or $(document).on('ready'). But the problem is that the content of the disclaimer, like many other elements on the page, is loaded by AngularJS (which grabs it from backend, which grabs it from the database or server-side cache) and both window.load and document.ready fire long before Angular is done loading and the final height of the div is known.
My next thought was "I'll listen for the context of the div to change, and trigger the resize then". So I used a MutationObserver.
const targetNode = document.getElementById('some-id');
const config = { attributes: true, childList: true, subtree: true };
let observer = new MutationObserver(callback);
function callback() {
$("#disclaimer-padding").height($("#disclaimer-container").height());
}
observer.observe(targetNode , config);
I tried hooking this up in window.load and in document.ready. It didn't work, which really surprised me, because when I tried it in a minimal working test page, it worked like a charm. But alas, in my project, the callback function just never got called. I have no idea why.
In the end I implemented a truly ugly solution (which I found somewhere here on StackOverflow). In window.load I call a function that periodically checks the contents of my diclaimer div. If the content has changed (until it's loaded it says something like #Application.DislaimerContent) then it will set the height, otherwise it will schedule itself to run again in 200 miliseconds using setTimeout().
I don't like this solution, but I was running out of time and this is the only thing that worked. But I feel like if my supervisor ever sees this, he'll give me an earful and make me fix it.
So the question is, how do I fix it? How do I make the code register when the div has loaded, and resize the other div accordingly?

Why is the saved scroll position affected by a shrinking navbar on page reload?

I know there are a lot of similar questions out there, but I couldn't find one that answers this in my case. I am using an IntersectionObserver to trigger a navbar style (to shrink it). The navbar has the sticky-top class, with the viewport as the root (implicit).
The subtle issue is, on reloading the page, the scroll position is remembered, but it seems to adjust as the navbar shrinks. This results in a gradual "ratcheting up" of the page on subsequent reloads. I don't expect users to continuously reload the page, but I wanted to thoroughly understand what is going on, and why this is occurring.
Here is a jsfiddle which demonstrate the issue.
To see the issue, scroll the content frame down until the navbar shrinks, and then right click the frame to reload just that frame (since reloading the whole jsfiddle page will reset everything, including the scroll position).
One theory I have is that the scroll position is being restored prior to the position: sticky; style becoming effectively position: fixed;, thus the height of the navbar is throwing off the scroll position.
However, the scroll position seems to shift only by a small amount. This leads me to believe that the browser is smart enough to account for the sticky element being removed from the page flow, and accounts for the difference, but it cannot know ahead of time that the element will shrink. This still confuses me though, since the size of the navbar after it is removed from the flow of the page should not be relevant to the restored scroll position.
I have a hunch that there is some kind of race condition causing this, where the relevant events are the scroll restore, the IntersectionObserver event on the trigger element, and the application of the skinny navbar styles. Can anyone shed some light on this?

JavaScript/Vue.js: How to execute multiple methods at once before the DOM updates

Here is what I am trying to do: When the user is clicking a button, a transparent overlay is opening. The background shouldn't be scrollable but stay at the scroll position. So what I am doing at the moment is that once the button is clicked, I safe the current scroll position via window.scrollY, then add overflow: hidden to both the html and body tag (which unfortunately scrolls the page to the very top), then proceed to scroll to the saved position inside the main div of the website. In most browsers these steps aren't noticeable so it seems like everything just stays at the same position. In Safari however, you can see that for a few ms the background scrolls to the very top and then back again.
So what I would like to know is how to execute multiple methods at once before the DOM updates. Or maybe you can think of another way of doing this?
Thank you!

Javascript appendChild resets scroll of other page elements in IE

I am having trouble with the scroll of divs on pages when viewed in IE, no problem in Chrome or FF
I have a div on a page which has a scroll bar (vertical) and if it has been scrolled down when I use the function appendChild to either the body or any other part of the page the scroll on the other div resets back to the top.
Please can you offer any suggestions why this might happen, perhaps something to do with redraw/reflow but why only IE?
Another thing I have noticed which may be the cause is that it only seems to happen on pages with position fixed on them.
Internet Explorer 9 was finding a dead class reference on an element on my page when rendering. When it did this it was loading each element fresh again in this case without saving how far it was already scrolled down.

How to make the browser stay scrolled at a fixed posistion?

How can I keep the browser from scrolling, or how can I make the browser continually scroll to a fixed posistion?
I am working on a library for the Nintendo 3DS browser. I made the page fit perfectly within the browser, but the up arrow makes it scroll because the bottom screen is the only window recognized as the visible area.
I want to make it so the div #bottomScreen is the only thing in the bottom screen, and disabling scrolling is the only thing I can think that would work.
I have figured out how to scroll it to a said position via
document.body.scrollTop = 220;
How can I make it continually go to this position?
Making a repeating timer with setTimeout and putting the above code in it won't work. I believe it is because this only works prior to the page loading.
Any advice on how to enforce it?
It should work even after page load. Here's the code, although i'm not sure what the intent of the code is, might be annoying to the user.
setInterval( function(){ document.body.scrollTop = 200 }, 500 ); // set your time
A more elegant solution would be to disable scrolling when that method is called (to scroll to the position of 220 from top or whatever), and re-enable it whenever the appropriate action has been taken by the user etc... jQuery example:
$('body').css('overflow', 'hidden'); // removes scrollbars entirely
$('body').css('overflow', 'auto'); // re-enable scrolling
Otherwise use setInterval() with a very short interval like 10ms to repeatedly fire your scroll function. If you are going to do this it would be wise to add some logic to see if the window is already scrolled to approximately the right position (allow for +/- 10px or something) so it isn't extremely jarring for the user.
The best way I've seen on some sites (like twitter I think or facebook when an image pops up) which is to set the overflow property to hidden on the body element. This prevents any scrolling so all you need to worry about is the position of content when you do that.
I guess you would need to wrap the content in some sort of container element and when you change the overflow of the body element you also set the y-coordinate of the container to reveal the specific area of the page being looked at.
This is by far the best thing I have seen to achieve that effect because it doesn't require timers etc.
You could add a event listener for the scroll event, and then set the position then.

Categories