I'm relatively new to programming and am working on a Chrome extension with a popup. I want to save the scroll position between popup invocations. It seems like I've found a lot of info on the internet, but so far I haven't been able to solve my problem. Saving the scroll position almost works within my extension, but I'm seeing 2 issue:
1) To save the scroll position at each scroll event, I use:
addEventListener('scroll', function(){
localStorage.scrollTop = document.body.scrollTop;
});
When the popup is opened, I use:
document.body.scrollTop = localStorage.scrollTop
This seems to work fine until my scroll position exceeds the popup height. The max popup height for visible content in a Chrome extension is 600px. document.body.style.height is also always a fixed value greater than 600px. When the scroll position is greater than document.body.style.height - 600px, say 900px - 600px, document.body.scrollTop is reset to 300px. Even if the last scroll position (document.body.scrollTop) was 400px before the popup is closed, scroll position is reset to 300px when the popup is re-opened. Obviously, I get the wrong scroll position because the proper scroll position value of 400px (for example) is then overwritten by 300px.
However, it doesn't always happen. Sometimes I can properly save a scroll position of, say 500px with a window height of 900px, and other times I can't. I don't know for sure why this has any effect, but as the content in the popup is taller the problem seems to magically go away and the proper scroll position is saved.
How can I properly save the scroll position when the scroll position in this situation? Perhaps I'm doing something fundamentally wrong?
(This seems confusing to read. I hope it's possible to help with the code above.)
2) I think might be intertwined with the above issue, but I'm not sure. For every scroll event (first code block in this post), I see a pair of scroll events fired.
If I just open the popup but don't scroll the mousewheel, I see code inside my event listener fire. When the first event happens, document.body.scrollTop is reset to the "wrong" value (300px in the example above). I think this might be the root cause of both issues.
Shouldn't the event listener only fire if the mousewheel is moved, and thus the code inside the event listener doesn't execute if the popup is opened but the mousewheel isn't touched?
The issue was actually in the corresponding CSS code. The <div> wasn't scrollable, and so it didn't allow saving a scroll position bigger than the visible area of the popup.
The solution was to add the position: relative; and overflow-y: auto; (overflow-y: scroll; works as well) properties to the CSS element.
Once those properties were added, I could properly save the scroll position to an object.
I know this shouldn't be an answer, but I can't format code in a comment:
I'm going to go out on a limb and suggest you try test one of the basic assumptions: Are the scroll events firing in order?
Try this, see if adds some useful debugging info:
var scrollCounter = 0;
addEventListener('scroll', function(){
localStorage.scrollTop = document.body.scrollTop;
console.log({counter: scrollCounter, scrollTop: document.body.scrollTop});
scrollCounter++
})
See if the numbers seem to jump around, or if they go in progressive order.
Edit: P-code for a possible solution
I think I have an idea for a fix, though I use the word fix kind of loosely here.
var captureSemaphore; // Use this to flag exactly when we want the `scrollTop` captured.
captureSemaphore = true; // Go into catch mode
addEventListener('scroll', function(){
if (captureSemaphore) {
localStorage.scrollTop = document.body.scrollTop;
}
});
// then later, right before you open your popup
captureSemaphore = false; // Disable catch mode because the screen is about to change
openPopup(); // Open the popup
// Elsewhere
closePopup(); // Close the popup
captureSemaphore = true; // Go into catch mode
Related
first of all I want to say that it's not about content jumping!
I have a navbar and a sidebar which both have absolute position. after user scrolls 100 pixels I change both of them to fixed. but an odd action happens (not always!). wrappers of navbar and sidebar flush for a second. I tested it with different browsers and it does not depend on browser. I tried to reproduce the situation in this fiddle:
https://jsfiddle.net/addxmkgj/
(resize the screen as large as possible it happens in large screens)
-- Edit --
https://codepen.io/anon/pen/dJKBPe
codepen link added too.
Causes
Scrolling can generate scroll events quickly and handlers may need to either throttle scroll events to some extent (e.g. perform code action after scrolling has stopped) or be fairly lightweight functions that can execute quickly.
In addition scroll event handling is not synchronized with page update: if the mouse wheel initiates downward scrolling, scrolling can continue after the wheel is released (and similarly with touch event scrolling). The browser can scroll below a top position of 100px before scroll event handling has had a chance to catch up and change the positioning.
The result is the header jumps down from being partially off-screen to occupy a fixed position at top of screen. The faster the scroll action (or the busier the browser is) the more likely it is that jumping will be noticeable.
A secondary effect in desktop browsing is that when the side bar panel scrolls upwards past top of screen and moves down again, a visible patch of white screen "flashes" momentarily below the side bar before fixed positioning takes effect.
Experimental Remedies
Flashing of the side bar can be reduced but not necessarily fully eliminated, by increasing the height of the container. Changing the height to 150% with visible overflow met with some success:
.side-bar {
position: absolute;
height: 150%;
... /* more declarations */
This may or may not conflict with application requirements.
Some mitigation of navbar jumping can be achieved by using requestAnimationFrame call backs to monitor scrollTop values and change positioning as necessary. This does not use scroll event handling as such:
$(document).ready(function() {
$(window).resize(function() {
if( $(window).width() > 850) {
$('.navbar').css('display', 'block');
} else {
$('.navbar').css('display', 'none');
}
});
scrollTo(0, 0);
var num = 100;
var bAbsolute = true;
function checkScroll() {
var newTop = $(window).scrollTop();
if( bAbsolute && newTop >= num) {
$('.navbar').css('position', 'fixed');
$('.navbar').css('top', '0');
$('.side-bar').css('position', 'fixed');
$('.side-bar').css('top', '0');
bAbsolute = false;
}
if( !bAbsolute && newTop < num) {
$('.navbar').css('position', 'absolute');
$('.side-bar').css('position', 'absolute');
$('.navbar').css('top', '100px');
$('.side-bar').css('top', '100px');
bAbsolute = true;
}
requestAnimationFrame( checkScroll);
}
requestAnimationFrame( checkScroll)
});
This code showed an improvement in jump reduction but was not perfect. It is not particularly a JQuery solution and calls requestAnimationFrame directly.
One option, of course, is to do nothing given browser timing constraints.
Update
This MDN guide for Scroll linked effects explains the root cause problem better than I was able to:
most browsers now support some sort of asynchronous scrolling .... the visual scroll position is updated in the compositor thread and is visible to the user before the scroll event is updated in the DOM and fired on the main thread ... This can cause the effect to be laggy, janky, or jittery — in short, something we want to avoid.
So the absolutely positioned elements can scroll off screen (to some extent) before scroll handlers are notified of a new scroll position.
The solution going forward is to use sticky positioning (see the scroll effects guide above or the CSS position guide. However position:sticky swaps between relative and fixed position so the HTML would need redesigning to accommodate this.
Sticky positioning is also leading edge technology at January 2018, and not yet recommended for production use on MDN. A web search for "JQuery support sticky position" revealed a choice of JQuery plugin support.
Recommendation
Potentially the best-case compromise may be to redesign the HTML to use sticky positioning and include a JQuery plugin that uses native support when available or a polyfill when not - site visitors with supporting browsers will get the best experience, those with older browsers will get functional support.
Reference Link: http://old.crazyripples.com/?debug=1
So, I am using jQuery and the jQuery fullPage plugin. It helps me achieve frame by frame scrolling.
Now, for internal divs where the vertical height is greater than the window height I have used some functions where I simply check for the scrollbar position and stopPropagation to the plugin so that inner scrollbar scrolls without shifting the frame.
All works fine with chrome and since I built with chrome I used some calculations that I observed on chrome. But firefox is showing different results, especially with scrollTop.
I am aware of the fact that there can be a difference in height but if you see the logs in the reference link you will see, the height is almost the same(even if it isn't for you, its the scrollTop value that is an issue).
Here is the code that I am using to decide whether to stopPropagation or not.
$(document).on("keydown",function(e){
var story=$("#story"),
story_con=story.find(".container"),
story_top=story_con.scrollTop();
if(story.hasClass("active")){
// console.log(story_top,story_con.height(),story_con.innerHeight(),story_con.children("div").height(),story_con.children("div").innerHeight(),e.which);
console.log("Div ScrollTop: "+story_top, "Container Height: "+story_con.height(), "Container InnerHeeight: "+story_con.innerHeight(),"Conatiner Div Height: "+story_con.children("div").height());
//up arrow
if(story_top==0 && e.which==38){
console.log("prev frame")
}
//down arrow
//chrome 280+432 >= 676 i.e. 712>=676 true
//firefox 207+429 >= 676 i.e 636>=676 false
else if(story_top + story_con.height() >=story_con.children("div").height() && e.which==40){
console.log("next frame");
}
else{
story_con.focus();
console.log(story_con[0]);
console.log("stopped propagation");
e.stopImmediatePropagation();
}
return;
}
});
And this is how I am calling the plugin:
$('#main').fullpage({
sectionSelector:'.frame',
scrollingSpeed:800,
responsiveWidth:1025,
normalScrollElements: '#story, #startups, #services',
});
Replication:
Go to the reference link. Navigate to the second section(Our Story) by either scrolling, arrow keys or the menu. Now, only use arrow keys, the frame should scroll normally, but when the scroll completes, it should go to the next frame(does so in Chrome). See js logs for more details.
I would love it if anyone could help me in any way. I have been trying to debug this for sometime. Maybe I am focussing on the wrong issue? Any help will be appreciated.
P.S. I know the plugin offers a scrollOverflow option. That was inducing more issues than this approach.
Update: Since I wasn't able to find any solution so specific, I changed my approach of detecting if the frame scrollbar had reached its end. I used this:
//down arrow
else if(container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight){
console.log("next frame");
}
*This is the start of my answer as I continue to debug to give you better details.
Currently using Firefox version 49.0.1 on window sizes smaller than 1280px your content inside your container classes are too tall.
Current proof:
In your divs, #story, #partners, and #services, if you where to give the div that is the direct child of div.container in each of the major sections a height: 200px all of your arrow down keypresses will work.
Edit 1
In magic.js line 111 is where your issue starts.
else if(story_top + story_con.height() >=story_con.children("div").height() && e.which==40){
The content within each of your containers, story_con.children("div").height(), is sometimes greater than story_top + story_con.height() so it does not go into the next page path that you desire.
Of course this all depends on the height of the window.
Your debug logging proving my point:
The Simple answer would be because Chrome and Firefox render pages differently. To understand why you get the differences in rendering have a look at this Explanation
I hope this clears it out.
This may come as a huge surprise to some people but I am having an issue with the IE browser when I am using the $(window).scroll method.
My goal:
I would like to have the menu located on the left retain it's position until the scroll reaches > y value. It will then fix itself to the top of the page until the scroll returns to a < y value.
My error:
Everything seems just fine in Chrome and Firefox but when I go to Internet Explorer it would seem the browser is moving #scroller every time the scroll value changes, this is causing a moving/flickering event.
If someone could point me to a resource or give me a workaround for this I would be very grateful!
Here is a fiddle:
http://jsfiddle.net/CampbeII/nLK7j/
Here is a link to the site in dev:
http://squ4reone.com/domains/ottawakaraoke/Squ4reone/responsive/index.php
My script:
$(window).scroll(function () {
var navigation = $(window).scrollTop();
if (navigation > 400) {
$('#scroller').css('top',navigation - 220);
} else {
$('#scroller').css('top',183);
$('#scroller').css('position','relative');
}
});
You might want to take a look at the jQuery Waypoints plugin, it lets you do sticky elements like this and a lot more.
If you want to stick with your current method, like the other answers have indicated you should toggle fixed positioning instead of updating the .top attribute in every scroll event. However, I would also introduce a flag to track whether or not it is currently stuck, this way you are only updating the position and top attributes when it actually make the transition instead of every scroll event. Interacting with the DOM is computationally expensive, this will take a lot of load off of the layout engine and should make things even smoother.
http://jsfiddle.net/WYNcj/6/
$(function () {
var stuck = false,
stickAt = $('#scroller').offset().top;
$(window).scroll(function () {
var scrollTop = $(window).scrollTop();
if (!stuck && scrollTop > stickAt) {
$('#scroller').css('top', 0);
$('#scroller').css('position','fixed');
stuck = true;
} else if (stuck && scrollTop < stickAt) {
$('#scroller').css('top', stickAt);
$('#scroller').css('position','absolute');
stuck = false;
}
});
});
Update
Switching the #scroller from relative to fixed removes it from the normal flow of the page, this can have unintended consequences for the layout as it re-flows without the missing block. If you change #scroller to use an absolute position it will be removed from the normal flow and will no longer cause these side-effects. I've updated the above example and the linked jsfiddle to reflect the changes to the JS/CSS.
I also changed the way that stickAt is calculated as well, it uses .offset() to find the exact position of the top of #scoller instead of relying on the CSS top value.
Instead of setting the top distance at each scroll event, please consider only switching between a fixed position and an absolute or relative position.All browsers will appreciate and Especially IE.
So you still listen to scroll but you now keep a state flag out of the scroll handler and simply evaluate if it has to switch between display types.
That is so much more optimized and IE likes it.
I can get flickers in Chrome as well if I scroll very quickly. Instead of updating the top position on scroll, instead used the fixed position for your element once the page has scrolled below the threshold. Take a look at the updated fiddle: http://jsfiddle.net/nLK7j/2/
Is there a way in javascript to bind an event handler to a horizontal scroll as opposed to the generic scroll event which is fired when the user scrolls horizontally and vertically? I want to trigger an event only when the user scrolls horizontally.
I searched around for an answer to this question, but couldn't seem to find anything.
Thanks!
P.S. My apologies if I'm using some terminology incorrectly. I'm fairly new to javascript.
UPDATE
Thanks so much for all your answers! In summary, it looks like you are all saying that this isn't supported in javascript, but I that I can accomplish the functionality with something like this (using jQuery) (jsFiddle):
var oldScrollTop = $(window).scrollTop();
$(window).bind('scroll', function () {
if (oldScrollTop == $(window).scrollTop())
//scrolled horizontally
else {
//scrolled vertically
oldScrollTop = $(window).scrollTop();
}
});
That's all I needed to know. Thanks again!
Answering from my phone, so unable to provide code at the moment.
What you'll need to do is subscribe to the scroll event. There isn't a specific one for vertical/horizontal.
Next, you'll need to get some measurements about the current display area. You'll need to measure the window.clientHeight and window.clientWidth.
Next, get window.top and window.left. This will tell you where position of the viewport is, ie if it's greater than 0 then scroll bars have been used.
It's pretty simple math from here to get what you need. If no one else has provided a code example in the next few hours I'll try to do so.
Edit:
A bit further information.
You must capture the scroll event. You also need to store the initial window.top and window.left properties somewhere. Whenever the scroll event happens, do a simple check to see if the current top/left values differ from the stores value.
At this point, if either are different you can trigger your own custom events to indicate vertical or horizontal scrolling. If you are using jQuery, this is very easy. If you are writing js without library assistance, it's easy too but a little more involved.
Do some searches for event dispatching in js.
You can then write any other code you want to subscribe to your custom events without needing to tie them together with method calls.
I wrote a jQuery plugin for you that lets you attach functions to the scrollh event.
See it in action at jsfiddle.net.
/* Enable "scrollh" event jQuery plugin */
(function ($) {
$.fn.enableHScroll = function() {
function handler(el) {
var lastPos = el
.on('scroll', function() {
var newPos = $(this).scrollLeft();
if (newPos !== lastPos) {
$(this).trigger('scrollh', newPos - lastPos);
lastPos = newPos;
}
})
.scrollLeft();
}
return this.each(function() {
var el = $(this);
if (!el.data('hScrollEnabled')) {
el.data('hScrollEnabled', true);
handler(el);
}
});
}
}(jQuery));
It's this easy to use:
$('#container')
.enableHScroll()
.on('scrollh', function(obj, offset) {
$('#info').val(offset);
});
Please note that scroll events come very fast. Even if you click in the scrollbar to jump to a new position, many scroll events are generated. You may want to adjust this code to wait a short time and accumulate all the changes in position during that time before firing the hscroll event.
You can use the same scroll event, but within your handler use the scrollLeft function to see if the scrollbar moved horizontally from the last time the event was fired. If the scrollbar did not move then just return from your handler. Otherwise update your variable to the new position and take action.
You can check if the the x value of the page changes and ignore your y value.
If the x value changes: There is your horizontal scroll.
With page-load, store the initial scrollbar positions for both in two variables (presumably both will be 0). Next, whenever a scroll event occurs, find the scrollleft and scrolltop properties. If the scrollleft property's value is different and scrolltop's value is same as compared to their earlier values, that's a horizontal scroll. Then set the values of the variables to the new scroll values.
No, there is no special event for scroll horizontal (it is for global scroll), but you can try to check the position of content by property .scrollLeft and if it's different from the previous value it means that the user scrolled content horizontally.
...the reason I ask is that Safari has a bug in its implementation of scroll() that is breaking my UI.
Imagine a page:
<body>
<div id="huge" style="width: 4000px; height: 4000px;"></div>
</body>
...so that you get both horizontal and vertical scrollbars. Now, normally when you press the scrollbar, the page scrolls (vertically). For the purposes of our fancy UI we don't want that to happen, so we squash the keyDown event:
window.onkeydown = function(e) {
if(e.keyCode == 32)
{
return false;
}
};
This works great...unless we decide that instead of preventing scrolling altogether, we want our own, custom scrolling behavior:
window.onkeydown = function(e) {
if(e.keyCode == 32)
{
window.scroll(foo, bar); // Causes odd behavior in Safari
return false;
}
};
In other browsers (Chrome, Firefox), this will instantaneously move the window's scroll position to the desired coordinates. But in Safari this causes the window to animate to the desired scroll position, similar to the scrolling animation that takes place if you press the space bar.
Note that if you trigger this scroll off of any key OTHER than the space bar, the animation does not take place; the window scrolls instantly as in other browsers.
If you happen to be scrolling, say, 1000 pixels or more, then the animated scroll can induce some serious discomfort.
I'm scratching my head trying to find a way around this. I suspect that there isn't one, but I'm hoping some God of Javascript here can suggest something. I'd really like to be able to use the space bar for this command.
If you know where in the document you want to scroll to then you can simply use named anchors. Setting document.location to the anchor (e.g. #top, #div50 or whatever) should be very reliable.
Use document.documentElement.scrollTop = ... (and document.body in some browsers).