How to detect Pull to refresh - javascript

There are many "pull to refresh" plugins. I have already tested 5 of them. But none of them running fast (especially on old smartphones).
What is the best (buttery UX performance and responsiveness) way to check for pull to refresh?
PS: I don't need any animation. I just want to recognize if a user "pull to refresh"

Performance requires minimal code
Plugins and libraries have to be written to be as flexible and general as possible, in order to solve many related problems. This means they'll always be bulkier than you need, impacting performance. It also means you'll never have to maintain that code. That's the trade off.
If performance is your goal, build it yourself.
Since ALL you need is a pull-down detection, build a simple swipe detector.
Of course, you'll have to adapt this to your needs, and the event-properties, event-triggers of the OS and browser you're targeting.
Simplified from my old js-minimal-swipe-detect
var pStart = { x: 0, y: 0 };
var pStop = { x: 0, y: 0 };
function swipeStart(e) {
if (typeof e["targetTouches"] !== "undefined") {
var touch = e.targetTouches[0];
pStart.x = touch.screenX;
pStart.y = touch.screenY;
} else {
pStart.x = e.screenX;
pStart.y = e.screenY;
}
}
function swipeEnd(e) {
if (typeof e["changedTouches"] !== "undefined") {
var touch = e.changedTouches[0];
pStop.x = touch.screenX;
pStop.y = touch.screenY;
} else {
pStop.x = e.screenX;
pStop.y = e.screenY;
}
swipeCheck();
}
function swipeCheck() {
var changeY = pStart.y - pStop.y;
var changeX = pStart.x - pStop.x;
if (isPullDown(changeY, changeX)) {
alert("Swipe Down!");
}
}
function isPullDown(dY, dX) {
// methods of checking slope, length, direction of line created by swipe action
return (
dY < 0 &&
((Math.abs(dX) <= 100 && Math.abs(dY) >= 300) ||
(Math.abs(dX) / Math.abs(dY) <= 0.3 && dY >= 60))
);
}
document.addEventListener(
"touchstart",
function (e) {
swipeStart(e);
},
false
);
document.addEventListener(
"touchend",
function (e) {
swipeEnd(e);
},
false
);

What about this?
var lastScrollPosition = 0;
window.onscroll = function(event)
{
if((document.body.scrollTop >= 0) && (lastScrollPosition < 0))
{
alert("refresh");
}
lastScrollPosition = document.body.scrollTop;
}
If your browser doesn't scroll negative, then you could replace line 4 with something like this:
if((document.body.scrollTop == 0) && (lastScrollPosition > 0))
Alternatively for android devices, you could swap out lastScrollPosition for "ontouchmove" or other gesture events.

have you tired these solutions??
You need to check this fiddle
var mouseY = 0
var startMouseY = 0
$("body").on("mousedown", function (ev) {
mouseY = ev.pageY
startMouseY = mouseY
$(document).mousemove(function (e) {
if (e.pageY > mouseY) {
var d = e.pageY - startMouseY
console.log("d: " + d)
if (d >= 200) location.reload()
$("body").css("margin-top", d / 4 + "px")
} else $(document).unbind("mousemove")
})
})
$("body").on("mouseup", function () {
$("body").css("margin-top", "0px")
$(document).unbind("mousemove")
})
$("body").on("mouseleave", function () {
$("body").css("margin-top", "0px")
$(document).unbind("mousemove")
})
and if you are looking for some plugin this plugin might help you

