How to calculate bulletproof scrolling progress - javascript

I am calculating scrolling progress (% of document hidden from view after scrolling down) on a page with the following line:
window.pageYOffset / document.body.scrollHeight
This works well for most sites. But some sites tweak the scrolling or the overflow property and in effect break the above calculation.
For example, on this page the above line will always return 0.
Is there a better way to calculate scrolling progress that would work in situations like these?

Here's my approach :
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
let limit = Math.max( document.body.scrollHeight, document.body.offsetHeight,
document.documentElement.clientHeight, document.documentElement.scrollHeight,
document.documentElement.offsetHeight );
let footerHeight = document.querySelector('footer').offsetHeight;
let max = limit - window.innerHeight - footerHeight;
let height = window.pageYOffset / max * 100;
height = Math.min(100, Math.max(0, height));
const loader = document.getElementById("loader-scroll");
loader.style.width = 0 + height + "%";
}
It works fine across most browsers

Related

How can I calculate scroll percentage of a div instead of whole page with JS?

I found the following code used to track the scroll percentage as you scroll down the page.
document.addEventListener('scroll', function(){
var h = document.documentElement,
b = document.body,
st = 'scrollTop',
sh = 'scrollHeight';
var percent = (h[st]||b[st]) / ((h[sh]||b[sh]) - h.clientHeight) * 100;
document.getElementById('scroll').textContent = 'scrolled: ' + percent + '%';
});
That shows the percentage of the whole page. If I only want the scroll percentage of a specific div like this example https://www.thefarmersdog.com/ how the dog bowl scrolls over when you move down the page.
You need to use getBoundingClientRect()
const coordinates = document.getElementById('your-id').getBoundingClientRect();
console.log(coordinates.top); // This is the distance to the top of the screen
Here is a detailed article that you can read if you need more info.
Divide number of pixels that an element's content has been scrolled vertically by the height of an element's content minus an element's offset height and multiply by 100 to get percentage value from 0 to 100. Optionally, round it to get an integer value.
scrollPercent = scrollTop / (scrollHeight - offsetHeight)
const scrollLabelElement = document.getElementById('scroll');
element.addEventListener('scroll', (_event) =>
const percentScrolled = Math.round(element.scrollTop / (element.scrollHeight - element.offsetHeight));
scrollLabelElement.textContent = `Scrolled: ${percentScrolled}%`;
});

DIV fades onto another DIV

