jQuery Detect Bottom of Page on Mobile Safari/iOS - iphone

I basically want the same functionality as facebook, twitter and all those other "infinite" scroll sites work, the code im using at the moment is
jQuery(document).ready(function(){
$ = jQuery;
$(window).scroll(function(){
if ($('.iosSlider').is(':visible'))
{
if($(window).scrollTop() + $(window).height() == $(document).height())
{
$.get('/our-work/fakework.php', function(data) {
$('#mobile-thumbs').append(data);
});
}
}
});
});
This works flawlessly on all desktop browers, and even on my blackberry sometimes it works after spamming the scroll down button.
However its not once been detected on either the iphone or ipad, I assumed it was something todo with the viewport on it but who knows.
I tried using the viewport height method of
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
but this didnt seem to fix it either!
So please could somebody share some light on it please as to how to detect the bottom of the page on the iDevices!
Thanks!!
Owen

After debugging for ages i found out that
if($(window).scrollTop() + $(window).height() == $(document).height())
was never actually getting met, well it WAS getting met however it seems that mobile safari doesnt run any javascript WHILST the viewport is moving.
This means that unless you stop the scroll EXACTLY on the document height (no bouncy bottom thing) it would very UNLIKELY to equal the same heights.
So I simply changed the code to instead of equaling the same height, to check if it was equal or more, this way it would trigger even if it had been scrolled past!
so the fix is here below
if($(window).scrollTop() + $(window).height() >= $(document).height()){
so the modified code now looks like
jQuery(document).ready(function(){
$ = jQuery;
$(window).scroll(function(){
if ($('.iosSlider').is(':visible'))
{
if($(window).scrollTop() + $(window).height() >= $(document).height())
{
$.get('/our-work/fakework.php', function(data) {
$('#mobile-thumbs').append(data);
});
}
}
});
});
and its now working like a charm!

Fully working multibrowser and multidevice-compatible solution:
function getDocumentHeight() {
return Math.max(
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
Math.max(document.body.offsetHeight, document.documentElement.offsetHeight),
Math.max(document.body.clientHeight, document.documentElement.clientHeight)
);
}
And then....
$(window).scroll(function() {
var docHeight = getDocumentHeight();
if($(window).scrollTop() + window.innerHeight == docHeight)
{
// enter your code here
}
});
Don't forget about viewport meta too:
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">

I had the same issue. The code snippet works fine on desktop but not on iOS mobile devices. After replacing document with body the issue was fixed.
Also, it's better to check if you're near bottom of the screen:
if($(window).scrollTop() + $(window).height() > $('body').height() - 200)

This solution will work on every device:
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');
}
}

Related

HTML5 Video - currentTime not setting properly on iPhone