I know this answer has been answered by a lot many people but it may help someone.
It's based on Mr. Pryamid's answer. but his answer does not work touch screen. that only works with mouse.
this answer works well on touch screen and desktop. i have tested in chrome in desktop and chrome in android
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
}
.pull-to-refresh-container {
width: 100%;
height: 100%;
background-color: yellow;
position: relative;
}
.pull-to-refresh-content-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: white;
margin: 0px;
text-align: center;
}
</style>
</head>
<body>
<div class="pull-to-refresh-container">
<div class="pull-to-refresh-loader-container">
loading ...
</div>
<div class="pull-to-refresh-content-container">
here lies the content
</div>
</div>
<script>
var mouseY = 0
var startMouseY = 0
var container = document.querySelector(
".pull-to-refresh-content-container"
)
container.onmousedown = (ev) => {
mouseY = ev.pageY
startMouseY = mouseY
container.onmousemove = (e) => {
if (e.pageY > mouseY) {
var d = e.pageY - startMouseY
console.log("d: " + d)
container.style.marginTop = d / 4 + "px"
if (d >= 300) {
// alert("load more content");
}
} else {
container.onmousemove = null
}
}
}
container.onmouseup = (ev) => {
container.style.marginTop = "0px"
container.onmousemove = null
}
container.onmouseleave = (ev) => {
container.style.marginTop = "0px"
container.onmousemove = null
}
container.ontouchstart = (ev) => {
mouseY = ev.touches[0].pageY
startMouseY = mouseY
container.ontouchmove = (e) => {
if (e.touches[0].pageY > mouseY) {
var d = e.touches[0].pageY - startMouseY
console.log("d: " + d)
container.style.marginTop = d / 4 + "px"
if (d >= 300) {
// alert("load more content");
}
} else {
container.onmousemove = null
}
}
}
container.ontouchcancel = (ev) => {
container.style.marginTop = "0px"
container.onmousemove = null
}
container.ontouchend = (ev) => {
container.style.marginTop = "0px"
container.onmousemove = null
}
</script>
</body>
</html>

Here is how I did it with Stimulus and Turbolinks:
See video in action: https://imgur.com/a/qkzbhZS
import { Controller } from "stimulus"
import Turbolinks from "turbolinks"
export default class extends Controller {
static targets = ["logo", "refresh"]
touchstart(event) {
this.startPageY = event.changedTouches[0].pageY
}
touchmove(event) {
if (this.willRefresh) {
return
}
const scrollTop = document.documentElement.scrollTop
const dy = event.changedTouches[0].pageY - this.startPageY
if (scrollTop === 0 && dy > 0) {
this.logoTarget.classList.add("hidden")
this.refreshTarget.classList.remove("hidden")
if (dy > 360) {
this.willRefresh = true
this.refreshTarget.classList.add("animate-spin")
this.refreshTarget.style.transform = ""
} else {
this.refreshTarget.classList.remove("animate-spin")
this.refreshTarget.style.transform = `rotate(${dy}deg)`
}
} else {
this.logoTarget.classList.remove("hidden")
this.refreshTarget.classList.add("hidden")
this.refreshTarget.classList.remove("animate-spin")
this.refreshTarget.style.transform = ""
}
}
touchend(event) {
if (this.willRefresh) {
Turbolinks.visit(window.location.toString(), { action: 'replace' })
} else {
this.logoTarget.classList.remove("hidden")
this.refreshTarget.classList.add("hidden")
this.refreshTarget.classList.remove("animate-spin")
this.refreshTarget.style.transform = ""
}
}
}
body(class="max-w-md mx-auto" data-controller="pull-to-refresh" data-action="touchstart#window->pull-to-refresh#touchstart touchmove#window->pull-to-refresh#touchmove touchend#window->pull-to-refresh#touchend")
= image_tag "logo.png", class: "w-8 h-8 mr-2", "data-pull-to-refresh-target": "logo"
= image_tag "refresh.png", class: "w-8 h-8 mr-2 hidden", "data-pull-to-refresh-target": "refresh"

One simple way:
$(document.body).pullToRefresh(function() {
setTimeout(function() {
$(document.body).pullToRefreshDone();
}, 2000);
});

Related

DIY - Pinch to Zoom for mobile with Javascript

