I have some JavaScript I'm using to add a class to my <body> element when the user is scrolling:
var $body = $('body');
$(window).scroll(function() {
$body.addClass('scrolling');
var scroll = $(window).scrollTop();
if (scroll <= 1) {
$body.removeClass('scrolling');
}
});
This works well on desktop browsers, but on iOS (and possibly other mobile devices) the class is only added upon touch release rather than when the user first touches the screen to scroll.
How do I adjust this script to trigger this class upon touch, whilst still working as normal for standard desktop users?
Here's a Fiddle showing this script in action: http://jsfiddle.net/alecrust/AKCCH/
You can use ontouchstart, ontouchmove and ontouchend to handle touch events.
var $body = $('body');
if ("ontouchstart" in document.documentElement)
{
$(window).bind('touchmove',function(e){
e.preventDefault();
$body.addClass('scrollingTouch');
});
$(window).bind('touchend',function(e){
e.preventDefault();
var scroll = $(window).scrollTop();
if (scroll <= 1) {
$body.removeClass('scrollingTouch');
}
});
}
else
{
$(window).scroll(function() {
$body.addClass('scrolling');
var scroll = $(window).scrollTop();
if (scroll <= 1) {
$body.removeClass('scrolling');
}
});
}
Here is a working jsfiddle: http://jsfiddle.net/BLrtr/
There might be some complications so I suggest you read this to understand them.
Also, you might want to check these 3rd party libraries for touch events handling.
Related
I am working on a Infinite scroll (on a div which have large number of subdiv)
infinite scroll is working for Desktop device but not for mobile devices
i checked that window.scroll function not getting fired for mobile device
i checked for some other events like OnTouchStart etc .. but none is working
Here is infinite scroll fiddle Infinite Scroll Fiddle
Here is infinite scroll project code example
infinitescroll: function() {
var $doc=$(document);
var $win=$(window);
var itemsPerScroll=5;
// hide everything that is out of bound
$('.scroll').filter(function(index){
return (($(this).offset().top) > $win.height());
}).hide();
$(window).scroll(function(event){
#event.preventDefault();
alert("here");
if ($doc.height()-$win.height()-$(this).scrollTop() == 0) {
$('.scroll:hidden:lt('+itemsPerScroll+')').show();
}
});
},
Alter the code to this and it should fix it -
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows
Phone/i.test(navigator.userAgent) == false) {
$(document).height() - window.visualViewport.height;
$('.article').filter(function(index){
return (($(this).offset().top) > $win.height());
}).hide();
$(window).scroll(function(){
if ($doc.height()-$win.height()-$(this).scrollTop() == 0) {
$('.article:hidden:lt('+itemstoshow+')').show();
}
})};
what I'm trying to do
I have dropdown-menus that open on hover and the parent menus have their own landing page link. we're not willing to sacrifice this behavior, but if obviously creates problem for large touch enabled devices. So, I'm detecting touch devices with jquery, and disabling the parent menu click on devices larger than 990px wide. devices below 990px is considered as mobile view and it switches to toggle. This switch between the toggle and the desktop view is expected to continue on screen rotation too.
what is happening
the menu link is disabled on first load and works as expected. Then I rotate the screen (from landscape to portrait) and see the mobile menu as expected and navigate to another page. once the page loads, I rotate it again (from portrait to landscape) and the desktop view is visible, but the parent links are clickable now!
I want to prevent this click event on second rotation too. HTML is standard bootstrap 3 navigation code and my js is like this:
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
$(document).ready(function () {
$(window).resize(function () {
var o = $(window).innerWidth();
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
if ((isTouchDevice() === true) && (o >= 990)) {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
return false
})
})
alert('oi!!')
}
else {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
location.href = this.href;
})
});
alert ("bad!") //for debugging purpose, not really needed
}
}).resize();
//the mobile menu clicks events
$('#menu .dropdown > a ').click(function () {
location.href = this.href;
});
});
PS this is a website, not an android app. I have found answers that answer this type of questions for android apps.
Update the jsfiddle for my code
I solved it myself. Turns out, the condition for width checking was creating the problem and in my case, unnecessary, because bootstrap is already hiding the menu in smaller screens and I was targeting touch enabled desktop devices anyway. so I took off && (o >= 990) from the if condition and it is working as expected.
full js is below (in case anyone needs it). I used the timer to prevent the event from firing before the resize, but it will probably work without the timer too. :
$(document).ready(function () {
var resizeTimer;
$(window).on('resize', function(e){
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
if (isTouchDevice() === true) {
$('.navbar .dropdown > a ').click(function () {
return false
});
console.log("landscape")
}
else {
$('.navbar .dropdown > a ').each(function () {
$(this).on("click", function(){
location.href = this.href;
})
});
console.log("portrait")
}
}, 250)
}).trigger('resize');
});
I think this is a problem with the way you are recognizing the mobile device. For checking device sizes I would not suggest using $(window).innerWidth(). What you are doing now does not check the screen size, rather it checks the window size, which fluctuates when switching orientation.
I would like to suggest that instead of checking for only >900px, that you check for the entire area of the device (width x height) so landscape and portrait would act the same way. And I would like to suggest using screen.availHeight * screen.availWidth to determine this.
I really hope this helps you with your problem. Please let me know if not and I'll help you debug.
Now that Bootstrap 3 is out, what is the recommended option for enabling touch? As before, there aren't many touch events in the bootstrap.js, though it is supposed to be a mobile first framework.
The last thing I've found on github suggests using fastclick.js, but that was before the v3.0 release.
My recommendation is to use Bootstrap alongside JQuery mobile, TouchSwipe, or Hammer.js . An example of a bootstrap touch carousel can be found here.
Start working on another fully working Touch Carousel on GitHub. This also includes drag events...
Despite I believe bootstrap is a joke of a css framework (especially due to no multileveled navigation), I would probably agree with others to go with some different carousel if you have a choice.
From my experience JQuery mobile will work rather smoothly but my site was not built alongside jquery mobile and the css belonging to it really messed the things up.
<script>
$(document).ready(function() {
$('.carouselresp').carousel({'data-interval': 6000, 'data-pause': "hover"});
var clicking = false;
var currentMousePos = 0;
var newMousePos = 0;
$('.carouselresp img').on('mousedown', function(event) {
clicking = true;
currentMousePos = event.pageX;
});
$('.carouselresp img').on('touchstart', function(event) {
clicking = true;
var touchstart = event.originalEvent.touches[0];
currentMousePos = touchstart.pageX;
});
$(document).on('mouseup', function(event) {
clicking = false;
});
$('.carouselresp img').on('touchend', function(event) {
clicking = false;
});
$(document).on('mousemove', function(event) {
if (!clicking) {
return;
}else {
if (event.pageX < currentMousePos) {
if ((currentMousePos - event.pageX) > 50) {
$('.carouselresp').carousel('next');
clicking = false;
}
} else {
if ((event.pageX - currentMousePos) > 50) {
$('.carouselresp').carousel('prev');
clicking = false;
}
}
}
});
$('.carouselresp img').on('touchmove', function(event) {
var touch = event.originalEvent.touches[0];
if (!clicking) {
return;
}else {
if (touch.pageX < currentMousePos) {
if ((currentMousePos - touch.pageX) > 50) {
$('.carouselresp').carousel('next');
clicking = false;
}
} else {
if ((touch.pageX - currentMousePos) > 50) {
$('.carouselresp').carousel('prev');
clicking = false;
}
}
}
event.preventDefault();
});
});
</script>
It works fine for me on android and iphone too, plus I am allowing the move event in browsers with no touch support.
I hope it helped.
TomHre
I'm creating a responsive template and I want to remove the listeners on an element when screen is being resized or is smaller than the specified width.
Imagine an menu which when you hover on it's items, it shows you the sub-menus in normal displays but the same menu in mobile devices will show the sub-menus only by tapping or clicking on the items.
I can't make the undelegate work. In resized screen I still have the mouseover and mouseout event-listeners. I'm not getting any errors in console and I've tried both:
.off('mouseover', 'li')
.off('mouseover')
.undelegate('li', 'mouseover')
.undelegate('li')
and none of them works.
var $window = $(window);
function handleSidenav() {
$(".nav-list").delegate('li', 'mouseover', function(e) {
$(this).find("a").addClass('active');
$(this).find("div.sub-items").toggle();
}).delegate('li', 'mouseout', function(e) {
$(this).find('a').removeClass('active');
$(this).find("div.sub-items").toggle();
});
}
function checkWidth() {
var windowsize = $window.width();
if (windowsize < 767) {
smallScreenDelegation();
} else {
SmallScreenUndelegation();
}
}
checkWidth();
handleSidenav();
$window.resize(checkWidth());
function smallScreenDelegation() {
$(".nav-list").undelegate('li'); //It's not working
$(".nav-list").undelegate('li'); //It's not working
$(".nav-list").delegate('li a:first', 'click', function(event) {
if ($(this).next().is(':hidden')) {
$(this).addClass('active');
$(this).next().slideDown('slow');
} else {
$(this).removeClass('active').next().slideUp('slow');
}
event.preventDefault();
});
}
You need to wrap window in the jQuery object. I'm not sure if you set $window = $(window), but it seems here that $window.width() and $window.resize(checkWidth) are missing parenthesis. I was able to get it working fine once I changed those to $(window). You have to define which event you want to undelegate. I used:
$('.nav-list').undelegate('li', 'mouseover');
Open up console and you can see that it works: http://jsbin.com/efonut/6/edit
Also, it's really best to use .on() and off() vs .delegate() and .undelegate(), but at least this works...
I still don't know what was wrong with undelegate which I couldn't make it work, but I managed to fix my code by using on and off.
As adeneo said I was delegating and undelegating on each window resize which was quiet a bug and I think I fixed that but holding the last state on device variable.
var $window = $(window);
var device;
function desktopSidenav() {
$(".nav-list > li").off('click');
$(".nav-list > li").on('mouseover', function(e) {
$(this).find("a").addClass('active');
$(this).find("div.sub-items").toggle();
}).on('mouseout', function(e) {
$(this).find('a').removeClass('active');
$(this).find("div.sub-items").toggle();
});
}
function handheldSidenav() {
$(".nav-list > li").off('mouseover').off('mouseout');
$(".nav-list > li").on('click', function(e) {
if ($(this).find("div.sub-items").is(':hidden')) {
$(this).find("a:first").addClass('active').next().slideDown('slow');
} else {
$(this).find("a:first").removeClass('active').next().slideUp('slow');
}
e.preventDefault();
});
}
Now I check the window size before doing anything else an I'll hold the device type in device variable. If window is resized, I'm gonna check the device state and do the things based on device type.
if ($window.width() > 767) {
device = 'desktop';
desktopSidenav();
} else {
device = 'handheld';
handheldSidenav();
}
$window.resize(function() {
if ($window.width() > 767) {
if (device == 'handheld') {
device = 'desktop';
desktopSidenav();
}
} else {
if (device == 'desktop') {
device = 'handheld';
handheldSidenav();
}
}
});
If I use delegate and undelegate instead of on and off, the code won't work and I still don't know why, so this cannot be count as a real answer, but I wanted to tell everyone who has a similar problem to use jQuery's on and off instead on delegate.
I'm trying to write mechanism on site which prevents users to scroll normally. When user scrolls down or up the site is smoothscrolling to next or previous slide (depends on scrolling direction) and stops there (like when you click on a navbar). See live preview: CLICK HERE
But there's an annoying problem. It works almost good in FF (no jumping), but breaks in another browsers (Chrome, Safari, IE)- it jumps. How can I prevent this?Here are snippets from my code.
I have a ScrollControl object where I prevent scrolling:
scrollControl = {
keys : [32, 37, 38, 39, 40],
scrollTimer : 0,
lastScrollFireTime : 0,
preventDefault : function(e){
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
},
keydown : function(e){
for (var i = scrollControl.keys.length; i--;) {
if (e.keyCode === scrollControl.keys[i]) {
scrollControl.preventDefault(e);
return;
}
}
},
wheel : function(e){
scrollControl.preventDefault(e);
},
disableScroll : function(){
if (window.addEventListener) {
window.addEventListener('DOMMouseScroll', scrollControl.wheel, false);
}
window.onmousewheel = document.onmousewheel = scrollControl.wheel;
document.onkeydown = scrollControl.keydown;
},
enableScroll : function(){
if (window.removeEventListener) {
window.removeEventListener('DOMMouseScroll', scrollControl.wheel, false);
}
window.onmousewheel = document.onmousewheel = document.onkeydown = null;
}
}
Then I'm listening if mousewheel occurs and trying to execute function only once (I'm using this plugin to detect mousewheel PLUGIN )
$(window).mousewheel(function(objEvent, intDelta){
var minScrollTime = 1000;
var now = new Date().getTime();
function processScroll() {
console.log("scrolling");
if(intDelta>0){
$.smoothScroll({
speed:med.effectDuration,
easing:med.scrollEase,
scrollTarget:med.prevPage,
afterScroll: function(){
med.currentPage = med.prevPage;
med.setActiveNav();
med.setSlides();
med.runAnimations();
}});
}else if(intDelta<0){
//scrollControl.disableScroll();
$.smoothScroll({
speed:med.effectDuration,
easing:med.scrollEase,
scrollTarget:med.nextPage,
afterScroll: function(){
med.currentPage = med.nextPage;
med.setActiveNav();
med.setSlides();
med.runAnimations();
}});
}
}
if (!scrollControl.scrollTimer) {
if (now - scrollControl.lastScrollFireTime > (3 * minScrollTime)) {
processScroll(); // fire immediately on first scroll
scrollControl.lastScrollFireTime = now;
}
scrollTimer = setTimeout(function() {
scrollControl.scrollTimer = null;
scrollControl.lastScrollFireTime = new Date().getTime();
processScroll();
}, minScrollTime);
}
});
I'm executing scrollControl.disableScroll function on DOM ready event when users starts website. And actually scrolling once prevention doesn't works prefectly and sometimes it triggers smoothscrolling twice. What am I doing wrong? Thanks in advance for any suggestions!
I had the same issue the Mouse Wheel Event was fired Twice.
function wheelDisabled(event){
event.preventDefault();
event.stopImmediatePropagation();
return false;
}
Also you might use both of these Events.
window.addEventListener('DOMMouseScroll', wheel, false);
window.addEventListener('mousewheel', wheel, false);
Instead of trying to prevent scrolling with Javascript, I would try a different approach. This approach includes CSS and Javascript to make sure the website is never bigger then the viewport (hence no scrollbars!).
Use CSS to force the main wrapping div (a div that wraps all the content on the site) to have overflow: hidden. Then use Javascript to dynamically ensure that the height and width of this div is always equal to the viewport's height and width.
In this scenario, if you want to implement scrolling in a predefined way you choose you can dynamically add negative margin-top (or negative margin-left for horizontal scrolling) to the parent wrapping div to give it the appearance that it is scrolling.