I have a basic HTML5 video set up from which I load one of four videos. The problem I'm having is that when I load the next video, it continues playing from the previous time position. Efforts to set the currentTime property seem to be either short lived or ignored entirely.
I have added listeners to a collection of events and have something like this in each one;
myPlayer.addEventListener("loadeddata", function() {
console.log(" loadeddata: before = " + myPlayer.currentTime);
myPlayer.currentTime = 0.1;
console.log(" loadeddata: after = " + myPlayer.currentTime);
}, false);
Sometimes I see the time change for one event but not persist correctly;
durationchange: before = 19.773332595825195
durationchange: after = 0.10000000149011612
loadedmetadata: before = 0.10000000149011612
loadedmetadata: after = 19.773332595825195
loadeddata: before = 19.773332595825195
loadeddata: after = 0.10000000149011612
canplay: before = 0.10000000149011612
canplay: after = 19.773332595825195
And sometimes it never even seems to set at all;
durationchange: before = 50.66666793823242
durationchange: after = 50.66666793823242
loadedmetadata: before = 50.66666793823242
loadedmetadata: after = 50.66666793823242
loadeddata: before = 50.66666793823242
loadeddata: after = 50.66666793823242
canplay: before = 50.66666793823242
canplay: after = 50.66666793823242
This seems similar to the issue here but there didn't seem to be any resolution. Has anyone encountered this issue on iPhone before?
TL;DR: change currentTime on the loadeddata event. This works for audio too.
It looks like Safari (and the problem is still appearing for me on Safari 11.1) is Safari will not allow currentTime to be changed when a video is first loaded IF it hasn't loaded a frame for that currentTime yet. The bigger problem: the wrong solution can break Chrome (and likely other browsers too).
Fortunately, we have a lot of events we can listen for while media is loading:
During the loading process of an audio/video, the following events occur, in this order:
loadstart
durationchange
loadedmetadata
loadeddata
progress
canplay
canplaythrough
-W3Schools (I know it's not a preferred source, but I couldn't find the same info on MDN)
I tried adjusting currentTime on different events, but on the first 3 events, Safari would move the time back to 0; and on 5 and 6 it seemed to prevent the video from playing in Chrome, because it would get stuck at currentTime (which I could've worked around, but I thought there was a better solution).
(I didn't want to have to load the whole file to go to the right spot, because I want to support hefty videos. So canplaythrough wasn't an option for me.)
The description for the loadeddata event reads:
The loadeddata event is fired when the first frame of the media has finished loading.
-MDN
When I changed currentTime on loadeddata, Safari could tell that the frame was loaded and available, and would update correctly. It also wouldn't cause Chrome to freeze in a single spot while playing.
The problem and solution are identical for audio.
From my findings the issue seems to be that on iPhone only (iPad works fine) the currentTime property will not be set correctly until the "canplaythrough" event, however changing the currentTime at that point will cause a noticeable hiccup. The solution for that would be to intentionally pause the video after calling load...
myVideo.load();
myVideo.pause();
...and then call play in the event when the time has reset.
The second problem however is when the duration of the new movie is shorter then the currentTime position. In this case not only does currentTime fail to set but "canplaythrough" is never called, and QT just sits at the end of the video doing nothing.
I discovered the solution to both problems was to force a secondary load if the currentTime was not reset in the event BEFORE "canplaythrough". Its a bit round about with the timer callback but it seems to do the trick;
var myVideo = document.getElementById("video1");
myVideo.addEventListener("canplay", function() {
console.log(" canplay: before = " + myVideo.currentTime);
myVideo.currentTime = 0.1;
console.log(" canplay: after = " + myVideo.currentTime);
if( myVideo.currentTime < 1 ) {
myVideo.play();
}
else {
myVideo.load();
myVideo.pause();
setTimeout(checkStarted, 500);
}
}, false);
function checkStarted()
{
console.log(" checkStarted called");
myVideo.play();
}
I had to set the preload attribute to metadata (on the HTML of the main video element) and set the currentTime of the video element within the loadedmetadata event listener.
<video id="myVideo" preload="metadata">
<source src="/path/to/video" type="video/mp4">
</video>
JS
VideoEl.addEventListener('loadedmetadata', VideoMetaDataLoaded);
function VideoMetaDataLoaded() {
VideoEl.currentTime = newTime;
}
Below is my Angular/Ionic solution to restore the video position. It works on IOS, Android, Chrome and Safari.
HTML:
<video preload="metadata"
poster="{{ resource.thumbnail_file }}"
playsinline webkit-playsinline
#videos>
...
</video>
Typescript:
#ViewChildren('videos') videos: QueryList<any>;
videoCache: Map<number, number> = new Map<number, number>();
private restoreVideoPositions() {
var context = this;
setTimeout(() => {
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
var currentTime = context.videoCache.get(video.id);
if (currentTime != null && currentTime > 0) {
console.log('add listener', currentTime);
video.addEventListener("loadeddata", function () {
if (video.readyState >= 3) {
video.currentTime = currentTime;
// video.play();
}
});
video.load();
}
});
}, 0);
}
private storeVideoPositions() {
var context = this;
this.videoCache.clear()
this.videos.forEach(function (item, idx) {
var video = item.nativeElement;
video.pause();
context.videoCache.set(video.id, video.currentTime)
});
}
I used a combination of the last two answers here, but had to reduce the ready state limit to 2 (HTMLVideoElement.prototype.HAVE_CURRENT_DATA) as the loadeddata event on iOS would often never come in higher than 2.
With help from the other answers here I came up with the next solution:
HTMLVideoElement.prototype.playFromTime = function(currentTime){
let that = this;
that.load();
that.pause();
that.currentTime = currentTime;
let loadedMetadata;
loadedMetadata = function(event){
that.currentTime = currentTime;
that.removeEventListener("loadedmetadata", loadedMetadata);
}
if(that.currentTime !== currentTime){
that.addEventListener("loadedmetadata", loadedMetadata);
}
that.play();
}
//usage example:
myVidElement.playFromTime(20);
Which in my situation works on an iPhone on Safari, Safari on the desktop, Firefox on macOS, Chrome on macOS. That's all I have tested for now.
Had the same problem with a play video on scroll with React. The following solved it for me.
useEffect(() => {
videoRef.current.load();
}, []);
const { render } = ReactDOM;
const { useState, useEffect, useRef } = React;
const Video = ({ src, height, length }) => {
const [currentTime, setCurrentTime] = useState(0);
const videoRef = useRef(null);
useEffect(() => {
// This is needed for IOS
videoRef.current.load();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const getCurrentTime = () => {
const percentScrolled = window.scrollY / (height - window.innerHeight);
return length * percentScrolled;
};
const handleScroll = (e) => {
const time = getCurrentTime();
videoRef.current.currentTime = time;
setCurrentTime(time);
};
return (
<div style={{ height }}>
<p>Time: {currentTime.toFixed(2)}s</p>
<video ref={videoRef} muted playsInline>
<source src={src} type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};
const App = () => {
return (
<Video
length={5}
height={3000}
src="https://lqez.github.io/js/airpodsvf/video.mp4"
/>
);
};
render(<App />, document.getElementById("root"));
body {
background: black;
}
p {
color: slategrey;
top: 0;
position: fixed;
z-index: 1;
padding: 20px;
}
video {
top: 60px;
position: fixed;
width: 100%;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
this is a problem with chrome on local assets. happened with me. when i was serving video from local assets and setting time it resets the video to 0.
so the solution that worked for me was serving the video from s3 bucket.

Why do the pages blink/flicker after transitions in my jQuery Mobile PhoneGap app on iOS?

I have a jQuery Mobile app that I've converted to an iOS app using PhoneGap. I'm using version 1.1.0 of jQM.
I'm using "fade" transitions between pages (as I read they were less demanding).
When initially running the PhoneGap version of the app in the iPhone Simulator I was getting a flicker/flash after every page transition - as if the page was being displayed, cleared and then redisplay - all with a fraction of a second. Some thing happened when I ran it on the device.
I applied the advice in Sarah-Jane's answer to a similar question.
This fixed the problem in the simulator, but not on the actual device.
Has anyone experienced this problem, and found a solution?
This guy solved the problem - it worked for me:
http://outof.me/fixing-flickers-jumps-of-jquery-mobile-transitions-in-phonegap-apps/
CSS:
body {
/* Setting body margins to 0 to have proper positioning of #container div */
margin: 0;
}
/* #container div with absolute position and 100% width and height so it takes up whole window */
#container {
position: absolute;
width: 100%;
height: 100%;
}
JS:
$(document).one("mobileinit", function () {
// Setting #container div as a jqm pageContainer
$.mobile.pageContainer = $('#container');
// Setting default page transition to slide
$.mobile.defaultPageTransition = 'slide';
});
And wrap all your jQM pages in a single <div id="container">
Fade transition blinks mostly you should change it to slide or some other transition mode.
That might help
<meta name="viewport" content="width=device-width, user-scalable=no" />
use following code
$(document).ready(function()
{
$.mobile.defaultPageTransition = "none"
$.mobile.defaultDialogTransition = 'none';
$.mobile.useFastClick = true;
$.mobile.touchOverflowEnabled = true;
});

FB.Canvas.getPageInfo not working

I am working on a FB app and used FB.Canvas.getPageInfo to get the scroll of the canvas to set popups in the screen center because the application is not scrolling but its the fb canvas which is scrolling and to find the center of the screen on long pages i need to get the number of pixels the canvas is scrolled and then with some plus minus i get the screen center of current page position. This was working fine till yesterday but is not since morning, I have tried alot but no success yet.
the code is called in setTimeout function, until the value is received I forcefully set my popup's dispaly to none and show a loader image.
remember it was working fine til last night
here is the code
$('.popup-call-local').live('click', function(event){
//FB.Canvas.getPageInfo(function(info){alert(info.scrollTop);});
var view_name = this.id;
var check_view_name = view_name.replace(/_/g, '-');
if($('#'+check_view_name).length > 0){
$('#popupDiv').css('height',$('.wrap').css('height'));
$('#popupDiv').css('display','block');
$('#popupDiv').css('z-index',1000);
var x = Number((window.screen.width - 400) / 2);
var y = Number(event.pageY)+ 10;
$('#popupDiv').html('<img src="<?php echo base_url(); ?>images/loaderblack.gif" style="position:absolute; z-index:50000; left:'+x+'px; top:'+y+'px;" />');
$('#'+check_view_name).css('z-index',2000);
var viewportHeight = window.screen.height;
var windowScrolled = -1;
$foo = jQuery('#'+check_view_name),
elWidth = $foo.width(),
elHeight = $foo.height(),
elOffset = $foo.offset();
setTimeout(function(){FB.Canvas.getPageInfo(function(info){windowScrolled=info.scrollTop; while(windowScrolled == -1){
$('#'+check_view_name).css('display','none');}
var v = (((viewportHeight - elHeight) / 2) + (windowScrolled) - 100); if(v<=0){v=Number(v)*Number(-1);}$('#popupDiv').html('');$('#'+check_view_name).css('display','block');$('#spinner').css('display','none');$('#'+check_view_name).css('top', Number(v) + 'px');});}, 3000);
}else{
return false;
}
});
EDIT
well
just found that its a facebook bug reported here
http://developers.facebook.com/bugs/185097921606322?browse=search_4f60961cc946d7143000700

webpage in the landscape mode of iPhone

I want to lock the horizontal scroll of my webpage in mobile Safari.
Specifying the width n height in background.js fixed the problem for iPad :
if(B.msie<=8 || B.iPad) (function() {
var mixin, i,
bg_tile = static_url('bg_tile');
if(B.iPhone || B.iPad) {
bg_tile = static_url('iOS_bg_tile');
}
for(i in mixin={
width: 768,
height: 600,
fade: false,
srcs: {
tile: bg_tile,
radial: static_url('bg_radial')
},
customCSS: function(el) {
el.style.background = 'url('+this.srcs.tile+')';
},
mainSrc: 'radial'
}) if(mixin.hasOwnProperty(i)) BG.BackgroundView.prototype[i] = mixin[i];
})();
But if I add a similar block for iPhone it's giving zooming problem.
Any suggestion how can I lock the horizontal scroll in iPhone?
Thanks in advance.. :)
Have you tried using a meta tag for viewport, fix the width as device width.
<meta name = "viewport" content = "width = 320">

Fancybox Positioning Inside Facebook Canvas iFrame

OK so I have a iframe canvas app with its height set to "Settable" with the facebook javascrip sdk calls to FB.Canvas.setSize(); and FB.Canvas.setAutoGrow();. These are working perfectly, as the iframe gets set to a certain pixel height based on its content.
The problem is that when I make a call to Fancybox, it positions itself based on this height. I know that's exactly what its supposed to do as the fancybox jQuery returns the viewport by:
(line 673 of latest version of jquery.fancybox-1.3.4.js):
_get_viewport = function() {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollTop() + currentOpts.margin
];
},
But the problem is the iframe will, for a lot of viewers, be longer than their browser window. So the Fancybox centers itself in the iframe and ends up only partly visible to the majority of viewers. (i.e. iframe height is 1058px and users browser is say only 650px).
Is there a way to have fancybox just calculate the physical browser height? Or do I need to change some settings in my Facebook canvas app to make it work?
I like how the only scrollbar is the one on Facebook (the parent, if you will).
All suggestions GREATLY appreciated!
For fancybox 2 try:
find:
_start: function(index) {
and replace with:
_start: function(index) {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
F._start_orig(index);
}
);
} else {
F._start_orig(index);
}
},
_start_orig: function (index) {
Then in function getViewport replace return rez; with:
if (window.canvasInfo) {
rez.h = window.canvasInfo.clientHeight;
rez.x = window.canvasInfo.scrollLeft;
rez.y = window.canvasInfo.scrollTop - window.canvasInfo.offsetTop;
}
return rez;
and finally in _getPosition function replace line:
} else if (!current.locked) {
with:
} else if (!current.locked || window.canvasInfo) {
As facebook js api provides page info, then we could use it, so
find
_start = function() {
replace with
_start = function() {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
_start_orig();
}
);
} else {
_start_orig();
}
},
_start_orig = function() {
and also modify _get_viewport function
_get_viewport = function() {
if (window.canvasInfo) {
console.log(window.canvasInfo);
return [
$(window).width() - (currentOpts.margin * 2),
window.canvasInfo.clientHeight - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
window.canvasInfo.scrollTop - window.canvasInfo.offsetTop + currentOpts.margin
];
} else {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
$(document).scrollTop() + currentOpts.margin
];
}
},
I had the same problem, i used 'centerOnScroll' :true, and now it works fine...
Had the same problem. Thankfully fancybox is accessable through CSS. My solution was to overwrite fancybox's positioning in my CSS file:
#fancybox-wrap {
top: 20px !important;
}
This code places the fancybox always 20px from top of the iframe. Use a different size if you like. The !important sets this positioning even though fancybox sets the position dynamically at runtime.
Here's one way to do it by positioning the Fancybox relative to the position of another element, in my case an Uploadify queue complete div that displays a view link after the user uploads an image.
Have a style block with a set ID like so:
<style id="style-block">
body { background-color: #e7ebf2; overflow: hidden; }
</style>
Then the link to open the Fancybox calls a function with the image name, width, and height to set the content and sizes. The important part is the positioning. By getting the position of the queue complete div, generating a new class declaration (fancy-position), appending it to the style block BEFORE the fancybox loads (!important in class will override positioning from fancybox), then adding the new class using the wrapCSS parameter in the fancybox options, it positions the fancybox exactly where I want it.
function viewImage(image, width, height) {
var complete_pos = $('#image_queue_complete').position();
var css_code = '.fancy-position { top: ' + complete_pos.top.toString() + 'px !important; }';
$('#style-block').append(css_code);
var img_src = '<img src="images/' + image + '" width="' + width.toString() + '" height="' + height.toString() + '" />';
$.fancybox({
content: img_src,
type: 'inline',
autoCenter: false,
wrapCSS: 'fancy-position'
});
}