in my project I came across with a need for pinch to zoom support, but not the basic one.
and there are two main "must have" while doing so.
1) only the DIV container will zoom, and not the rest of the page (eg: top bar)
2) I need to keep the window basic scroll
I've added a simple code showing how I've done so and it left me with 2 questions
I) the page is jumpy while I'm pinching for zoom.
II) have I calculate this correctly? or should I have it another way?
* you can find a working example in the link below
https://www.klika.co.il/canvas/pinch.html
// ----------- TOUCH --------------
//handles on move events for tracking touches
var touches = {
points: null,
start: function (e) {
if (gesture.is.on) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
//reset touches
touches.points = null;
msg('touch start');
},
move: function (e) {
// msg('touch move');
if (e.touches.length > 1) {
// msg('touch move ' + e.touches.length + ' fingers');
//set touches only if two fingers are on
touches.points = { x: {}, y: {} }
for (var i = 0; i < e.touches.length; i++) {
touches.points.x[i] = e.touches[i].clientX;
touches.points.y[i] = e.touches[i].clientY;
}
}
},
end: function (e) {
msg('touch end');
}
};
// ----------- GESTURE --------------
var gesture = {
is: {
on: false,
init: false
},
data: {
content: null,
elem: null,
width: 0,
state: {},
},
start: function (e) {
gesture.is.on = true;
msg('gesture start');
gesture.data.content = $('.containter')[0];
gesture.data.elem = $('.containter .wrap')[0];
gesture.data.width = toFloat(gesture.data.elem.style.width, 100);
gesture.is.init = true;
},
move: function (e) {
if (!touches.points) { return; }
//set the body element for Y axis
const el = document.scrollingElement || document.documentElement;
//set mid points
var X1 = touches.points.x[0];
var Y1 = touches.points.y[0];
var X2 = touches.points.x[1];
var Y2 = touches.points.y[1];
var mid = getMidPoint(X1, Y1, X2, Y2);
//set start position on init
if (gesture.is.init) {
gesture.data.state = {
w: gesture.data.width / 100,
sl: ((gesture.data.content.scrollLeft + mid.x) / gesture.data.content.scrollWidth) * 100,
st: ((el.scrollTop + mid.y) / el.scrollHeight) * 100,
mid: mid
}
gesture.is.init = false;
}
//calc/set width of the wrapping DIV
var inc = e.scale * gesture.data.state.w;
var WPX = gesture.data.content.scrollWidth * inc;
var HPX = document.body.scrollHeight * inc;
var w = (WPX / gesture.data.content.scrollWidth) * 100;
gesture.data.elem.style.width = w + '%';
//calc/set scrollLeft of the wrapping DIV (X axis)
var sln = Math.round(((gesture.data.state.sl / 100) * gesture.data.content.scrollWidth) - (getWindowXY().w / 2));
if (sln < 0) { sln = 0; }
sln += gesture.data.state.mid.x - mid.x;
gesture.data.content.scrollLeft = sln;
//calc/set scrollLeft of the body element (Y axis)
var slt = Math.round(((gesture.data.state.st / 100) * el.scrollHeight) - (getWindowXY().h / 2));
if (slt < 0) { slt = 0; }
slt += gesture.data.state.mid.y - mid.y;
el.scrollTop = slt;
},
end: function (e) {
var w = toFloat(gesture.data.elem.style.width);
if (isNaN(w) || w < 100) {
gesture.data.elem.style.width = '100%';
}
gesture.is.on = false;
}
}
// -------- BIND ------
window.addEventListener('touchstart', touches.start, false);
window.addEventListener('touchmove', touches.move, true);
window.addEventListener('touchend', touches.end, true);
window.addEventListener('gesturestart', gesture.start, false);
window.addEventListener('gesturechange', gesture.move, false);
window.addEventListener('gestureend', gesture.end, false);
//------ GENERAL --------
String.prototype.format = function () {
var s = this,
i = arguments.length;
if (!s) {
return "";
}
while (i--) {
s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
}
return s;
}
var toFloat = function (val) {
return parseFloat(val.replace("%", ""));
}
var getWindowXY = function () {
var myWidth = 0, myHeight = 0;
if (typeof (window.innerWidth) == 'number') {
//Non-IE
myWidth = window.innerWidth;
myHeight = window.innerHeight;
} else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
//IE 6+ in 'standards compliant mode'
myWidth = document.documentElement.clientWidth;
myHeight = document.documentElement.clientHeight;
} else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
//IE 4 compatible
myWidth = document.body.clientWidth;
myHeight = document.body.clientHeight;
}
return {
h: myHeight,
w: myWidth
}
}
var msg = function (text) {
$('.info').html(text);
}
var getMidPoint = function (x1, y1, x2, y2) {
return { x: ((x1 + x2) / 2), y: ((y1 + y2) / 2) }
}
window.oncontextmenu = function (event) {
event.preventDefault();
event.stopPropagation();
return false;
};
window.addEventListener('error', function (e) {
if (e.message == "Script error.") { return; }
if (e.message.indexOf('facebook') != -1) { return; }
msg(e.message);
}, false);
body {
margin: 0;
padding: 0;
width:100vw;
}
.top{
position: fixed;
width: 100%;
background-color: #333;
line-height: 40pt;
text-align: center;
color: #f1f1f1;
font-size: 20pt;
left: 0;
top: 0;
}
.top .info{
}
.containter {
width: 100%;
overflow-x: auto;
}
.containter .wrap {
display: flex;
flex-direction: column;
width: 100%;
}
.containter .wrap img {
width: 100%;
margin-top: 30pt;
}
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, height=device-height, user-scalable=no" />
<title>Pinch to zoom</title>
<script src="https://www.klika.co.il/plugins/jqTools/js/jquery-1.7.1.min.js" type="text/javascript"></script>
</head>
<body>
<div class="top">
<div class="info">Pinch to zoom</div>
</div>
<div class="containter">
<div class="wrap">
<img src="https://thumb7.shutterstock.com/display_pic_with_logo/91858/594887747/stock-photo-dreams-of-travel-child-flying-on-a-suitcase-against-the-backdrop-of-sunset-594887747.jpg" />
<img src="https://thumb9.shutterstock.com/display_pic_with_logo/1020994/556702975/stock-photo-portrait-of-a-happy-and-proud-pregnant-woman-looking-at-her-belly-in-a-park-at-sunrise-with-a-warm-556702975.jpg" />
<img src="https://thumb7.shutterstock.com/display_pic_with_logo/234100/599187701/stock-photo-funny-little-girl-plays-super-hero-over-blue-sky-background-superhero-concept-599187701.jpg" />
<img src="https://thumb1.shutterstock.com/display_pic_with_logo/1316512/661476343/stock-photo-funny-pineapple-in-sunglasses-near-swimming-pool-661476343.jpg" />
<img src="https://thumb1.shutterstock.com/display_pic_with_logo/2114402/689953639/stock-photo-adult-son-hugging-his-old-father-against-cloudy-sky-with-sunshine-689953639.jpg" />
<img src="https://thumb7.shutterstock.com/display_pic_with_logo/172762/705978841/stock-photo-businessman-looking-to-the-future-for-new-business-opportunity-705978841.jpg" />
</div>
</div>
</body>
</html>