How would you make it if the user scrolls down a page, the top DIV fades into the DIV underneath it, and so on and so forth until it fades to a white background?
Here's a jsfiddle of my attempt: https://jsfiddle.net/fkgzzxku/
And here's it's hosted on a staging server for a better illustration: http://bound.staging.wpengine.com/
var target = $('div.slider-item');
var targetHeight = target.height();
var containerHeight = $('#intro-slider').outerHeight();
var maxScroll = containerHeight - targetHeight;
var scrollRange = (maxScroll / (target.length - 1)) + 250; // originally 450
$(document).scroll(function(e) {
var scrollY = $(window).scrollTop();
var scrollPercent = (scrollRange - scrollY % scrollRange) / scrollRange;
var divIndex = Math.floor(scrollY / scrollRange);
target.has(':lt(' + divIndex + ')').css('opacity', 0);
target.eq(divIndex).css('opacity', scrollPercent);
target.has(':gt(' + divIndex + ')').css('opacity', 1);
});
But the DIVs don't completely fade to 0, they fade to a number close to 0 so I feel like my math is wrong.
I also found that if the user scrolls too fast (by pressing page down, etc) you can see all 3 of the images faded into another.
Thanks!
I think because scrollY%scrollRange is never equal to scrollRange your scrollPercent is never 0. You can use scrollPercent= Math.round(scrollPercent*10)/10; after calculating scrollPercent to round it off to 0.
Moreover the problem caused by scrolling too fast seems to be caused by the has function replacing it with slice function works fine for me ( I can't understand why ). Here is the updated code
$(document).scroll(function(e) {
var scrollY = $(window).scrollTop();
var scrollPercent =(scrollRange - scrollY % scrollRange) / scrollRange;
var divIndex = Math.floor(scrollY / scrollRange);
target.slice(0,divIndex).css('opacity', 0);
target.eq(divIndex).css('opacity', scrollPercent);
target.slice(divIndex+1).css('opacity', 1);
});
This works without rounding off scrollPercent. Hope it helps

Detect percent on screen of an element

I'm trying to detect what % of the element can be seen on the current window.
For example, if the user can only see half the element, return 50. If the user can see the whole element, return 100.
Here's my code so far:
function getPercentOnScreen() {
var $window = $(window),
viewport_top = $window.scrollTop(),
viewport_height = $window.height(),
viewport_bottom = viewport_top + viewport_height,
$elem = $(this),
top = $elem.offset().top,
height = $elem.height(),
bottom = top + height;
return (bottom - viewport_top) / height * 100;
}
But it doesn't seem to be working. Can anyone help me out in achieveing this I seem to be spinning gears.
What you want to get is the amount of pixels that the element extends past the top and bottom of the viewport. Then you can just subtract it from the total height and divide by that height to get the percentage onscreen.
var px_below = Math.max(bottom - viewport_bottom, 0);
var px_above = Math.max(viewport_top - top, 0);
var percent = (height - px_below - px_above) / height;
return percent;
One thing to note is that jQuery's height method won't include padding. You probably want to use .outerHeight for that.
Your $elem = $(this)assignment seems wrong, here function scoping means this refers to the function you're in (ala ~ the function getPercentOnScreen), try referencing by $elem = $('#yourElementId')instead.
if you only want to calculate percent of element then just do this
function getPercentOnScreen(elem) {
$docHeight = $(document).height();
$elemHeight = $(elem).height();
return ($elemHeight/$docHeight)* 100;
}

Aplying 'K method' to make a custom scroll bar in jQuery

I am trying to make a custom scrollbar using the following method:
Scrollbar height is inversely proportional to content height.
scrollHeight ∝ 1 / contentHeight.
scrollHeight = k / contentHeight
So if we take constant of k 's value as 10000. (As the height of container of content is 100px and 100 times 100 is 10000). It's still not doing the job.
Can't figure out the problem. If words are confusing check out this JS Fiddle. I know the concept just can't figure out how to set the height of the scroller for dynamic content.
You can try this:
$(document).ready(function(e){
$('#scroll').draggable({
axis: 'y',
containment: 'parent'
});
var contentHeight = $('p').height();
$('.hey').html(contentHeight);
var containerHeight = $('#scroll-cont').height();
var k = 10000;
var scrollerHeight = $('#scroll').height((k / contentHeight) / 2);
$('.hey').append('<br>' + $('#scroll').height());
$(document).mousemove(function(e){
var offsetS = $('#scroll').offset().top * 2;
$('#scroll-cont').scrollTop(offsetS);
});
});
scrollbarHeight / scrolltrackHeight = visibleHeight / contentHeight
scrollbarHeight = scrolltrackHeight * visibleHeight / contentHeight
For example, you content's height 1000px, and visible height is 250px. If your scrolltrack height is 240px (all visible space - 5px padding from top/bottom), then your scrollbar should be 240 * 250 / 1000 = 60

JavaScript div resizing with aspect ratio

I'm writing a little script that allows the user to move and resize a div. I need to keep the aspect ratio and my logic doesn't work.
function resizing() {
var currentHeight = elmnt.offsetHeight;
var currentWidth = elmnt.offsetWidth;
var newHeight = currentHeight + (event.pageY - currentY);
var newWidth = currentWidth + (event.pageX - currentX);
var ratio = currentWidth / currentHeight;
if(ratio < 1) {
newwidth = parseInt(newHeight * ratio);
}
else {
newheight = parseInt(newWidth / ratio);
}
elmnt.style.height = newHeight + "px";
elmnt.style.width = newWidth + "px";
currentY = event.pageY;
currentX = event.pageX;
}
The script kind of works. But unfortunately it doesn't keep the aspect ratio completely correct. Sometimes, when I resize horizontyl only, the old height remains the same, sometimes it works, but one length gets resized with a little offset.
When I resize up and down and up and down again, the lengths gets more and more equal and when it is a proper square, everything is right.
Hwo can I fix my problems? Where is my fallacy?!
Your ratio is wrong I think.
You need to calculate this by taking the old width and dividing by the new width, or old height / new height.
e.g.
var ratio = newWidth / currentWidth;
newHeight = currentHeight * ratio;
Change it about if it is the height that is changing.
I could fiy it.
THANK YOU VERY MUCH!
My problem was that I first, had to track of which axis has more change. The second problem, which I didn't recognized was, that I had BIG problems with rounding issues.
When setting the css size using jQuery, it rounds. And I took the height for ratio calculations every single event.
That means that the inaccuracy was getting more and more bad.
Now I took this into account and figured out a way to get this working very good.
I now do this directly onclick and just update them instead of getting from the element:
currentHeight = $("#dragger").height();
currentWidth = $("#dragger").width();
So thanks again for your help! Here is my final result:
http://jsfiddle.net/julian_weinert/xUAZ5/30/
You have to do this, get the min scale (ratio). The code below is a part of my PHP script, but easily translated to JS. $iSrc = Source and $iDest is destination MAX width/height.
Your problem is you don't get the right ratio. The first line to define the ratio is where your problem will be solved. It gets the lowest ratio of the width or height. You just do width/height and forget height/width. That's why vertical scaling is not correct.
$scale = min($iDestWidth/$iSrcWidth, $iDestHeight/$iSrcHeight);
if($scale >= 1){
$iDestHeight = $iSrcHeight;
$iDestWidth = $iSrcWidth;
}else{
$iDestWidth = floor($scale*$iSrcWidth);
$iDestHeight = floor($scale*$iSrcHeight);
}
replace your if(ratio < 1) block with the following. offsetx and offsety relate to your (event.pageX - currentX) and (event.pageY - currentY):
if (Math.abs(offsetx) > Math.abs(offsety)) {
ratio = currentHeight/currentWidth;
newHeight = currentHeight + (offsetx * ratio);
newWidth = currentWidth + offsetx;
} else {
ratio = currentWidth/currentHeight;
newHeight = currentHeight + offsety;
newWidth = currentWidth + (offsety * ratio);
}
here is a quick jsfiddle of the whole thing in action: http://jsfiddle.net/8TWRV/

Categories