How can I find out what percentage of the vertical scrollbar a user has moved through at any given point?
It's easy enough to trap the onscroll event to fire when the user scrolls down the page, but how do I find out within that event how far they have scrolled? In this case, the percentage particularly is what's important. I'm not particularly worried about a solution for IE6.
Do any of the major frameworks (Dojo, jQuery, Prototype, Mootools) expose this in a simple cross-browser compatible way?
Oct 2016: Fixed. Parentheses in jsbin demo were missing from answer. Oops.
Chrome, Firefox, IE9+. Live Demo on jsbin
var h = document.documentElement,
b = document.body,
st = 'scrollTop',
sh = 'scrollHeight';
var percent = (h[st]||b[st]) / ((h[sh]||b[sh]) - h.clientHeight) * 100;
As function:
function getScrollPercent() {
var h = document.documentElement,
b = document.body,
st = 'scrollTop',
sh = 'scrollHeight';
return (h[st]||b[st]) / ((h[sh]||b[sh]) - h.clientHeight) * 100;
}
If you prefer jQuery (original answer):
$(window).on('scroll', function(){
var s = $(window).scrollTop(),
d = $(document).height(),
c = $(window).height();
var scrollPercent = (s / (d - c)) * 100;
console.clear();
console.log(scrollPercent);
})
html{ height:100%; }
body{ height:300%; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I think I found a good solution that doesn't depend on any library:
/**
* Get current browser viewpane heigtht
*/
function _get_window_height() {
return window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight || 0;
}
/**
* Get current absolute window scroll position
*/
function _get_window_Yscroll() {
return window.pageYOffset ||
document.body.scrollTop ||
document.documentElement.scrollTop || 0;
}
/**
* Get current absolute document height
*/
function _get_doc_height() {
return Math.max(
document.body.scrollHeight || 0,
document.documentElement.scrollHeight || 0,
document.body.offsetHeight || 0,
document.documentElement.offsetHeight || 0,
document.body.clientHeight || 0,
document.documentElement.clientHeight || 0
);
}
/**
* Get current vertical scroll percentage
*/
function _get_scroll_percentage() {
return (
(_get_window_Yscroll() + _get_window_height()) / _get_doc_height()
) * 100;
}
This should do the trick, no libraries required:
function currentScrollPercentage()
{
return ((document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight) * 100);
}
These worked for me perfectly in Chrome 19.0, FF12, IE9:
function getElementScrollScale(domElement){
return domElement.scrollTop / (domElement.scrollHeight - domElement.clientHeight);
}
function setElementScrollScale(domElement,scale){
domElement.scrollTop = (domElement.scrollHeight - domElement.clientHeight) * scale;
}
A Typescript implementation.
function getScrollPercent(event: Event): number {
const {target} = event;
const {documentElement, body} = target as Document;
const {scrollTop: documentElementScrollTop, scrollHeight: documentElementScrollHeight, clientHeight} = documentElement;
const {scrollTop: bodyScrollTop, scrollHeight: bodyScrollHeight} = body;
const percent = (documentElementScrollTop || bodyScrollTop) / ((documentElementScrollHeight || bodyScrollHeight) - clientHeight) * 100;
return Math.ceil(percent);
}
If you're using Dojo, you can do the following:
var vp = dijit.getViewport();
return (vp.t / (document.documentElement.scrollHeight - vp.h));
Which will return a value between 0 and 1.
This question has been here for a long time, I know, but I stumbled onto it while trying to solve the same problem. Here is how I solved it, in jQuery:
First, I wrapped the thing I wanted to scroll in a div (not semantic, but it helps). Then set the overflow and height on the wrapper.
<div class="content-wrapper" style="overflow: scroll; height:100px">
<div class="content">Lot of content that scrolls</div>
</div>
Finally I was able to calculate the % scroll from these metrics:
var $w = $(this),
scroll_top = $w.scrollTop(),
total_height = $w.find(".content").height(),
viewable_area = $w.height(),
scroll_percent = Math.floor((scroll_top + viewable_area) / total_height * 100);
Here is a fiddle with working example: http://jsfiddle.net/prEGf/
Everyone has great answers, but I just needed an answer as one variable. I didn't need an event listener, I just wanted to get the scrolled percentage. This is what I got:
const scrolledPercentage =
window.scrollY / (document.documentElement.scrollHeight - document.documentElement.clientHeight)
document.addEventListener("scroll", function() {
const height = window.scrollY / (document.documentElement.scrollHeight - document.documentElement.clientHeight)
document.getElementById("height").innerHTML = `Height: ${height}`
})
.container {
position: relative;
height: 200vh;
}
.sticky-div {
position: sticky;
top: 0;
}
<!DOCType>
<html>
<head>
</head>
<body>
<div id="container" class="container">
<div id="height" class="sticky-div">
Height: 0
</div>
</div>
</body>
First attach an event listener to some document you want to keep track
yourDocument.addEventListener("scroll", documentEventListener, false);
Then:
function documentEventListener(){
var currentDocument = this;
var docsWindow = $(currentDocument.defaultView); // This is the window holding the document
var docsWindowHeight = docsWindow.height(); // The viewport of the wrapper window
var scrollTop = $(currentDocument).scrollTop(); // How much we scrolled already, in the viewport
var docHeight = $(currentDocument).height(); // This is the full document height.
var howMuchMoreWeCanScrollDown = docHeight - (docsWindowHeight + scrollTop);
var percentViewed = 100.0 * (1 - howMuchMoreWeCanScrollDown / docHeight);
console.log("More to scroll: "+howMuchMoreWeCanScrollDown+"pixels. Percent Viewed: "+percentViewed+"%");
}
My two cents, the accepted answer in a more "modern" way. Works back to IE9 using #babel/preset-env.
// utilities.js
/**
* #param {Function} onRatioChange The callback when the scroll ratio changes
*/
export const monitorScroll = onRatioChange => {
const html = document.documentElement;
const body = document.body;
window.addEventListener('scroll', () => {
onRatioChange(
(html.scrollTop || body.scrollTop)
/
((html.scrollHeight || body.scrollHeight) - html.clientHeight)
);
});
};
Usage:
// app.js
import { monitorScroll } from './utilities';
monitorScroll(ratio => {
console.log(`${(ratio * 100).toFixed(2)}% of the page`);
});
I reviewed all of these up there but they use more complex approaches to solve. I found this through a mathematical formula; brief.
The formula goes Value/Total * 100. Say Total is 200 u wanna know the percentage of 100 out of 200, you do it 100/200 * 100% = 50% (the value)
pageYOffset = The vertical scroll count without including borders. When you scroll down to bottom you get the maximum count.
offsetHeight = The total height of the page including borders!
clientHeight = The height in pixels without borders but not to the end of content!
When u scroll to bottom u get pageyoffset of 1000 for example, whereas offsetHeight of 1200 and clientHeight of 200. 1200 - 200(clientheight) now u get paggeYOffset value in offsetHeight and so scrollPosition300(300 of 1000)/1000 * 100 = 30%.
`pageOffset = window.pageYOffset;
pageHeight = document.documentElement.offsetHeight;
clientHeight = document.documentElement.clientHeight;
percentage = pageOffset / (pageHeight - clientHeight) * 100 + "%";
console.log(percentage)`
The reason why we must do offsetHeight - clientHeight it is because client heights shows all the available content in px without borders, and offsetheight shows the available content including borders, whereas pageYOffset counts the scrolls made; The scrollbar is quite long to count the whole windows it counts the scrolls itself until reaches the end, the available space in scrollbar is in px pageYOffset, so to reach that number you substract offsetHeight - clientHeight to bring to the lower value of pageYOffset.
i'll update when i get on pc, please leave a comment to make it clear so i don't forget! Thanks :)
Using jQuery
$(window).scrollTop();
will get you the scroll position, you can then work out from there what the percentage is based on the window height.
There is also a standard DOM property scrollTop that you can use like document.body.scrollTop however I'm not sure how this behaves cross-browser, I would assume if there are inconsistencies then the jQuery method accounts for these.
var maxScrollTop = messages.get(0).scrollHeight - messages.height();
var scroll = messages.scrollTop() / maxScrollTop; // [0..1]
I found a way to correct a previous answer, so it works in all cases. Tested on Chrome, Firefox and Safari.
(((document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight) || 0) * 100)
I have X and Y scroll values in percentage (values spanning from 0 to 100) and I want to scroll the page to that location. I am receiving the percentage values from an API call.
function scrollPageByGivenPercentage(percentageX, percentageY) {
// ... scroll by percentage ...
//window.scrollTo(percentageX, percentageY);
}
The above function scrolls in pixels and it doesn't do the job. What I tried using is this:
function scrollPageByGivenPercentage(percentageX, percentageY) {
var yScrollInPx = document.body.documentElement.clientHeight * percentageY;
window.scrollTo(0, yScrollInPx);
}
My guess is clientHeight is the wrong property. Tried offsetHeight as well.
I get the calculate the percentages from this question:
I have this function to receive the current scroll value in percentage:
function getScrollPercent() {
var h = document.documentElement,
b = document.body,
st = 'scrollTop',
sh = 'scrollHeight';
return (h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight) * 100;
}
Get the total height of the document, work out the percentage you need in px then apply that to window.scrollTo(). Try this:
var scrollYPercent = 0.25;
var scrollYPx = document.documentElement.scrollHeight * scrollYPercent;
window.scrollTo(0, scrollYPx);
html, body { height: 1000px; }
p { margin-top: 400px; }
<p>Note the scroll position -></p>
I am trying to animate an opacity value from 0 to 1, based on the scroll position within the viewport height. The code below sets variables for windowHeight and scrollTop, which can be combined to calculate percentageScrolled (0–100) of the viewport height. Based on this I am able to switch CSS values at set points, but instead I want to gradually change the opacity from 0–100 of percentageScrolled.
How can I adjust the code below to transition/animate the opacity?
Thanks.
$(window).on('scroll', function(){
// Vars
var windowHeight = $(window).height();
var scrollTop = $(this).scrollTop();
var percentageScrolled = (scrollTop*100)/windowHeight;
if( percentageScrolled < 100 ) {
$('.colour-overlay').css('opacity', '1');
} else {
$('.colour-overlay').css('opacity', '0');
}
});
You can remove the if and set the opacity to the percentage divided by 100
$(window).on('scroll', function() {
// Vars
var windowHeight = $(window).height();
var scrollTop = $(this).scrollTop();
$('.colour-overlay').css('opacity', scrollTop / windowHeight);
});
.colour-overlay {
display: block;
width: 20px;
height: 1200px;
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="colour-overlay"></div>
$(‘.colour-overlay’).css(opacity, percentageScrolled / 100);
Instead of if else statement.
Also as a general advice try to avoid using var, use const or let instead and if your project doesnt depend on jquery try to avoid it too.
const overlays = document.querySelectorAll(‘.colour-overlay’);
window.addEventListener('scroll', () => {
const windowHeight = window.offsetHeight;
const scrollTop = window.scrollTop;
const percentageScrolled = (scrollTop * 100) / windowHeight;
for (const overlay of overlays) {
overlay.style.opacity = percentageScrolled / 100;
}
});
This would be the pure js solution.
Dont know if i understood you right, but a wrote an example have a look.
$(document).on('scroll', function(){
// Vars
// use body instead of window, body will return the right height where window will return the view size
var windowHeight = $("body").height();
var scrollTop = $(this).scrollTop();
var percentageScrolled = Math.abs((((scrollTop / windowHeight) * 100) / 100 ));
$('.colour-overlay').css('opacity', percentageScrolled);
});
.colour-overlay{
background:red;
height:1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="colour-overlay"></div>
I've tried to find similar posts about this but failed to do so. What I'm trying to do is set up a parallax background that has a moderate zoom upon scrolling. I have the parallax down, that was simple enough, but the zoom on scroll is causing me difficulties.
if ($(".zoomImage").length == 0)
{
console.warn("You're attempting to set hero images without an existing class. '.heroImage'");
return;
}
$(document).scroll(function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
$(".zoomImage").each(function(){
var offset = $(this).offset().top;
// Only modify when top is at top of browser on screen.
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
$(this).css("background-size", heroEffectPerc + "% auto");
}
});
});
This is where I'm doing the zoom for the image, the parallax is done in pure CSS as represented below. The issue I'm having is figuring out the mathematics to make sure that the image doesn't escape the edge of its parent when the screen gets excessively wide or tall and still achieve the same effect. 1:
CSS:
pageCanvas
{
position: relative;
background-repeat: no-repeat;
background-position: center;
background-size: auto 100%;
background-color: white;
display: block;
left: 0;
top: 0;
width: 100%;
height: 100vh;
}
pageCanvas.parallax
{
background-attachment: fixed;
}
.
<pageCanvas class="parallax zoomImage" style="background-image: url('./Images/DisplayBackground.png');">
<banner>
<header>
Company name
</header>
<description>
I don't want to<br><span style="margin-left: 200px;">advertise here.</span>
</description>
</banner>
</pageCanvas>
I've tried to get it working, but either have an issue with one of the following:
White background shows on too wide.
White background shows on too tall.
Image stretching.
Do I need to bring in the images origin ratio with this or something? If I figure out a solution prior to an answer given, I'll post it.
Although the original was in a namespace, I'm going to place this answer as if it were not because I hadn't specified. Either way, I found the solution.
The first step was finding the ratio of the original image;
$(".zoomImage").each(function(index){
var bg = $(this).css('background-image');
bg = bg.replace('url(','').replace(')','').replace(/\"/gi, "");
// get image size.
var tmpImg = new Image();
tmpImg.src=bg;
$(tmpImg).one('load', function(){
orgWidth = tmpImg.width;
orgHeight = tmpImg.height;
bgImageRatios[index] = orgHeight / (orgWidth * 1.0);
});
});
To make life easier, I placed them in an array that was global to the name space. This is so I don't have to A) keep finding the ratio of the image, and B) can access it similarly to initializing later on. It should be noted that this method would require being called again in the instance there is any more or less '.zoomImage' classes brought into instance, as the array will be incorrect at that point.
What I did next was place the original code that loops the class into a function.
function zoomImage(scrollpos, screenHeight, screenWidth)
{
//console.log(screenHeight);
$(".zoomImage").each(function(index){
var offset = $(this).offset().top;
if (offset < scrollpos && scrollpos < offset + screenHeight)
{
var heroEffectPerc = 100 + 25 * (scrollpos - offset) / (screenHeight * 1.0);
if ((bgImageRatios[index] * screenWidth / screenHeight) > 1)
$(this).css("background-size", heroEffectPerc + "% auto");
else
$(this).css("background-size", "auto " + heroEffectPerc + "%");
}
});
}
I put it into a function because it would have been placed into two separate locations otherwise. (that's just messy coding). I updated the image size as follows.
$(window).on("resize", function(){
var scrollpos = $(document).scrollTop();
var screenHeight = $(this).height();
var screenWidth = $(this).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
$(document).on("scroll", function(){
var scrollpos = $(this).scrollTop();
var screenHeight = $(window).height();
var screenWidth = $(window).width();
pageCanvas.zoomImage(scrollpos, screenHeight, screenWidth);
});
The following sources helped me solve my answer:
Can I get div's background-image url?
How to get image size from URL
Credit is due to them.
I saw this question and found it helpful, but I tried doing more with the code to apply zoom buttons and can't seem to get it to work properly. I'm new to javascript and jquery and I'm having trouble understanding how the "this" keyword is applied.
$('#plus').click(
function( event ){
var scale = 150/100;
var pos = $('#Container img').offset();
var clickX = event.pageX - pos.left;
var clickY = event.pageY - pos.top;
var container = $('#Container img').parent().get(0);
$('#Container img').css({
width: this.width*scale,
height: this.height*scale
});
container.scrollLeft = ($(container).width() / -2 ) + clickX * scale;
container.scrollTop = ($(container).height() / -2 ) + clickY * scale;
}
);
$('#minus').click(
function( event ){
var scale = 100/150;
var pos = $('#Container img').offset();
var clickX = event.pageX - pos.left;
var clickY = event.pageY - pos.top;
var container = $('#Container img').parent().get(0);
$('#Container img').css({
width: this.width*scale,
height: this.height*scale
});
container.scrollLeft = ($(container).width() / -2 ) + clickX * scale;
container.scrollTop = ($(container).height() / -2 ) + clickY * scale;
}
);
I made a jsfiddle to demo my code.
this refers to the clicked element (actually, you should probably be using $(this) in jQuery), because the function that's called after the click gets that scope. So in your example, this refers to either the plus or the minus button, therefore you're techically scaling their width/height and applying the resulting width/height to the image.
A common thing to avoid having to debug what this represent is to assign it to a variable at the very top of the function, for example:
var self = this;
or
var $myClickedButton = this;
Also, pay attention to styling:
element.width refers to an element's width attribute (like <img width="300" />, similar to jQuery's $(element).attr('width')), but
element.style.width refers to its CSS width (like <img style="width: 300px;" />, or <img class="myClassWithDenotedWidth" />, similar to jQuery's $(element).css('width')). The latter also has a unit, so you have to parseInt it to get just the numeric value.
I've fixed it for your plus button, you can follow the same principle for the minus. Here's what I did:
var $image = $('#Container img');
var container = $image.parent().get(0);
$('#Container img').css({
width: Math.round(parseInt($image.css('width'), 10) * scale),
height: Math.round(parseInt($image.css('height'), 10) * scale)
});
See it here: http://jsfiddle.net/shomz/4A9Gt/4/ (also added a CSS transition to make zooms less choppy)