Stop moving element when he outside of parent element

So, I have an event listener keydown on arrow right, when you push the square moving on xPX but my parent element this w and h
set a 100px for example, and I would like to stop moving and I can't, I try with element.offsetWidth > 0 so them you can move.
Please look this fiddle : FIDDLE
Few errors in your code. I've commented fixes. Here is how i made it for the right arrow - you can apply same logic to the rest of the moves...
Code:
const carre = document.querySelector('.carre');
const main = document.querySelector('.main');
const w = main.offsetWidth - carre.offsetWidth; // this was wrong in your code
carre.style.left="0px"; // set start position
document.addEventListener('keyup', (event) => {
const positionLeft = parseInt(carre.style.left); //i've used style.left, rather, it gives expected numbers (10,20,30....)
if(event.keyCode == '39') {
if (positionLeft < w) { // this was fixed too
carre.style.left = (positionLeft) + 10 + 'px';
} else {
carre.style.left = '0'
}
}
})
DEmo:
const carre = document.querySelector('.carre');
const main = document.querySelector('.main');
const w = main.offsetWidth - carre.offsetWidth; // this was wrong in your code
carre.style.left="0px"; // set start position
document.addEventListener('keyup', (event) => {
const positionLeft = parseInt(carre.style.left); //i've used style.left, rather, it gives expected numbers (10,20,30....)
if(event.keyCode == '39') {
if (positionLeft < w) { // this was fixed too
carre.style.left = (positionLeft) + 10 + 'px';
} else {
carre.style.left = '0'
}
}
})
* {
box-sizing:border-box;
}
.carre {
position:absolute;
left: 0;
width: 50px;
height: 50px;
background-color: red;
}
.main {
position: relative;
width: 100px;
height: 100px;
border: 1px solid #000;
}
<main class="main">
<div class="carre"></div>
</main>
I rebuild your code:
document.addEventListener('keydown', (event) => {
const w = main.getBoundingClientRect().right - carre.getBoundingClientRect().right;
const positionLeft = carre.getBoundingClientRect().left;
if(event.keyCode == '39') {
if (w >= 0) {
carre.style.left = (positionLeft) + 10 + 'px';
} else {
carre.style.left = '0'
}
}
if (event.keyCode == '37') {
if (w >= 0) {
carre.style.left = (positionLeft) - 10 + 'px';
}else {
carre.style.left = '0'
}
}
})

