I have designed my site with an image gallery that does not display the next / previous arrows until the mouse is rolled over them .next:hover{display: block} This is fine but the site is fluid and I did not think at all that hover (mouse over) does not work on touch screen! I do not want to keep the arrows on constant display unless the device is touch screen only like an ipad for example!
What is the best way to detect if the device is touch only and if so change display: none to display block automatically. Is there a jQuery way of doing this?
I hope this makes sense, I have googled it and search on S.O. but am unable to find a direct answer. That along with the fact that I am a bit (understatement) of a novice when it comes to jQuery!
Thank you for any help
I worked something out like this, the click counter also fires on touch events when I was testing it.
// Detect touchscreen only
var touchscreenonly = true;
var moves = 0;
var clicks = 0;
$("body").mousemove(function() {
moves++;
if (moves > clicks + 1){
touchscreenonly = false;
// now unbind these functions if you like
$(this).unbind("mousemove").unbind("click");
console.log('mouse moved, this is not a touch-only device');
}
}).click(function(){
clicks++;
});
I was in a same situation like you but, I did not wanted to use Modernizr, so as a quick fix I just checked for the window width.Most of the touch devices including tablets falls below the width of <=1024. So I wrote the below piece of code inside document.ready:
$(document).on("ready", function() {
if ( $(window).width() < 1025 ){
//Insert your code to add/display something different for mobile/tablet devices
}
});
Hope it helps someone.
Related
I have a site that opens a modal upon click that contains a form. I noticed on my iPad that when the soft keyboard is open it covers several fields of the form, and as I am near the bottom of the screen, I cannot scroll to reveal those hidden fields.
In researching this issue I came across this answer which includes code from this answer. However, neither of these answers seem to work when tested on iOS 8.3.
Here is what I would like to achieve:
Detect when the keyboard is opened.
Find the height of the keyboard.
Add padding to the bottom of the footer to accommodate the height of the keyboard.
When the keyboard is dismissed or closed, the padding is removed.
There are a few things to note:
If someone is using a connected keyboard (including bluetooth keyboards) don't do anything as the soft keyboard shouldn't appear
jQuery is okay to use.
A solution must run via client side code.
I prefer a solution that covers iOS and Android. Preferably, any device that can use a soft keyboard.
How could I achieve a solution that will increase the padding in my footer, that will work on the majority of devices, when someone is using a soft keyboard as a means of filling out a form in a modal?
I bumped into a problem similar to this not too long ago and I found a small solution to this, when a mobile or tablet is being used and a keyboard is activated it triggers a resize event of the screen so you could use that to trigger a function.
var lastHeight = $(window).height(); // store the intial height.
var lastWidth = $(window).width(); // store the intial width.
var keyboardIsOn = false;
$(window).resize(function () {
if ($("input").is(":focus")) {
keyboardIsOn =
((lastWidth == $(window).width()) && (lastHeight > $(window).height()));
}
if(keyboardIsOn){
var keyboardHeight = lastHeight - $(window).height();
$("footer").css("padding", keyboardHeight+"px");
} else{
$("footer").removeAttr("style");
//or if you just want to remove the padding
//$("footer").css("padding", 0);
}
});
//An alternative solution is by checking if the height of the screen
//change on input/textarea focus.
$('input, textarea').focus(function() {
keyboardIsOn =
((lastWidth == $(window).width()) && (lastHeight > $(window).height()));
if(keyboardIsOn){
var keyboardHeight = lastHeight - $(window).height();
$("footer").css("padding", keyboardHeight+"px");
} else{
$("footer").removeAttr("style");
//or if you just want to remove the padding
//$("footer").css("padding", 0);
}
});
In all versions prior to iOS8, I was able to prevent the iPhone keyboard from pushing up (and destroying) my html/css/js view when the keyboard appeared by the following method:
$('input, select').focus(function(event) {
$(window).scrollTop(0);
// or via the scrollTo function
});
Since iOS8, this no longer works. One workaround is to place this code within a setTimeOut
setTimeout(function() { $(window).scrollTop(0); }, 0);
But it only makes the view do a jerky motion as the view is initially pushed up by iOS, then dragged back down by my js code. preventDefault and stopPropagation does not help either.
I've tried everything available on the web of course including my own solution posted here: How to prevent keyboard push up webview at iOS app using phonegap but so far, nothing works for iOS8. Any clever ideas on how to prevent the keyboard in iOS8 to push/move the view?
Try position:fixed on body, and/or wrap content in a div and position:fixed on it as well.
There are some options :
Make listener on your ios code, to move the screen up along with the keyboard height, so everything move up along with the keyboard, then your design save.
Make your css design responsive. Then no problem with change height, it will be scrollable inside your webview.
When keyboard pushes up view in iOS, a scroll event is triggered ($(window).scrollTop() is changed). You can put $(window).scrollTop(0) inside the scroll event handler. To prevent the jerky motion, set opacity to 0 during scrolling. Related codes may look like this:
function forceScrollTop() {
var scrollTop = $(window).scrollTop();
if (scrollTop != 0) {
$(window).scrollTop(0);
$(selector).css('opacity', 1);
$(window).off('scroll', forceScrollTop);
}
}
// when an input is focused ...
$(selector).css('opacity', 0);
$(window).on('scroll', forceScrollTop);
I have the following function that checks the scrolling position of a user so that the menu becomes fixed once they scroll past the masthead
function checkScrolling() {
if( $(window).scrollTop() > $('.masthead').height() ) { // we check against ACTUAL height
$('.menu').addClass('fixed');
}else {
$('.menu').removeClass('fixed');
}
}
and here the desktop and touch screen event listeners:
$(document).bind('touchstart touchend touchcancel touchleave touchmove', function(e){
checkScrolling();
});
$(window).scroll(function(){
checkScrolling();
});
However the touch events only make the menu fixed during the touchmove reliably. If I scroll really fast with a swipe up or down, there is a delay before the menu becomes fixed or unfixed.
Any ideas on how to fix this? See a code example here: http://dev.driz.co.uk/mobileMasthead.html (has been amended based on some answers below, but still does not work correctly on an iPad or iPhone)
Update:
And reading up on the topic reveals that JS like checking scroll position don't happen during the scroll... BUT... I've noticed that http://www.facebook.com/home/ doesn't have this issue when testing it on my iPad. So it's definitely possible to achieve this effect without using any custom JavaScript scrollbars like iScroll or similar.
Maybe I understand the question wrong, but to ensure functionality with high speed scrolling, why don't you tackle it the pure CSS way (aka faking the 'fancy' effect):
Old School (fast but primitive)
HTML
<div id="menu2"></div>
<div class="scroll" id="scroller">
<div id="overlay"></div>
<div id="menu"></div>
</div>
With the simple CSS:
html, body { overflow: hidden; height: 100% }
body { margin:0; padding:0; }
.scroll {
-webkit-overflow-scrolling: touch;
height:960px;
width:640px;
}
#menu2 {
width:640px;
height:20px;
background:red;
position:absolute;
z-index:-1;
}
#menu {
width:100%;
height:20px;
background:red;
z-index:0;
}
You can see a working example HERE.
It may be a bit primitive: but hey! It works! :)
New School (fancy but slow)
Check all of the other answers supplied.
But note that it 'is known' that the usage of JavaScript in combination with scrolling on mobile devices is causing a lot of trouble regarding speed.
That's why I think the simple CSS approach may be better.
If you want to learn about JavaScript and scrolling on mobile devices (and other functions), then there are two articles which I highly recommend reading:
Fast animations with iOS Webkit
Native scrolling: Grins and Gotchas
Facebook doesn't use JavaScript but pure css:
position: -webkit-sticky;
If i remember it correctly this makes the element stick at the top of its parent container when scrolled.
You just needed to attach your scroll events, not to window, document or body, but to a custom container.
On iOS you can't programatically react during these hardware-accelerated window scrolling behaviour.
Here's a fiddle:
a wrapper:
<div id="content">
some not-so-relevant css:
html,body,#content {
width:100%;
height:100%;
}
#content {
background-color: lightblue;
overflow:scroll;
}
attaching the listeners to the container:
function checkScrolling() {
if ($('#content').scrollTop() > mastheadHeight) {
menu.addClass('fixed');
} else {
menu.removeClass('fixed');
}
}
$('#content').scroll(function () {
checkScrolling();
});
You can see it working here for the JS-only fallback:
http://jsfiddle.net/jaibuu/YqPzS/
direct URL: http://fiddle.jshell.net/jaibuu/YqPzS/show/
I once tried to implement sticky headers on mobile version of a site but had encountered whole set of problems.
First and most important is that scroll event does not fire on mobile devices as often as it does on desktop. Usually it does fire when page stops. I don't know the exact reason for that but I can suggest it's because browsers on mobile "send" such tasks to GPU meantime CPU can not keep JS objects up to date with what happens to the canvas (but that's just my suggestion). Newer version of iOSes are making it better for us and probably scroll will work on iPhones.
You should use touch events. This makes you write a separate version of code for devices that support touch input. So have to look for reliable ways of testing for platform.
But touch events are also tricky especially on Android. I thought that in the callback for touchmove event I will be able to figure out current coordinates and go further from that point.
But There this issue https://code.google.com/p/android/issues/detail?id=5491 on Android, in summary touchmove fires once or twice at the very beginning of the touch action and does not fire again. Which makes it totally useless. People are suggesting preventDefault() but you no longer can scroll with that.
So I ended up with idea to reimplement scrolling from scratch using CSS transforms. And here is my results so far:
http://jsbin.com/elaker/23
Open that link on your device and http://jsbin.com/elaker/23/edit on your desktop: you'll be able to edit code and it will live update on you device, very convenient.
NOTE:
I'd like to warn you that this CSS scrolling thing is raw and there are some known problems that are not resolved yet: like you can sometimes scroll beyond top or bottom boundaries or if you just touch (not move) it still will scroll. Also the notorious URL bar will not hide. So there is work to do.
Do you need the touch events to fire this at all? Modern devices should return $(window).scroll() events and scrollTop values. On older Android and and pre-ios5 (I think), position:fixed: didn't work as expected because the of how the viewport worked. But it has been corrected in all new releases.
To further debug devices, I highly recommend Adobe Edge Inspect. You could console.log the scrollTop and see if the devices you care about actually work correctly with out any trickery. You'll get everything you need with their free version.
A great way to dealing with scroll events is not to attach your checks to the scroll event
takes a lot of resources and doesn't work very well with older browsers.
fortunately you can have a lot more control if you just perform a time loop to do that.
codewise that looks like that:
(It's used by twitter)
var MyMenu = $('#menu'),
didScroll = false;
$(window).scroll(function() {
didScroll = true;
});
setInterval(function() {
if ( didScroll ) {
didScroll = false;
//Do your check here
checkScrolling();
}
}, 180);
You should put your $('.masthead').height() outside this checkScrolling function of yours (in a higher scope) as this kind of operations takes a lot of resources and always ask jquery to "select your element" and calculate its size will eventually make your application laggy:
var headerHeight = $('.masthead').height()
function checkScrolling()
.....
Last thing , you can change the value of the interval attribute (right now it's 180 (a bit more that 60hz -. refresh time of the screen) you can make this value bigger, if you want your app to be more performant but a bit less accurate)
Thanks John Resig and twitter:
http://ejohn.org/blog/learning-from-twitter/
Hope that helped
Ajios !
I have just tested your functions for efficiency. You should try this
function checkScrolling() {
if( $(window).scrollTop() > mastheadHeight )menu.css('position','fixed');
else menu.css('position','');
}
This will reduce function call of addClass and RemoveClass and your execution time will take effect. If you want to reduce more execution time, then better to use pure JavaScript
$(document).ready(function(){
var p = $("#stop").offset().top;
$(window).scroll(function(){
if(p<$(window).scrollTop()){
console.log("div reached");
$("#stop").css({position:"fixed",top:0});
}
else{
console.log("div out");
$("#stop").css({position:"static"});
}
})
});
I think this will help you.
The total code is here in jsfiddle.net.
I have tested it for ipad using safari of online ipad2 simulator in http://alexw.me/ipad2/ and it has worked there. So, I think it will work on real ipad too.
setScrollTop: function(inTop) {
var top = Math.max(0,inTop);
if (wm.isMobile) { // should always be true for touch events
top = Math.min(top, this.listNode.clientHeight - this.listNodeWrapper.clientHeight);
if (dojo.isWebKit) {
this.listNode.style.WebkitTransform = "translate(0,-" + top + "px)";
} else if (dojo.isMoz) {
this.listNode.style.MozTransform = "translate(0,-" + top + "px)";
} else if (dojo.isOpera) {
this.listNode.style.OTransform = "translate(0,-" + top + "px)";
} else if (dojo.isIE) {
this.listNode.style.MsTransform = "translate(0,-" + top + "px)";
}
this.listNode.style.transform = "translate(0,-" + top + "px)";
this._scrollTop = top;
this._onScroll();
} else {
this.listNode.scrollTop = top + "px";
}
},
Im fairly new to JS... please be gentle.
Can anyone suggest a way to pull off a delayed autoscroll effect on a block of text?
It's important to mention that my ultimate goal is to use this on a popup modal window, on iOS devices. And because iOS browsers do not display the scrollbar until user interaction, I am resorting to the auto-scroll.
In effect: I would like the page to load, wait a couple of seconds, then have begin to slowly scroll down. The scroll is intended to be a hint to the user that there is more content available, therefore if there is any way to stop or temporarily pause the auto-scroll on user interaction- that would be optimal.
I have searched for my answers a couple of hours now, but between not being able to initialize the found code to my design (again, I'm fairly green), and not being able to find a solution that achieves everything I need - I am turning to brighter minds.
I have set up a fiddle with my HTML and CSS: http://jsfiddle.net/zfMsQ/
Any help is greatly appreciated!
ps: This is my very first post on StackOverflow :)
My code:
Extensive. Linked above.
Here you go: http://jsfiddle.net/zfMsQ/3/
var roll = true;
var max = 0;
var text = $("#content");
function scroll() {
text.scrollTop(text.scrollTop() + 1)
var top = text.scrollTop()
if (top > max) {
max = top
if (roll) {
setTimeout(scroll, 50)
}
}
}
text.on("mouseenter mouseover mousedown", function(){
roll = false;
})
setTimeout(scroll, 2000)
I am using the most wonderful javascript tool iScroll4 http://cubiq.org/iscroll-4 on a mobile website for iOS and Android. Here is what my layout looks like:
The horizontally scroll-able area is making use of iScroll4 with the following settings:
var myScroll = new iScroll('frame', { hScrollbar: false, vScrollbar: false, vScroll: false })
The horizontal scrolling part works great. This issue is what happens when a user attempts to scroll up or down the page placing their finger on the horizontal scrolling area. So I need native vertical scrolling, and iScroll horizontal scrolling on the same area.
What I have tried so far:
Removing e.preventDefault() in the iScroll code (allows for native scrolling, but in BOTH axes).
Removing e.preventDefault() and then disabling horizontal scrolling page wide with this:
var touchMove;
document.ontouchstart = function(e){
touchMove = e.touches[0];
}
document.ontouchmove = function(e){
var theTouch = e.touches[0] || e.changedTouches[0];
var Xer = rs(touchMove.pageX - theTouch.pageX).toPos();
var Yer = rs(touchMove.pageY - theTouch.pageY).toPos();
touchMove = theTouch;
if(Yer > Xer){ e.preventDefault(); }
}
which seems to do nothing. How can I allow for native vertical scrolling in the horizontal scrolling area, without loosing the horizontal scrolling of iScroll? I am really stumped here. Thanks in advance.
(just for the record rs(foo).toPos() is a function that makes foo a positive number regardless of its value).
If you would like to achieve the effect described by Fresheyeball without hacking the core, and without changing from iScroll to swipeview, then iScroll 4 does offer you its event listeners to work with.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollMove: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
} else {
// delegate the scrolling to window object
window.scrollBy( 0, -this.distY );
}
},
});
By doing so, the onBeforeScrollMove-Handler checks whether the scroll direction seems to be horizontal, and then prevents the default handler, thus effectively locking the scroll action to the X-Axis (try commenting it out, you'll see the difference). Otherwise, if the scroll direction needs to be vertical, we make the browser scroll via the window.scrollBy() method. This is not exactly native, but does the job just fine.
Hope that helps
Lukx
[EDIT]
My original solution, which didn't use window.scrollBy() ,did not work on slower Samsung phones, which is why I needed to adapt the answer.
Suggested edit to #Lukx's excellent solution. New versions of iScroll4 place the e.preventDefault() in onBeforeScrollMove which can be overridden. By placing the if block into this option, default is not prevented for vertical scrolling, and vertical can scroll natively.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollStart: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
}
},
});
With iscroll 5, you can set eventPassthrough: true to achieve this. See http://iscrolljs.com/#configuring
OLD ANSWER
UPDATE a special pluggin has been written just to address this problem:
http://cubiq.org/swipeview
I found a way!
add a variable to the top of the document: if android is 15 and is iOS is 3
var scrollTolerance = ( rs().isDevice('android') )?15:3;
disable the original e.preventDefault(); for scrolling. This is under onBeforeScrollStart:
the in _move just under
timestamp = e.timeStamp || Date.now();
add this line
if( Math.sqrt(deltaX*deltaX) > scrollTolerance){e.preventDefault();}
What this does is the following:
the scrollTolerance sets, you guessed it, a tolerance for finger direction. We don't want to demand a perfect vertical angle to get the up down native scroll. Also iOS does not detect properly and will never be higher than 4 for some reason so I used 3. Then we disable iScroll's standard e.preventDefault(); which prevents native vertical scrolling on our bi-scrollable area. Then we insert e.preventDefault(); only upon move and based on finger direction from tolerance.
This does not work perfect. But is acceptable and works on iOS and Android. If anyone sees better ways please post here. This is something I (and assume others) need to use regularly, and we should have a perfect rock solid solution.
Thanks.
Please test this solution from Adam.
https://gist.github.com/hotmeteor/2231984
I think the trick is to add the check in onBeforeScrollMove. First get the initial touch position in onBeforeScrollTouchStart and then in onBeforeScrollMove check the new position and then disable the required scroll based on the difference.
iScroll 5 supports native scrolling of any axis!
http://iscrolljs.com/
on iScroll5 just set eventPassthrougt to true. That fixes it.