I want to call focus() on an input after the widow scrolled. I'm using the smooth behavior for the scrollTo() method. The problem is the focus method cut the smooth behavior. The solution is to call the focus function just after the scroll end.
But I can't find any doc or threads speaking about how to detect the end of scrollTo method.
let el = document.getElementById('input')
let elScrollOffset = el.getBoundingClientRect().top
let scrollOffset = window.pageYOffset || document.documentElement.scrollTop
let padding = 12
window.scrollTo({
top: elScrollOffset + scrollOffset - padding,
behavior: 'smooth'
})
// wait for the end of scrolling and then
el.focus()
Any ideas?
I wrote a generic function based on the solution of George Abitbol, without overwriting window.onscroll:
/**
* Native scrollTo with callback
* #param offset - offset to scroll to
* #param callback - callback function
*/
function scrollTo(offset, callback) {
const fixedOffset = offset.toFixed();
const onScroll = function () {
if (window.pageYOffset.toFixed() === fixedOffset) {
window.removeEventListener('scroll', onScroll)
callback()
}
}
window.addEventListener('scroll', onScroll)
onScroll()
window.scrollTo({
top: offset,
behavior: 'smooth'
})
}
I found a way to achieve what I want but I think it's a bit hacky, isn't it?
let el = document.getElementById('input')
let elScrollOffset = el.getBoundingClientRect().top
let scrollOffset = window.pageYOffset || document.documentElement.scrollTop
let padding = 12
let target = elScrollOffset + scrollOffset - padding
window.scrollTo({
top: target,
behavior: 'smooth'
})
window.onscroll = e => {
let currentScrollOffset = window.pageYOffset || document.documentElement.scrollTop
// Scroll reach the target
if (currentScrollOffset === target) {
el.focus()
window.onscroll = null // remove listener
}
}
Other answers didn't fully work for me, therefore based on #Fabian von Ellerts answer, I wrote my own solution.
My problems were that :
The element I was scrolling (and all its parents along the hierarchy) always had a offsetTop of 0, so it was not working.
I needed to scroll a nested element.
Using getBoundingClientRect and a container element as reference works :
const smoothScrollTo = (
scrollContainer,
scrolledContent,
offset,
callback
) => {
const fixedOffset = (
scrollContainer.getBoundingClientRect().top + offset
).toFixed()
const onScroll = () => {
if (
scrolledContent.getBoundingClientRect().top.toFixed() ===
fixedOffset
) {
scrollContainer.removeEventListener('scroll', onScroll)
callback()
}
}
scrollContainer.addEventListener('scroll', onScroll)
onScroll()
scrollContainer.scrollTo({
top: offset,
behavior: 'smooth',
})
}
I am trying to trigger an event when the users scrolls down and reaches to the bottom of the page.
I searched the internet and found some posts in stackoverflow but unexpectedly the answers did not work for me.
Ex: Check if a user has scrolled to the bottom
using the answers given for the above SO post, the event I am trying to trigger is executed when reaching the top of the page and not the bottom.
Please let me know if I am going wrong:
$(window).scroll(function() {
if($(window).scrollTop() + $(window).height() == $(document).height()) {
loadmore();
}
});
function loadmore(){
var lastProd = $('.product_div').last();
var lastProdID = $('.product_div').last().prop('id');
//console.info(lastProdID); return false;
//var val = document.getElementById("row_no").value;
$.ajax({
type: 'post',
url: 'includes/load_products.php',
data: { getresult:lastProdID },
success: function (response) {
console.log(response);
//var content = document.getElementById("all_rows");
//content.innerHTML = content.innerHTML+response;
lastProd.after(response);
// We increase the value by 10 because we limit the results by 10
// document.getElementById("row_no").value = Number(val)+10;
}
});
}
Use window.innerHeight + window.scrollY to determine bottom position and check if document.body.offsetHeight is lower (equal won't work).
Credit goes to mVChr, see here.
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
alert("bottom of the page reached");
}
};
.jump {
height: 1000px;
}
<div class="jump"></div>
check the height and offset are equal
window.onscroll = function() {
var d = document.documentElement;
var offset = d.scrollTop + window.innerHeight;
var height = d.offsetHeight;
console.log('offset = ' + offset);
console.log('height = ' + height);
if (offset === height) {
console.log('At the bottom');
loadmore(); // call function here
}
};
The only proper way to do this, at time of writing Feb/11/2022, is here: Check if a user has scrolled to the bottom in subpixel precision era
That detects bottom of scroll for any element. To do this with the "window" in a default web page, you should set element to document.documentElement or document.scrollingElement to get the result you want.
Or, apply the same Math.abs trick as in that answer with window.scrollY, but not that that does not work with scrollable in any other elements, only the documentElement (that's what element is being scrolled when saying "window scroll", and it is the root <html> element).
This function is working fine in Chrome but in I.E the css changes are slow, so is visible to the user when it shouldn't. I am thinking the problem could be with the scroll event or something but I can't solve it... Anyone can help me please?
var freezeLeft = settings.left > 0 || settings.leftClass !== null;
var freezeRight = settings.right > 0 || settings.rightClass !== null;
parent.scroll(function () {
var scrollWidth = parent[0].scrollWidth;
var clientWidth = parent[0].clientWidth;
var left = parent.scrollLeft;
if (freezeLeft) {
settings.leftColumns.css("left", left);
}
if (freezeRight) {
settings.rightColumns.css("right", scrollWidth - clientWidth - left);
}
}.bind(table));
}
Here's my JS code..
<script>
var sticky = document.querySelector('.sticky');
var origOffsetY = sticky.offsetTop;
function onScroll(e) {
window.scrollY >= origOffsetY ? sticky.classList.add('fixed') :
sticky.classList.remove('fixed');
}
document.addEventListener('scroll', onScroll);
</script>
It's used to let a div stays in place even when the user scrolls down.
It doesn't work in IE10 (which has querySelector, classList, and addEventListener, so it's not that).
IE10 doesn't support scrollY. You have to use scrollTop on document.documentElement:
var sticky = document.querySelector('.sticky');
var origOffsetY = sticky.offsetTop;
var hasScrollY = 'scrollY' in window;
function onScroll(e) {
var y = hasScrollY ? window.scrollY : document.documentElement.scrollTop;
y >= origOffsetY ? sticky.classList.add('fixed') : sticky.classList.remove('fixed');
}
document.addEventListener('scroll', onScroll);
Live Example | Live Source
(You may not need the check, it's possible all of your target browsers support document.documentElement.scrollTop and you could just always use that.)
I want to get the position of an element relative to the browser's viewport (the viewport in which the page is displayed, not the whole page). How can this be done in JavaScript?
Many thanks
The existing answers are now outdated. The native getBoundingClientRect() method has been around for quite a while now, and does exactly what the question asks for. Plus it is supported across all browsers (including IE 5, it seems!)
From MDN page:
The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box, in pixels, with the top-left relative to the top-left of the viewport.
You use it like so:
var viewportOffset = el.getBoundingClientRect();
// these are relative to the viewport, i.e. the window
var top = viewportOffset.top;
var left = viewportOffset.left;
On my case, just to be safe regarding scrolling, I added the window.scroll to the equation:
var element = document.getElementById('myElement');
var topPos = element.getBoundingClientRect().top + window.scrollY;
var leftPos = element.getBoundingClientRect().left + window.scrollX;
That allows me to get the real relative position of element on document, even if it has been scrolled.
var element = document.querySelector('selector');
var bodyRect = document.body.getBoundingClientRect(),
elemRect = element.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
Edit: Add some code to account for the page scrolling.
function findPos(id) {
var node = document.getElementById(id);
var curtop = 0;
var curtopscroll = 0;
if (node.offsetParent) {
do {
curtop += node.offsetTop;
curtopscroll += node.offsetParent ? node.offsetParent.scrollTop : 0;
} while (node = node.offsetParent);
alert(curtop - curtopscroll);
}
}
The id argument is the id of the element whose offset you want. Adapted from a quirksmode post.
jQuery implements this quite elegantly. If you look at the source for jQuery's offset, you'll find this is basically how it's implemented:
var rect = elem.getBoundingClientRect();
var win = elem.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset
};
function inViewport(element) {
let bounds = element.getBoundingClientRect();
let viewWidth = document.documentElement.clientWidth;
let viewHeight = document.documentElement.clientHeight;
if (bounds['left'] < 0) return false;
if (bounds['top'] < 0) return false;
if (bounds['right'] > viewWidth) return false;
if (bounds['bottom'] > viewHeight) return false;
return true;
}
source
The function on this page will return a rectangle with the top, left, height and width co ordinates of a passed element relative to the browser view port.
localToGlobal: function( _el ) {
var target = _el,
target_width = target.offsetWidth,
target_height = target.offsetHeight,
target_left = target.offsetLeft,
target_top = target.offsetTop,
gleft = 0,
gtop = 0,
rect = {};
var moonwalk = function( _parent ) {
if (!!_parent) {
gleft += _parent.offsetLeft;
gtop += _parent.offsetTop;
moonwalk( _parent.offsetParent );
} else {
return rect = {
top: target.offsetTop + gtop,
left: target.offsetLeft + gleft,
bottom: (target.offsetTop + gtop) + target_height,
right: (target.offsetLeft + gleft) + target_width
};
}
};
moonwalk( target.offsetParent );
return rect;
}
You can try:
node.offsetTop - window.scrollY
It works on Opera with viewport meta tag defined.
I am assuming an element having an id of btn1 exists in the web page, and also that jQuery is included. This has worked across all modern browsers of Chrome, FireFox, IE >=9 and Edge.
jQuery is only being used to determine the position relative to document.
var screenRelativeTop = $("#btn1").offset().top - (window.scrollY ||
window.pageYOffset || document.body.scrollTop);
var screenRelativeLeft = $("#btn1").offset().left - (window.scrollX ||
window.pageXOffset || document.body.scrollLeft);
Thanks for all the answers. It seems Prototype already has a function that does this (the page() function). By viewing the source code of the function, I found that it first calculates the element offset position relative to the page (i.e. the document top), then subtracts the scrollTop from that. See the source code of prototype for more details.
Sometimes getBoundingClientRect() object's property value shows 0 for IE. In that case you have to set display = 'block' for the element. You can use below code for all browser to get offset.
Extend jQuery functionality :
(function($) {
jQuery.fn.weOffset = function () {
var de = document.documentElement;
$(this).css("display", "block");
var box = $(this).get(0).getBoundingClientRect();
var top = box.top + window.pageYOffset - de.clientTop;
var left = box.left + window.pageXOffset - de.clientLeft;
return { top: top, left: left };
};
}(jQuery));
Use :
var elementOffset = $("#" + elementId).weOffset();
Based on Derek's answer.
/**
* Gets element's x position relative to the visible viewport.
*/
function getAbsoluteOffsetLeft(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetLeft;
offset -= currentElement.scrollLeft;
currentElement = currentElement.offsetParent;
}
return offset;
}
/**
* Gets element's y position relative to the visible viewport.
*/
function getAbsoluteOffsetTop(el) {
let offset = 0;
let currentElement = el;
while (currentElement !== null) {
offset += currentElement.offsetTop;
offset -= currentElement.scrollTop;
currentElement = currentElement.offsetParent;
}
return offset;
}
Here is something for Angular2 +. Tested on version 13
event.srcElement.getBoundingClientRect().top;