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'm wrapping up a site that involves a few elements (image / text / diagonal line) that have to scale proportionately on different screens.
Because there's text that has to be resized, I'm using jQuery to calculate the measurements for all of the elements based on a ratio. This was the best solution I could think of at the time, and with a deadline approaching, I think I'm stuck with it. It's a single-page site that scrolls by the page (e.g., full pages in the viewport).
Here's a link to the demo site
The idea behind the code:
We check the height of the viewport to set the container size
Set the wrapper element height, based on the container size and necessary
margins
Set the width based on a ratio
Use these values to calculate font size, image size, and offsets
As the screen is re-sized, the element shrinks proportionately to fill the available space.
It looks kind of like this:
There are two panels like this. I re-use the same code (with different variable names, and a few sizing differences) for the second panel.
Here's my Javascript/jQuery for the first:
// Set panel height on page load & resize
$(window).on("resize", function () {
var $panelHeight = $(window).height();
var $headerHeight = $('.banner').height();
// General height for panels
$('.bg-panel').css('height', $panelHeight );
$('.bg-panel').css('padding-top', $headerHeight);
}).resize();
// We want to scale content proportionately
// First let's get some breakpoints
var $breakPoint = 768;
var $breakPointSM = 480;
// Panel 1
$(window).on("resize", function () {
// Check height of current panel
// If on single-column view, we want to measure the space between the text column and bottom of screen
// Otherwise, height of entire panel
var $windowHeight = $('.panel-test').height();
// But we need to subtract the header height, so our math is correct
var $headerHeight = $('.banner').height();
var $windowHeight = $windowHeight - $headerHeight;
// Now we have the correct height to work with
// We're at 768px or below, subtract the text element from the overall height
if ( $(document).width() <= $breakPoint) {
var $heightofDiv = $('.panel-1-text').height();
var $mobileHeight = $windowHeight - $heightofDiv;
var $windowHeight = $mobileHeight;
}
// Save the window height for calculating our margins!
var $windowHeightforMargins = $windowHeight;
// Top and bottom margins
var $marginTop = $windowHeight * (102/792); // ratio from PSD
var $marginBottom = $windowHeight * (84/792); // ratio from PSD
var $marginTotal = $marginTop + $marginBottom;
// Responsive solution
// As browser shrinks, reduce the height of panel so it produces a smaller container
if ( $(document).width() > 1200 && $(document).width() <= 1440) {
var $windowHeight = $windowHeight * 0.9;
var $marginTop = $marginTop * 2;
}
else if ( $(document).width() > 990 && $(document).width() <= 1200) {
var $windowHeight = $windowHeight * 0.8;
var $marginTop = $marginTop * 3;
}
else if ( $(document).width() > $breakPoint && $(document).width() <= 990) {
var $windowHeight = $windowHeight * 0.7;
var $marginTop = $marginTop * 3.5;
}
else if ( $(document).width() < $breakPoint) { // Ratio here goes up again because we're accounting for new height with $mobileHeight
var $windowHeight = $windowHeight * 0.8;
}
// This ratio determines the width of the container
var $ratio = 697 / 607; // from PSD
// Set container height, depending on height of panel
if ( $(document).width() <= $breakPointSM) {
var $taglinesHeight = ($windowHeight * 1.5); // Scale up for phones
}
else if ( $(document).width() > $breakPointSM && $(document).width() <= $breakPoint ){
var $taglinesHeight = ($windowHeight * 1); // Scale down for tablet
}
else {
var $taglinesHeight = $windowHeight - $marginTotal;
}
// Set container width as ratio of height
if ( $(document).width() <= $breakPoint) {
var $taglinesWidth = $taglinesHeight * $ratio
} else {
var $taglinesWidth = $taglinesHeight * $ratio
}
$('.panel-test .bg-taglines').css("width", $taglinesWidth);
$('.panel-test .bg-taglines').css("height", $taglinesHeight);
// Add top margin if above breakpoint
if ( $(document).width() > $breakPoint) { // No margin unless above 768px
$('.panel-test .bg-taglines').css("margin-top", $marginTop);
}
else {
$('.panel-test .panel-1-tagline').css("bottom", $marginTop);
}
// Set font size
var $fontSize = $taglinesWidth * 0.12;
$('.bg-panel h4').css("font-size", $fontSize);
// Set pink line origin (relative to bottom-left of frame)
var $pinkX = $taglinesWidth * (286 / 705);
var $pinkY = $taglinesHeight * (192 / 607);
$('.panel-test .animation-wrapper').css("left", $pinkX);
$('.panel-test .animation-wrapper').css("bottom", $pinkY);
// Set image size
var $imageWidth = $taglinesWidth * 0.556;
$('.panel-test .scaleable-image').css("width", $imageWidth);
// Set h3 margin from top
if ( $(document).width() >= $breakPoint) {
var $marginH3 = $windowHeight * (217/792); // ratio from PSD
$('.panel-test h3').css("margin-top", $marginH3);
} else {
// CSS
}
// Set line offset from top
var $lineOffset = $taglinesHeight * 0.7;
$('.panel-test .line-wrapper').css("top", $lineOffset);
// Set line length
var $lineLong = $taglinesWidth * 1;
$('.panel-test .pink-line').css("width", $lineLong);
}).resize();
It works: MOST of the time.
If I drag my window to resize, some of the elements get resized. Others don't.
A page refresh generally solves it, but right now, elements (mostly the images!) just aren't scaling properly and in sync with other elements.
I'm very new to jQuery and this is my first big undertaking. New to using resize as well. Hoping I just made a goof that's easy to fix.
Thanks!
LIVE SITE LINK
Other plugins in use: jQuery Scrollify (for full page scrolling) and ScrollReveal.
Guess I can answer my own question.
The issue seemed to be that the values were getting mixed up when scrolling from one full-screen panel to another.
Changing this:
$(window).on("resize", function () {
To this:
$(window).on("resize load scroll", function (e) {
... solved the issue. I'm not sure if it's the right way to do it, but the resizes are all working fine now.
The below works with background-position: but it does not work when trying to use the same effect on a different element using margin-top: the second element does not have a background - but is div with content. Any suggestions?
function parallax() {
setpos(".filter-page");
setpos(".filter");
// setpos(".filter", 4);
// setpos("#pb3");
// setpos("#pb4");
}
function setpos(element, factor) {
factor = factor || 2;
var offset = $(".filter-page").offset();
var w = $(window);
var posx = (offset.left - w.scrollLeft()) / factor;
var posy = (offset.top - w.scrollTop()) / factor;
$(".filter-page").css('background-position', '10% '+posy+'px');
// $(".filter").css('background-position', '15% '+posy+'px');
$(".filter").css('margin-top', '10% '+posy+'px');
}
$(document).ready(function () {
parallax();
}).scroll(function () {
parallax();
});
When you use
$(".filter-page").css('background-position', '10% '+posy+'px');
it means you're setting the values x (10%) and y(posy pixels) of the background-position, wich is a valid value to this property (http://www.w3schools.com/cssref/pr_background-position.asp) - because you can set both X and Y at the same time.
However the attribute margin-top does not expect this kind of value (http://www.w3schools.com/cssref/pr_margin-top.asp). There is no way to set X and X values to "margin-top". This attribute has only one value to be setted. So, if you remove both 10% or posy px this will work.
Actually I'm looking for a jQuery plug-in that can handle this:
there is a container with overflow hidden
inside of this is another one, which is way larger
when i move over the div, the part I'm seeing depends on my current position
when I'm in the left top corner I see the top left corner of the inner container
when I'm in the middle i see the middle of the container …
I wrote a little JavaScript that does that:
this.zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
var relativeX = event.pageX - parentOffset.left;
var relativeY = event.pageY - parentOffset.top;
var differenceX = that.zoom.width() - that.pageWidth;
var differenceY = that.zoom.height() - that.pageHeight;
var percentX = relativeX / that.pageWidth;
var percentY = relativeY / that.pageHeight;
if (1 < percentX) {
percentX = 1;
}
if (1 < percentY) {
percentY = 1;
}
var left = percentX * differenceX;
var top = percentY * differenceY;
that.zoom.css('left', -left).css('top', -top);
});
But this isn't very smooth and kinda jumpy, when you enter from another point of the container. So, before reinventing the wheel: Is there one plug in, that does exactly that and has iOS support (drag instead of mouse move)? All zoom plug ins (like Cloud Zoom) are over the top for this purpose and most have no support for dragging on iOS.
And if there's not something like this. How can I make this smoother and cooler. Any approach would be helpful. :)
Many thanks.
So, here is my solution - which works pretty well and is easy to achieve. There could be done some improvement, but to get the idea i'll leave it that way. :)
there is a demo on jsfiddle:
http://jsfiddle.net/insertusernamehere/78TJc/
CSS
<style>
div.zoom_wrapper {
position: relative;
overflow: hidden;
width: 500px;
height: 500px;
border: 1px solid #ccc;
cursor: crosshair;
}
div.zoom_wrapper > * {
position: absolute;
}
</style>
HTML
<div class="zoom_wrapper">
<img id="zoom" src="http://lorempixel.com/output/people-q-c-1020-797-9.jpg" alt="">
</div>
JAVASCRPT
<script>
var zoom = null;
// this function will work even if the content has changed
function move() {
// get current position
var currentPosition = zoom.position();
var currentX = currentPosition.left;
var currentY = currentPosition.top;
// get container size
var tempWidth = zoom.parent().width();
var tempHeight = zoom.parent().height();
// get overflow
var differenceX = zoom.width() - tempWidth;
var differenceY = zoom.height() - tempHeight;
// get percentage multiplied by difference (in pixel) cut by percentage (here 1/4) that is used as "smoothness factor"
var tempX = zoom.data('x') / tempWidth * differenceX / 4;
var tempY = zoom.data('y') / tempHeight * differenceY / 4;
// get real top and left values to move to and the last factor slows it down and gives the smoothness (and it's corresponding with the calculation before)
var left = (tempX - currentX) / 1.25;
var top = (tempY - currentY) / 1.25;
// finally apply the new values
zoom.css('left', -left).css('top', -top);
}
$(document).ready(function () {
zoom = $('#zoom');
//handle mousemove to zoom layer - this also works if it is not located at the top left of the page
zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
zoom.data('x', event.pageX - parentOffset.left);
zoom.data('y', event.pageY - parentOffset.top);
});
// start the action only if user is over the container
zoom.hover(
function() {
zoom.data('running', setInterval( function() { move(); }, 30) );
},
function() {
clearInterval(zoom.data('running'));
}
);
});
</script>
Note:
This one has, of course, no support for touch devices. But for that I use (again)/I can recommend the good old iScroll … :)
I'm trying to work out how to enlarge all elements on a page, but keep the centre of enlargement in the centre of the window.
On this page, once the image reaches the top or the left side of the window the centre of enlargement changes. It also changes when you move the image. (exactly what you would expect)
I'm thinking I'd need to take a completely different approach to achieve what I want. But I'm not sure what that approach is..
Any ideas?
Well, here's my take.
Only thing is that I ditched the containers you were using. Is that cheating? Seems like they were only there to get the image centered. No need.
This works as expected with no side effects.
Here's a working demo you can test:
http://jsfiddle.net/YFPRB/1/
(You need to click on the pane with the baboon first.)
HTML
<body>
<img src="http://cdn.sstatic.net/stackoverflow/img/apple-touch-icon.png" />
</body>
CSS
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
jQuery
EDIT: Thanks to #stagas for the reminder to clean up redundancies.
var $img = $('img'); // Cache the image. Better for performance.
$img.draggable();
$img.css({left: ($('body').width() / 2) - ($img.width() / 2)})
.css({top: ($('body').height() / 2) - ($img.height() / 2)})
$(document).keydown(function(event) {
if (event.keyCode == 38) {
var adjustment = 1.25;
} else if (event.keyCode == 40) {
var adjustment = 0.8;
} else {
return;
}
var offset = $img.offset();
var width = $img.width();
var height = $img.height();
var newWidth = width * adjustment;
var newHeight = height * adjustment;
var diffWidth = newWidth - width;
var diffHeight = newHeight - height;
var hcenter = $('body').width() / 2;
var vcenter = $('body').height() / 2;
var leftPercent = (hcenter - offset.left) / width;
var topPercent = (vcenter - offset.top) / height;
$img.offset({top: offset.top - (diffHeight * topPercent), left: offset.left - (diffWidth * leftPercent)});
$img.width(newWidth).height(newHeight);
});
This is what I came up, it works as you say except the image will always go to the center after zooming in or out:
$('document').ready(function() {
zoomimg=$('#zoomimg'); // we store this in a variable since we don't need to traverse the DOM every time -- this is faster
var viewportWidth = $(window).width();
var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height(); // this is to work with Opera
zoomimg.css({'position': 'absolute', 'left': (viewportWidth/2)-(zoomimg.width()/2), 'top' : (viewportHeight/2)-(zoomimg.height()/2)}).draggable();
$(document).keydown(function(event) {
event = event || window.event;
var viewportWidth = $(window).width();
var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height(); // this is to work with Opera
if (event.keyCode == 38) {
width = zoomimg.width();
height = zoomimg.height();
zoomimg.width(width*1.2).height(height*1.2);
var viewportWidth = $(window).width();
var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height();
zoomimg.css({'left': (viewportWidth/2)-(zoomimg.width()/2), 'top' : (viewportHeight/2)-(zoomimg.height()/2)});
} else if (event.keyCode == 40) {
width = zoomimg.width();
height = zoomimg.height();
zoomimg.width(width*0.8).height(height*0.8);
var viewportWidth = $(window).width();
var viewportHeight = window.innerHeight ? window.innerHeight : $(window).height();
zoomimg.css({'left': (viewportWidth/2)-(zoomimg.width()/2), 'top' : (viewportHeight/2)-(zoomimg.height()/2)});
} else {
return
}
});
});
You should put an ID 'zoomimg' on the tag for it to work, and overflow:hidden on the #container . Also ditch that display:table and display:table-cell they're useless now that we center with Javascript. Also, pressing the down arrow key will cause the container to scroll down, so you should use other keys, as the arrows are reserved by the browser for scrolling the viewport.