JS .animate() not working in Firefox/IE

So I'm trying to animate a div but I'm not sure what I'm doing wrong here. The following code works just fine on the latest Safari and Chrome but not on Internet Explorer and Firefox.
On Firefox, the error being el.animate is not a function
Any suggestions/solutions?
var slowMo = false;
var dur = slowMo ? 5000 : 500;
function $(id) {
return document.getElementById(id);
}
var players = {};
var hue = 0;
function addTouch(e) {
var el = document.createElement('div');
el.classList.add('ripple');
var color = 'hsl(' + (hue += 70) + ',100%,50%)';
el.style.background = color;
var trans = 'translate(' + e.pageX + 'px,' + e.pageY + 'px) '
console.log(trans);
var player = el.animate([{
opacity: 0.5,
transform: trans + "scale(0) "
}, {
opacity: 1.0,
transform: trans + "scale(2) "
}], {
duration: dur
});
player.playbackRate = 0.1;
players[e.identifier || 'mouse'] = player;
document.body.appendChild(el);
player.onfinish = function() {
if (!document.querySelector('.ripple ~ .pad')) each(document.getElementsByClassName('pad'), function(e) {
e.remove();
});
el.classList.remove('ripple');
el.classList.add('pad');
}
}
function dropTouch(e) {
players[e.identifier || 'mouse'].playbackRate = 1;
}
function each(l, fn) {
for (var i = 0; i < l.length; i++) {
fn(l[i]);
}
}
document.body.onmousedown = addTouch;
document.body.onmouseup = dropTouch;
document.body.ontouchstart = function(e) {
e.preventDefault();
each(e.changedTouches, addTouch);
};
document.body.ontouchend = function(e) {
e.preventDefault();
each(e.changedTouches, dropTouch);
};
var el = document.body;
function prevent(e) {
e.preventDefault();
}
el.addEventListener("touchstart", prevent, false);
el.addEventListener("touchend", prevent, false);
el.addEventListener("touchcancel", prevent, false);
el.addEventListener("touchleave", prevent, false);
el.addEventListener("touchmove", prevent, false);
function fakeTouch() {
var touch = {
pageX: Math.random() * innerWidth,
pageY: Math.random() * innerHeight,
identifier: 'fake_' + Math.random() + '__fake'
}
addTouch(touch);
var length = Math.random() * 1000 + 500;
setTimeout(function() {
dropTouch(touch);
}, length)
setTimeout(function() {
fakeTouch();
}, length - 100)
}
if (location.pathname.match(/fullcpgrid/i)) fakeTouch(); //demo in grid
.ripple {
position: absolute;
width: 100vmax;
height: 100vmax;
top: -50vmax;
left: -50vmax;
border-radius: 50%;
}
body {
overflow: hidden;
}
.pad {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
<div class="pad"></div>
Code thanks to Kyle
So I took a different approach to achieve the same result. I think this one is a lot more straightforward and works in all browsers.
https://jsfiddle.net/cbrmuxcq/1/
Though there is a slight issue and I'm not sure what causes it. If I don't wait (by using setTimeout()), the transition doesn't happen but by setting a timeout it does. If anyone can spot the issue, I'll fix the fiddle and update my answer accordingly.

JavaScript check if element is outside top viewport

I have a code that works just fine to check if element is outside of bottom viewport, as follows:
function posY(elm) {
let test = elm, top = 0;
while(!!test && test.tagName.toLowerCase() !== "body") {
top += test.offsetTop;
test = test.offsetParent;
}
return top;
}
function viewPortHeight() {
let de = document.documentElement;
if(!window.innerWidth)
{ return window.innerHeight; }
else if( de && !isNaN(de.clientHeight) )
{ return de.clientHeight; }
return 0;
}
function scrollY() {
if( window.pageYOffset ) { return window.pageYOffset; }
return Math.max(document.documentElement.scrollTop, document.body.scrollTop);
}
function checkvisible (elm) {
let vpH = viewPortHeight(), // Viewport Height
st = scrollY(), // Scroll Top
y = posY(elm);
return (y > (vpH + st));
}
if (hasActivity && isHidden) {
isVisibleCSS = <div className='onscreen'>More activity below ↓</div>;
} else if (hasActivity && !isHidden) {
//technically, there's no need for this here, but since I'm paranoid, let's leave it here please.
}
My question is, how can I adapt this code or create a new one similar to this one that identifies when element is outside of top viewport?
Cheers.
For an element to be completely off the view top then the sum of the element's top offset and it's height, like in this JS Fiddle
var $test = document.getElementById('test'),
$tOffset = $test.offsetTop,
$tHeight = $test.clientHeight,
$winH = window.innerHeight,
$scrollY;
window.addEventListener('scroll', function() {
$scrollY = window.scrollY;
if ($scrollY > ($tOffset + $tHeight)) {
console.log('out of the top');
}
});
body {
margin: 0;
padding: 0;
padding-top: 200px;
height: 1500px;
}
#test {
width: 100px;
height: 150px;
background-color: orange;
margin: 0 auto;
}
<div id="test"></div>

