I'm hoping someone can explain to me this odd behavior I'm seeing with window.scrollTo.
This doesn't work.
document.addEventListener('DOMContentLoaded', function() {
console.log('window.scrollY = ', window.scrollY);
window.scrollTo(0, 200)
console.log('window.scrollY = ', window.scrollY);
});
... well it does work, sort of ...
It works for the initial page load, but not refreshes (cmd + shift + r)...
The console output is:
window.scrollY = 0
window.scrollY = 200
So in that sense its working... except the page isn't scrolled, and when you type window.scrollY into the dev console it does indeed show 0.
So it would appear that the scroll is being set too early?
Where I get real confused is here:
var delay_ms = 0;
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
window.scrollTo(0, 200);
}, delay_ms);
});
SOMEHOW THAT WORKS, but not consistently.
However...
var delay_ms = 10;
Increasing the delay, even by only 10ms, improves the consistently dramatically! To the point where it hasn't failed on me yet.
At first I thought maybe DOMContentLoaded was simply too early for height to be properly evaluated, so I switched the event I was listening for to:
window.addEventListener('load', function() { /* ... */ });
According to the MDN Docs for .onload
The load event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images, scripts, links and sub-frames have finished loading.
So I can't imagine what would be setting the scroll to 0, after I'm setting it to 200...
Does anyone have any insight into what is happening with this timing?
Covering my bases
Yes there is space to scroll.
Here's a gist with some full reproduction code.
I'm on a mac, using chrome.
Related
I have quite a complex page (responsive) with a lot of scripts running, and rather than trying to recalculate all these scripts when the browser is resized, I simply wish to trigger a refresh when the browser is resized.
Trouble is, that a refresh on browser resize sends Android devices, and some versions of IE into a refresh loop. I was thinking therefore to only refresh the browser after it has been resized a certain amount, and see if that cures the problem, which is what I'm attempting to do below. Though for some reason, the browser won't refresh at all. The counter works fine, but the window.location.reload(); doesn't appear to be working here (works outside of this function fine). Any ideas as to why this is?
var $resizeTolerance = 0;
jQuery(window).on("resize", function(){
$resizeTolerance++;
console.log($resizeTolerance);
if($resizeTolerance > 10) {
window.location.reload();
}
});
I would strongly recommend not doing this, not least because you've said you don't want to "...[try] to recalculate all these scripts when the browser is resized..." but of course that's exactly what you're doing: When the page is reloaded, you're repeating all the calcuations. Instead, make sure the calculations are repeatable, then repeat them.
But with that out of the way:
What you're looking for is setTimeout, to do something after the user is done resizing the browser:
var loadedAt = Date.now();
var resizeHandle = 0;
jQuery(window).on("resize", function(){
if (Date.now() - loadedAt < 300) {
// ignore
return;
}
if (resizeHandle) {
clearTimeout(resizeHandle);
}
resizeHandle = setTimeout(reloadPage, 100); // 100ms = 1/10th second
});
function reloadPage() {
window.location.reload();
}
That will wait until a tenth of a second after the last resize event occurs, and then trigger a page refresh. You may want to adjust the time.
It also ignores any resize event within 300ms of when the code hooks up the handler, to ignore the initial resize event the browser apparently sends.
Live Example
Side note: This uses Date.now, which nearly all browsers have at this point. You can shim it in older browsers like this:
if (!Date.now) {
Date.now = function() {
return +new Date();
}
}
For a mockup-webpage used for research on interaction on websites, I created a mockup message-stream using JavaScript. This message stream is loaded in an IFrame and should show images at pre-set intervals and scroll to the bottom of the page after placing a new image at the bottom of the page. Getting the images to appear is working quite well with the provided script. However, both Chrome and IE seem to have trouble scrolling the page to the bottom. I would like to scroll to the bottom of the page as soon as the image is attached, but have for now added a 5 ms delay because that seemed to work sometimes. My questions are:
Is it okay to use document.body.scrollHeight for this purpose?
Can I make the scroll occur directly, or do I need a small interval before scrolling?
How to make the code scroll to the bottom of the IFrame directly after adding an image?
The following functions are used and trypost() is started onLoad:
function scrollToBottom(){
window.scrollBy(0,document.body.scrollHeight);
}
function trypost(){
point = point + 1;
if(point < interval.length){
//create and append a new image
var newImg = document.createElement("IMG");
newImg.src = "images/"+images[point]+".png";
document.getElementById('holder').appendChild(newImg);
//create and append a return
var br = document.createElement("br");
document.getElementById('holder').appendChild(br);
//time scroll to bottom (after an arbitrary 5 seconds)
var stb = window.setTimeout(scrollToBottom, 5);
//time next post
var nextupdate = interval[point]*400;
var tp = window.setTimeout(trypost, nextupdate);
}
}
My script section contains at least the following variables:
var point = -1;
var interval = [10, 10, 15];
var images = ["r1", "a1", "r2"];
This questions is a continuation of the project described in How to proper use setTimeout with IE?
To answer one of your questions, document.body.scrollHeight is appropriate for this purpose, but not if you're actually calling for document. That'll give you the scroll height of the document the iFrame is in, not the iFrame's document. The iFrame's document can be called upon by [insert variable for iFrame here].contentDocument.
Here's how I did it (and by that, I mean I tested it out with my own stuff to make sure it worked):
let i = document.querySelector('iframe')
i.contentWindow.scrollTo(0, i.contentDocument.body.scrollHeight);
That being said, the other answer by Thomas Urban will also work most of the time. The difference is only if your page has a really long scroll height. Most pages won't be longer than 999999 (for all I know that's impossible and that's why they chose that number), but if you have a page longer than that, the method I showed here would scroll to the bottom and the 999999 would scroll to somewhere not yet at the bottom.
Also note, if you have more than one iFrame, you're gonna want to query it in a different way than I did, like by ID.
Scrolling to bottom is always like scrolling to some ridiculously large top offset, e.g. 999999.
iframe.contentWindow.scrollTo( 0, 999999 );
In addition see this post: Scrolling an iframe with javascript?
If scrolling occurs too early it's probably due to images not being loaded yet. Thus, you will have to scroll as soon as added image has been loaded rather than on having placed it. Add
newImg.onload = function() { triggerScrolling(); };
after creating newImg, but before assigning property src.
If several events are required to trigger scrolling you might need to use some "event collector".
function getEventCollector( start, trigger ) {
return function() {
if ( --start == 0 ) { trigger(); )
};
}
You can then use it like this:
var collector = getEventCollector( 2, function() { triggerScrolling(); } );
newImg.onload = collector;
window.setTimeout( collector, 100 );
This way triggerScrolling() is invoked after 100ms at least and after image has been loaded for collector has to be invoked twice for triggerScrolling() being invoked eventually.
So I am trying to find the height of my images then add a top margin this enables me to impose a a vertical center.
I'm running this code, and on an F5 refresh I get correct height but on CTRL+F5 refresh it gives me a much smaller height. I kind of assume this is a loading/delay thing, but I am using document ready so not really sure whats going on. I tried using a php function but it slows the site down amazingly so have to stick with jquery.
you can see it working here. www.mzillustration.com
jQuery(document).ready(function() {
if (jQuery('.imagedisplay').length != 0) {
jQuery('.imagedisplay').each(function(){
var imgheight = jQuery(this).find('img').height();
var topmarg = ((240 - imgheight) / 2) ;
jQuery(this).find('img').css({'margin-top':topmarg+'px'});
});
});
any ideas/help/explanation much appreciated.
thanks
There is a difference between onload and onready.
ready will wait until the actual DOM-tree is done, while onload will wait until ALL of the content displayed on the page is finnished loading. So an explanation would be that when clearing the cache and refreshing, the dom tree finishes much faster than the images, hence giving the wrong heigh.
Try using the onload-event instead and see if you get a different result.
You need to insure the image has loaded before asking the browser for its height. If that image path is living in the html you will unfortunately need a jquery pluggin to handle this in a cross browser manner.
https://github.com/alexanderdickson/waitForImages
http://desandro.github.com/imagesloaded/
Or you will have to wait for the window.onload event which in jquery looks like this:
$(window).on('load', function(){....
However if you use the window load event, it will wait until ALL resources have loaded and depending on your site that can be a serious delay when compared to measuring just the image itself.
Or if you are comfortable with loading the image from javascript, simply ordering your code properly will handle this:
var loadTester = new Image(),
imgH;
$(loadTest).on('load',function(){
imgH = $('#image').attr('src',loadTester.src).height();
}
loadTester.src = "paht/to/image.jpg";
The reason you are seeing a difference in the manner you reload the page, is that a simple refresh does not clear the cache, so the image is already loaded. When you hit ctrl+f5 it clears the cache and so the image is not yet loaded when you ask the browser for the height.
For cache control durring development consider getting the firefox web-developer toolbar.
Try this approach:
jQuery(function() {
jQuery('.imagedisplay img').each(function() {
var $this = jQuery(this),
height = $this.height();
if (height) {
$this.css('margin-top', ((240 - height) / 2) + 'px');
} else {
$this.on('load', function() {
$this.css('margin-top', ((240 - $this.height()) / 2) + 'px');
});
}
});
});
images are/can be cached/loaded separately from the actual page content. the document being ready can (and in my experience usually) occurs before everything is loaded.
try adding an event listener to the actual element being loaded.
You need to make sure the image has loaded before extracting a height. You can easily check this using the complete property on the image. Try this:
var setH = function() {
$(this).css('margin-top', (240 - this.height) / 2);
}
$('.imagedisplay img').each(function() {
if( this.complete ) {
setH.call(this); // apply height straight away
return;
}
$(this).load(setH); // apply height when the image has loaded
});
I'd like to know when the user first does some scrolling on my page, as distinct from the browser-initiated scrolling that happens automatically when you reload a page.
If I try capturing the window's initial scroll position, and then registering an onscroll handler to tell me the scroll position has changed, I don't get too far: browser-initiated scrolling happens after document ready (jQuery's definition), so window.pageYOffset is always 0 on doc ready, even if the browser's right about to jump me down a hundred pixels.
If I try inspecting the onscroll event, nothing seems to let me distinguish a user-initiated event object from a browser-initiated one. The two events have pretty identical properties.
I'm looking for something a little more robust than what's suggested here: How to distinguish scrolling by mouse from scrolling programmatically in JavaScript?.
Thanks...
It's ugly, but possibly the best way to tell is by timing. I notice that on Chrome, the change in pageYOffset happens within 1 millisecond of the window.onload, while on Firefox, it happens within one millisecond of the dom loading. (haven't tested IE, but it is likely that one or the other works) For instance I added this to the bottom of a page:
<script>
window.onload = function () {
var first = window.pageYOffset;
setTimeout(function () {
second = window.pageYOffset;
alert("first: " + first + ", second:" + second)
}, 1);
};
</script>
One chrome, "first" is 0, "second" is a big number, when refreshing a scrolled page. On firefox, that is only true if the below is added right before the closing body tag (which should be the same as jquery's document.ready).
<script>
var first = window.pageYOffset;
setTimeout(function () {
second = window.pageYOffset;
alert("first: " + first + ", second:" + second)
}, 1);
</script>
I think you could detect a "browser refresh initiated" scroll reliably using this sort of technique, but obviously, it's not what I'd call a pretty solution and it might break on a future browser version.
I use a similar technique for a scroll behavior on our home page, not the most elegant but it works well:
<html>
<head></head>
<body>
<div style="height:500px;line-height:500px">
<a id="bm" href="#bm">bookmarked</a>
</div>
<script>
(function(){
var scrollingCanStart = false;
//the user interact with the page
window.onmouseover = window.onkeydown = function(e){
scrollingCanStart = true;
};
//onscroll action
window.onscroll = function(e){
if(!scrollingCanStart){
return;
}
console.log('manual scroll');
};
})();
</script>
</body>
</html>
i was solving the same task and would like to share my approach.
my assumptions:
the user's scroll always preŃeded with events like 'wheel',
'another user's scroll' and etc
all events have timeStamp
I have chosen rxjs library for implementation
// this stream will contain last User's scroll
var last_scroll = new Rx.Subject();
var start = rx.Observable.merge(
Rx.Observable.fromEvent(element, 'wheel'),
last_scroll
)
.map(function (e) { return e.timeStamp; });
Rx.Observable.fromEvent(element, 'scroll')
.withLatestFrom(start, function (scroll, start) {
return {scroll: scroll, start: start};
})
.filter(function (o) {
return Math.abs(o.scroll.timeStamp - o.start) <= 500;
})
.map(function (o) {
return o.scroll;
})
.do(function (ev) {
last_scroll.onNext(ev);
})
.subscribe(function (ev) {
// here we have scroll event initiated by user
});
Please feel free to improve this code and share your thoughts. Happy coding!
I assigned a timeout to my window.resize handler so that I wouldn't call my sizable amount resize code every time the resize event fires. My code looks like this:
<script>
function init() {
var hasTimedOut = false;
var resizeHandler = function() {
// do stuff
return false;
};
window.onresize = function() {
if (hasTimedOut !== false) {
clearTimeout(hasTimedOut);
}
hasTimedOut = setTimeout(resizeHandler, 100); // 100 milliseconds
};
}
</script>
<body onload="init();">
...
etc...
In IE7 (and possibly other versions) it appears that when you do this the resize event will constantly fire. More accurately, it will fire after every timeout duration -- 100 milliseconds in this case.
Any ideas why or how to stop this behavior? I'd really rather not call my resize code for every resize event that fires in a single resizing, but this is worse.
In your //do stuff code, do you manipulate any of the top,left,width,height,border,margin or padding properties?
You may unintentionally be triggering recursion which unintentionally triggers recursion which unintentionally triggers recursion...
How to fix the resize event in IE
also, see the answer for "scunliffe" "In your ... properties?
IE does indeed constantly fire its resize event while resizing is taking place (which you must know, as you are already implementing a timeout for a fix).
I am able to replicate the results you are seeing, using your code, on my test page.
However, the problem goes away if I increase the timeout to 1000 instead of 100. You may want to try with different wait values to see what works for you.
Here is the test page I used: it has a nicely dynamic wait period already set up for you to play with.
I stumbled on the same problem, but solved it differenly, and I think it's more elegant than making a timeout....
The context: I have an iframed page, loaded inside the parent page, and the iframe must notify the parent when its size changes, so the parent can resize the iframe accordingly - achieving dynamic resizing of an iframe.
So, in the iframed HTML document, I tried to register a callback on the body tag. First, on the onchange - it didn't work. Then on resize - it did work, but kept firing constantly. (Later on I found the cause - it was apparently a bug in Firefox, which tried to widen my page to infinity). I tried the ResizeObserver - for no avail, the same thing happened.
The solution I implemented was this:
<body onload="docResizePipe()">
<script>
var v = 0;
const docResizeObserver = new ResizeObserver(() => {
docResizePipe();
});
docResizeObserver.observe(document.querySelector("body"));
function docResizePipe() {
v += 1;
if (v > 5) {
return;
}
var w = document.body.scrollWidth;
var h = document.body.scrollHeight;
window.parent.postMessage([w,h], "*");
}
setInterval(function() {
v -= 1;
if (v < 0) {
v = 0;
}
}, 300);
</script>
So how it works: each time the callback fires, we increment a variable v; once in every 300 ms, we decrement it; if it's too big, the the firing is blocked.
The big advantage of this over the timeout-based solution, is that it introduces to lag for a user experience, and also clear in how exactly it does block the recursion. (Well, actually not )))