I need to keep the scroll offset of an element in sync with another (the window actually) and I'm having trouble doing so during the inertial "roll off" phase of scrolling on Mobile Safari (iPad).
I have a couple of divs with position:fixed; overflow:hidden and I need to keep their scroll offset in sync with the window's one (meaning the entire body scroll.) Usually I'd code it like this (jQuery):
var $win = $(window),
$div1 = $(...)
$win.scroll(function() {
$div1.scrollTop($win.scrollTop())
})
But testing the interface on an iPad, I noticed that the div was not being updated neither during the touch phase, when you are dragging the virtual page with your finger, nor during the inertial phase, when you let go and the page slows down to a stop.
I solved it for the dragging phase by registering the handler for the touchmove event as well as the scroll one.
But I can't find a way to solve the problem for the inertial phase. The div stays still (and goes slowly out of sync with the rest of the page) until the inertial movement comes to a full stop, when the scroll event is finally fired and it skips into position.
Here's a working demo.
Try to scroll it on an iPad to see the "inertial scolling" problem. Unfortunately I couldn't get it to work on jsFiddle, due to the iPad's weird behaviour with iframe scrolling.
If I could just run a polling during that phase, I could keep a semblance of synchronization between the two elements. I've tried with setTimeout, setInterval, and requestAnimationFrame, but neither of them fires during the inertial scrolling phase. It seems like all Javascript stops during that phase.
Questions:
Is there any touch or scroll event fired during the inertial scrolling phase?
Is there any way to run a Javascript callback during that phase?
Is there a way to sync the scroll offset of two elements (either X or Y, not both) using CSS or some other technology other than JS?
OldDrunkenSailor beat me to suggesting iScroll.
Unfortunately, out of the box iScroll just replicates the same problem as native inertial scrolling -- there's no event handling during the inertial phase.
Here's a version of your demo with a monkey-patched iScroll to add a custom event that fires even during the inertial stage: https://dl.dropbox.com/u/15943645/scrollingdemo.html
Works great on my 2nd gen iPad.
JS:
// Disable touch events
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
// Patch iScroll for position change custom event
iScroll.prototype._oldPos = iScroll.prototype._pos;
iScroll.prototype._pos = function(x, y) {
this._oldPos(x, y);
if (this.options.onPositionChange) this.options.onPositionChange.call(this);
}
$(function() {
var $win = $(window),
$div_cols = $('#cols'),
$div_rows = $('#rows'),
$div_body = $('#body')
// attach scrolling sync handler and execute it once
function sync_scroll(e) {
$div_cols.scrollLeft(0 - $div_body.position().left);
$div_rows.scrollTop(0 - $div_body.position().top);
}
// initialize iScroll on wrapper div, with position change handler
var myScroll = new iScroll('iscroll_wrapper', {
bounce: false,
onPositionChange: sync_scroll
});
})
CSS:
#iscroll_wrapper {
position:absolute;
z-index: 1;
left: 168px;
top:77px;
bottom:0px;
right:0;
overflow:auto;
}
#body {
position:absolute;
z-index: 1;
width: 2046px;
height: 3376px;
}
Note only the body responds to touch events, but you can extend the technique to the rows and cols divs for the reverse relationship.
iOS actually freezes DOM manipulation and Javascript while inertial scrolling is happening. Here is a simple demo that I made to illustrate the difference between scrolling in a normal desktop environment vs the iPad: http://jsfiddle.net/notjoelshapiro/LUcR6/. This code only has this JS in it:
var num = 0;
function updateNum(){
num++;
$('#awesomeDiv').text(num);
}
$(window).scroll(updateNum);
On scroll it increments a number and displays it on the bottom of the page. You'll see that the scroll number at the bottom of the screen is only being incremented when the inertial scroll has stopped. If Javascript was acting in the background it wouldn't refresh until the scroll ended but the number should be incremented higher.
So to answer your questions specifically:
Is there any touch or scroll event fired during the inertial scrolling phase?
Negative, ghostrider.
Is there any way to run a Javascript callback during that phase?
See above.
Is there a way to sync the scroll offset of two elements (either X or Y, not both) using CSS or some other technology other than JS?
Not quite sure what you mean here, JS can do all of the math'ing that you need but it will have to be when inertial scroll has completed. To be honest though, this may be a symptom of tl;dr since I'm at work right now.
Have you looked at the iScroll library? It simulates intertial (or non-inertial) scrolling for touch and non-touch environments and gives you JS callbacks during/after "scroll" and "inertial scroll" and provides a lot of information that you can use to calculate where you are on the page.
Triggers when a scroll begins. Note that iOS devices freeze DOM manipulation during scroll, queuing them to apply when the scroll finishes. We're currently investigating ways to allow DOM manipulations to apply before a scroll starts.
http://jquerymobile.com/demos/1.0rc1/docs/api/events.html#/demos/1.0rc1/docs/api/events.html
Thats what they had to say under scroll start section
Related
with WooCommerce 4.2, on iOS, FlexSlider (v2.7.2) show a bug with a horizontal slider: when the focus is on the slider as the user swipes left or right, the vertical scroll is still active, there is some level of vertical movement. That makes the slider "rebound".
You can see the bug in this video: https://youtu.be/bM8zcv3ciTo
EDIT: My question is similar to this one, but related to Flexslider.
I have this in functions.php:
/*PRODUCT PAGE FlexSlider Options*/
add_filter( 'woocommerce_single_product_carousel_options', 'filter_single_product_carousel_options' );
function filter_single_product_carousel_options( $options ) {
if ( wp_is_mobile() ) {
$options['controlNav'] = true; // Option 'thumbnails' by default
$options['direction'] = "horizontal";
$options['slideshow'] = false; // Already "false" by default
}
return $options;
}
Any idea how to prevent this vertical scroll while the focus is on the Flexslider?
Anybody also has this bug?
I installed a test site with only Wordpress and WooCommerce, and the bug is here, so it's not plugin-related: http://woo.makemy.biz/produit/produit-de-test/
Edit: Adapting the owl carousel solution would result in code that looks like this:
//You should replace ".myFlexslider" with an appropriate class.
jQuery('.myFlexslider').data('flexslider').vars.before = function(){
jQuery('body').css('overflow', 'hidden');
};
jQuery('.myFlexslider').data('flexslider').vars.after = function(){
jQuery('body').css('overflow', '');
};
which should work. https://codepen.io/Terrafire123/pen/YzWRXOJ
It's not possible to do this via PHP, because PHP doesn't expose the API necessary to do this.
You should know, that preventing vertical scroll when the slider swipes is usually discouraged because this can result in the slider being annoyingly difficult to scroll past.
Original answer: The fact that the issue seems to occur with both Flickity and Flexsider seems to indicate that it isn't a problem with either one, but rather with the user.
A careful review of the Youtube video you have seems to indicate it's working correctly, and there is no real bug. Every time the slider doesn't move in your video, it's because
a. He's sliding vertically, not horizontally, or
b. He's sliding in a direction he can't slide because loop is disabled and he's at the first element.
The rebound effect is normal when you scroll to the top of the page in iOS. If desired, you can disable the bouncing effect with a workaround like this:
html {
position: fixed;
height: 100%;
overflow: hidden;
}
body {
width: 100vw;
height: 100vh;
position:relative;
overflow:auto;
-webkit-overflow-scrolling: touch;
}
Today I'm facing "a typical" problem on iOS with a page scrolling when a user selects an input text. The point is that I'm using a scrollable page including a lot of texts and I need this. So I can't use a solution that disables scolling by preventing default, minimizing the view etc..
I basically need to open a software keyboard without scrolling the page.
I also need to find a solution that will be suitable for my users - no screen blinking, etc... After some research I finally managed to find a working solution.
Please see my answer below.
The idea is to return the page to its original position as soon as possible and thus prevent the iOS scrolling animation.
You simply add a focus handler to your input text field and in this handler you firstly read the window.scrollTop property and than set it back with a delay of 0 ms.
Here is my code (using jQuery):
$("#myinput").on("focus", function() {
var scrollTop = $(window).scrollTop();
setTimeout(function() {
$(window).scrollTop = scrollTop;
}, 0);
});
I hope there is no bug in this - actually I'm using TypeScript so if you want to see my original code here it is:
this._inputText.on("focus", () => {
var jQueryWin:JQuery = <JQuery>$(window);
var scrollTop:number = jQueryWin.scrollTop();
setTimeout(() => {
jQueryWin.scrollTop(scrollTop);
}, 0);
});
OK - I hope this helps you guys to better control an unwanted page scrolling when dealing with input elements.
I am trying to set up a JQuery Mobile 1.3 site that uses a panel and a slider.
Problem is, that using the slider triggers the panel, which opens on a "swiperight" event, as I am moving the slider to the right. The slider will be for pagination, the panel for a menu.
Code here:
http://jsfiddle.net/kMARn/1/
Move the slider to the right and the panel will open.
I have tried using the .not() selector for the panel to not react on the slider:
$(document).not("#slider").on("swiperight", function(event, ui) {
$("#myPanel").panel("open");
});
But it won't work, the panel opens when i move the slider to the right. Tried a bunch of variants too, but I'm lost...
Any ideas?
Thanks!
A bit late to the party, but you can disable swipe-to-close by setting the data-swipe-close attribute to "false" on the panel div.
http://jquerymobile.com/demos/1.3.0-beta.1/docs/panels/options.html
In my case I used this simple code, without data-swipe-close = "false" in panel.
Keeping panel close with swipe right, outside of the slider.
$('#panel').find('#slider')
.on('slidestop',function(e,ui) {
var value = e.target.value;
//...operations with slider value...
})
.parent().on('swiperight',function(e,ui) {
e.stopPropagation(); //block panel close
})
From the 1.3.0b1 Docs for Swipe:
"Triggers when a horizontal drag of 30px or more (and less than 75px
vertically) occurs within 1 second duration"
This applies to and can be configured for swiperight too. You can make the slider small in length and this would ensure that both the slider event stop and the swipe are not triggered at the same time, yet that may not be practical for all scenarios.
What might be better is to bind the swipe right to a DIV or section of the page. By this, I mean if you have a 75 px div box on the left hand side of the display, and when a swipe event occurred within that div, it could trigger the menu.
I feel the logic here might be better controlled by a button, much like used in the Facebook App to display there slide out menu. In the Dolphin browser on Android, this type of event also triggers a bookmark menu, so if a page has a swiperight event and trigger it, I sometimes get both the event and the bookmark menu from the App. Annoying!
I did fork your jsfiddle and will play with it more (http://jsfiddle.net/Twisty/Hg2pw/). FYI, they have JQM 1.3.0b1 in their available frameworks so you don't have to link it in your HTML. If I find some more info, I will comment here.
The following solution is more a workaround. It should be relatively reliable though.
$(document).ready( function () {
var menu_available = true;
$(document).on("swiperight", function(event, ui) {
if (menu_available) $("#myPanel").panel("open");
});
$("#slider").on("slidestop", function( event, ui ) {
menu_available = false;
window.setTimeout(function() {menu_available= true;},250);
});
});
The variable menu_available is false for a 250 milliseconds right after the slide stops. The window.setTimeout block will reset the variable so that the menu is available again.
This is a stupid workaround, but jQuerys function event.stopEventPropagation(), which IMHO would be the correct way to go, didn't work.
I'm using Jquery mobile and doing some custom stuff.
The default collapsible object just seems to 'show' the hidden content instantly, which I find a bit user unfriendly. A few problems occur with this in that if the button is at the bottom of the screen, and the hidden content is off screen, then the user might not know that anything has even happened.
In my mind two things should happen.
The content should slideDown().
I should have the option to have the page scroll down so that the button finds itself at the top of the screen, in doing so guaranteeing the the previously hidden content is visible.
Any pointers in how I might go about doing either of these?
If I understood you correctly you are talking about a collapsible content block and when a user taps on the header it should scroll down a bit so that the body part is shown to the user.
You can do it by attaching a click event to the header that triggers a scrolling. In the code below I have done it as an animation. I also have adjusted the scroll position with -40px so that the user still sees some part of the elements that are on top of the header.
$('.ui-collapsible-heading-collapsed').on('click.scrollintoview', function (event) {
$('body').animate({ scrollTop: $(event.target).offset().top - 40}, 500);
});
I have a basic JQM page which displays a left side sliding menu when swiping (like the facebook mobile app). It worked fine until I started using scrollview (to properly keep the header fixed). The swipe event is not triggered when I swipe over my page content (it still works if I swipe on the header).
$('.ui-page-active').live("swiperight", function() {
if (!menuStatus) {
showMenu();
}
});
Does anyone has any idea on how to make it work?
Cheers!
I think you might find that the event is consumed in the scrollview control. You can override the javascript handler against that control to allow it to keep bubbling.
To override the function you can use this technique: Overriding a JavaScript function while referencing the original
Having a quick look at the file, it looks like this method could hold clues as to what you want:
_handleDragMove: function(e, ex, ey)
specifically:
var svdir = this.options.direction;
if (!this._directionLock)
I was using the version of scrollview shown here: http://jquerymobile.com/test/experiments/scrollview/scrollview-nested.html