How much of an element is visible in viewport

There's a div (brown rectangle) on the page. The page is higher than the viewport (orange rectangle) so it can be scrolled, which means that the div might only partially show up or not at all.
What's the simplest algorithm to tell how much % of the div is visible in the viewport?
(To make things easier, the div always fits into the viewport horizontally, so only the Y axis needs to be considered at the calculations.)
See one more example in fiddle:
https://jsfiddle.net/1hfxom6h/3/
/*jslint browser: true*/
/*global jQuery, window, document*/
(function ($) {
'use strict';
var results = {};
function display() {
var resultString = '';
$.each(results, function (key) {
resultString += '(' + key + ': ' + Math.round(results[key]) + '%)';
});
$('p').text(resultString);
}
function calculateVisibilityForDiv(div$) {
var windowHeight = $(window).height(),
docScroll = $(document).scrollTop(),
divPosition = div$.offset().top,
divHeight = div$.height(),
hiddenBefore = docScroll - divPosition,
hiddenAfter = (divPosition + divHeight) - (docScroll + windowHeight);
if ((docScroll > divPosition + divHeight) || (divPosition > docScroll + windowHeight)) {
return 0;
} else {
var result = 100;
if (hiddenBefore > 0) {
result -= (hiddenBefore * 100) / divHeight;
}
if (hiddenAfter > 0) {
result -= (hiddenAfter * 100) / divHeight;
}
return result;
}
}
function calculateAndDisplayForAllDivs() {
$('div').each(function () {
var div$ = $(this);
results[div$.attr('id')] = calculateVisibilityForDiv(div$);
});
display();
}
$(document).scroll(function () {
calculateAndDisplayForAllDivs();
});
$(document).ready(function () {
calculateAndDisplayForAllDivs();
});
}(jQuery));
div {
height:200px;
width:300px;
border-width:1px;
border-style:solid;
}
p {
position: fixed;
left:320px;
top:4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="div1">div1</div>
<div id="div2">div2</div>
<div id="div3">div3</div>
<div id="div4">div4</div>
<p id="result"></p>
Here's a snippet illustrating how you can calculate this.
I've put the % values in the boxes for readability, and it even kinda "follows" the viewport ^^ :
Fiddle version
function listVisibleBoxes() {
var results = [];
$("section").each(function () {
var screenTop = document.documentElement.scrollTop;
var screenBottom = document.documentElement.scrollTop + $(window).height();
var boxTop = $(this).offset().top;
var boxHeight = $(this).height();
var boxBottom = boxTop + boxHeight;
if(boxTop > screenTop) {
if(boxBottom < screenBottom) {
//full box
results.push(this.id + "-100%");
$(this).html("100%").css({ "line-height": "50vh" });
} else if(boxTop < screenBottom) {
//partial (bottom)
var percent = Math.round((screenBottom - boxTop) / boxHeight * 100) + "%";
var lineHeight = Math.round((screenBottom - boxTop) / boxHeight * 50) + "vh";
results.push(this.id + "-" + percent);
$(this).html(percent).css({ "line-height": lineHeight });
}
} else if(boxBottom > screenTop) {
//partial (top)
var percent = Math.round((boxBottom - screenTop) / boxHeight * 100) + "%";
var lineHeight = 100 - Math.round((boxBottom - screenTop) / boxHeight * 50) + "vh";
results.push(this.id + "-" + percent);
$(this).html(percent).css({ "line-height": lineHeight });
}
});
$("#data").html(results.join(" | "));
}
$(function () {
listVisibleBoxes();
$(window).on("scroll", function() {
listVisibleBoxes();
});
});
body {
background-color: rgba(255, 191, 127, 1);
font-family: Arial, sans-serif;
}
section {
background-color: rgba(175, 153, 131, 1);
height: 50vh;
font-size: 5vh;
line-height: 50vh;
margin: 10vh auto;
overflow: hidden;
text-align: center;
width: 50vw;
}
#data {
background-color: rgba(255, 255, 255, .5);
left: 0;
padding: .5em;
position: fixed;
top: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section id="one"></section>
<section id="two"></section>
<section id="three"></section>
<section id="four"></section>
<section id="five"></section>
<section id="six"></section>
<div id="data">data here</div>
After playing around a bit I think I've found perhaps the simplest way to do it: I basically determine how much the element extends over the viewport (doesn't matter in which direction) and based on this it can easily be calculated how much of it is visible.
// When the page is completely loaded.
$(document).ready(function() {
// Returns in percentages how much can be seen vertically
// of an element in the current viewport.
$.fn.pvisible = function() {
var eTop = this.offset().top;
var eBottom = eTop + this.height();
var wTop = $(window).scrollTop();
var wBottom = wTop + $(window).height();
var totalH = Math.max(eBottom, wBottom) - Math.min(eTop, wTop);
var wComp = totalH - $(window).height();
var eIn = this.height() - wComp;
return (eIn <= 0 ? 0 : eIn / this.height() * 100);
}
// If the page is scrolled.
$(window).scroll(function() {
// Setting the opacity of the divs.
$("div").each(function() {
$(this).css("opacity", Math.round($(this).pvisible()) / 100);
});
});
});
html,
body {
width: 100%;
height: 100%;
}
body {
background-color: rgba(255, 191, 127, 1);
}
div {
width: 60%;
height: 30%;
margin: 5% auto;
background-color: rgba(175, 153, 131, 1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
A little illustration to help understand how it works:
Chrome now supports Intersection Observer API
Example (TypeScript):
export const elementVisibleInPercent = (element: HTMLElement) => {
return new Promise((resolve, reject) => {
const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
entries.forEach((entry: IntersectionObserverEntry) => {
resolve(Math.floor(entry.intersectionRatio * 100));
clearTimeout(timeout);
observer.disconnect();
});
});
observer.observe(element);
// Probably not needed, but in case something goes wrong.
const timeout = setTimeout(() => {
reject();
}, 500);
});
};
const example = document.getElementById('example');
const percentageVisible = elementVisibleInPercent(example);
Example (JavaScript):
export const elementVisibleInPercent = (element) => {
return new Promise((resolve, reject) => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
resolve(Math.floor(entry.intersectionRatio * 100));
clearTimeout(timeout);
observer.disconnect();
});
});
observer.observe(element);
// Probably not needed, but in case something goes wrong.
const timeout = setTimeout(() => {
reject();
}, 500);
});
};
const example = document.getElementById('example');
const percentageVisible = elementVisibleInPercent(example);
Please note that the Intersection Observer API is available since then, made specifically for this purpose.

Categories