I am using the jquery.mousewheel.js plugin (Github) to build a horizontal scrolling website. It works so far, but I am not able to figure out one problem:
If I am using a trackpad (e.g. on a MacBook Pro) and scroll horizontally with two fingers, depending on how parallel the fingers are positioned, the site will stuck or is confused in which direction it should scroll.
Is there a way to make this horizontal scroll also working smooth?
This is the code I use in the head part:
$(function () {
$("html, body, *").mousewheel(function (event, delta) {
this.scrollLeft -= (delta * 5);
this.scrollRight -= (delta * 5);
event.preventDefault();
});
});
Here my jsfiddle with the rebuilt situation: http://jsfiddle.net/mU24m/
even if this question is a couple of months old, I'll leave something that worked for me.
The idea is that trackpads usually move simultaneously on X and Y, so if we detect a horizontal movement in the scroll wheel listener it is likely a trackpad. Note that you only need to adjust left not both left and right.
$(".container").mousewheel( function(e, delta) {
if( Math.abs(e.deltaX) ) {
this.scrollLeft -= (e.deltaX * 20);
} else {
this.scrollLeft -= (e.deltaY * 20);
}
e.preventDefault();
});
You could also remember that there was a movement on X and then only respond to that axis for a second or so :
var timeout, trackpad = false;
$(".modernlayout .wrapper").mousewheel( function(e, delta) {
if( trackpad || Math.abs(e.deltaX) ) {
// probably using trackpad
// only respond on X axis for a second
trackpad = true; clearTimeout( timeout );
timeout = setTimeout(function(){ trackpad = false; }, 1000);
// use a smaller multiplier
this.scrollLeft -= (e.deltaX * 10);
} else {
// most likely not trackpad
this.scrollLeft -= (e.deltaY * 20);
}
e.preventDefault();
});
Related
I am working on a multi-image slider (i.e. several images appear next to each other at the same time and move together) that can be moved with the mouse wheel. I detect the direction of the mouse wheel (up or down) and the slider moves to the left or the right accordingly. It works quite well, the only problem is that I can't get the last picture to stop at the edge of the slider, so it doesn't jump inside if you roll a bigger with the mouse wheel (same for the first picture if you scroll backwards). I would be really grateful for some assistance. Thank you in advance. This is my JS code:
// move slider on mouse wheel scrolling depending on mouse wheel direction
aboutSliders[i].addEventListener('wheel', () => {
// let slideWidth = firstImg.getBoundingClientRect().width;
// get the first slide's position from left side of screen
let sliderLeft = firstImg.getBoundingClientRect().left;
// get the slider wrapper's position from left side of screen
let sliderWrapperLeft = aboutSliders[i].getBoundingClientRect().left;
// get the last slide's position from left side of screen
let sliderRight = lastImg.getBoundingClientRect().right;
// get the slider wrapper's position from left side of screen
let sliderWrapperRight = aboutSliders[i].getBoundingClientRect().right;
// detect mouse wheel's direction (up or down)
function detectMouseWheelDirection(e) {
var delta = null,
direction = false;
if (!e) {
// if the event is not provided, we get it from the window object
e = window.event;
}
if (e.wheelDelta) {
// will work in most cases
delta = e.wheelDelta / 60;
} else if (e.detail) {
// fallback for Firefox
delta = -e.detail / 2;
}
if (delta !== null) {
direction = delta > 0 ? 'up' : 'down';
}
return direction;
}
function handleMouseWheelDirection(direction) {
// if mousewheel direction is 'down' and the slider wrapper's position is not further to the right than the last slide's, move the slider to the left
if (direction == 'down' && sliderRight >= sliderWrapperRight) {
innerSlider.style.marginLeft = --count * 5 + '%';
// if mousewheel direction is 'up', and the slider wrapper's position is not further to the left than the first slide's, move the slider to the right
} else if (direction == 'up' && sliderLeft <= sliderWrapperLeft) {
innerSlider.style.marginLeft = ++count * 5 + '%';
}
}
document.onmousewheel = function (e) {
handleMouseWheelDirection(detectMouseWheelDirection(e));
};
if (window.addEventListener) {
document.addEventListener('DOMMouseScroll', function (e) {
handleMouseWheelDirection(detectMouseWheelDirection(e));
});
}
});
I am trying to create a website for tablet with a navigation where the user swipes between pages. The site will be viewed only on iOS devices.
I have tried one approach where I move the page in relation to the touch location, then on touchend scrollLeft the container to the page position.
Here is the code
$(function() {
var xScreenStartPos, xScrollStartPos;
xScrollStartPos = 0;
xScreenStartPos = 0;
$(document).on('touchstart', '.page', function(e) {
xScreenStartPos = $(this).parent().scrollLeft();
xScrollStartPos = e.originalEvent.touches[0].pageX;
});
$(document).on('touchmove', '.page', function(e) {
var deltaX, xPos;
e.preventDefault();
xPos = e.originalEvent.touches[0].pageX;
deltaX = xScrollStartPos - xPos;
if ((deltaX > 50 || deltaX < -50) && scrollingVertical === false && menuOpen === false) {
$(this).parent().scrollLeft(xScreenStartPos + deltaX);
}
});
$('.page').on('touchend', function() {
var previousPage;
previousPage = void 0;
$('.page:in-viewport').each(function() {
if ($(this) !== previousPage && $(this).offset().left < 512 && $(this).offset().left > -511) {
$(this).parent().animate({
scrollLeft: $(this).parent().scrollLeft() + $(this).offset().left
}, 250);
}
});
});
});
However this approach is very jittery and feels sluggish. It doesn't feel like a smooth user experience. What I am trying to replicate is the smooth navigation similar to swiping between pages on the iPad home screen.
I know it will never be the same as the native experience, but am trying to get as close as possible. Can anyone please help point me in the direction of a better solution?
You should try swipejs. The swiping effect is really close to native, and it has a very good api.
Instead of the normal mouse scroll speed, I'd like to make it slower and smoother and as consistent as it can in modern browsers.
I have already tried using a few plugins like NiceScroll (https://nicescroll.areaaperta.com/).
But after installing it, for some reason it puts an overflow: hidden; on my content and can't scroll anywhere after. I don't need any custom-designed scrollbars. I just need the scrolling to be smoother when using the mouse scroll or vertical scroll bar like this:
http://pervolo.com/
Would anyone please enlighten me about this? I plan to use the skrollr plugin (https://github.com/Prinzhorn/skrollr) along with the smooth scrolling.
This may get you going:
$(window).on('mousewheel DOMMouseScroll', function(e) {
var dir,
amt = 100;
e.preventDefault();
if(e.type === 'mousewheel') {
dir = e.originalEvent.wheelDelta > 0 ? '-=' : '+=';
}
else {
dir = e.originalEvent.detail < 0 ? '-=' : '+=';
}
$('html, body').stop().animate({
scrollTop: dir + amt
},500, 'linear');
})
DOMMouseScroll and e.originalEvent.detail are required for Firefox. Change amt to be your desired scroll distance, and change 500 to be your desired scroll speed.
Fiddle: http://jsfiddle.net/utcsdyp1/1
I succeeded in making the scroll by mouse wheel look smooth as butter! Copy-paste the following snippet and try yourself.
let easeInOutQuint = t => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t; // Easing function found at https://gist.github.com/gre/1650294
// With this attempt I tried to make the scroll by mouse wheel look smooth
let delay = ms => new Promise(res => setTimeout(res, ms));
let dy = 0;
window.addEventListener("wheel", async e => {
// Prevent the default way to scroll the page
e.preventDefault();
dy += e.deltaY;
let _dy = dy; // Store the value of "dy"
await delay(150); // Wait for .15s
// If the value hasn't changed during the delay, then scroll to "start + dy"
if (_dy === dy) {
let start = window.scrollY || window.pageYOffset;
customScrollTo(start + dy, 600, easeInOutQuint);
dy = 0;
}
}, { passive: false });
function customScrollTo(to, duration, easingFunction) {
let start = window.scrollY || window.pageYOffset;
let time = Date.now();
let timeElapsed = 0;
let speed = (to - start) / duration;
(function move() {
if (timeElapsed > duration) {
return;
}
timeElapsed = Date.now() - time;
// Get the displacement of "y"
let dy = speed * timeElapsed;
let y = start + dy;
// Map "y" into a range from 0 to 1
let _y = (y - start) / (to - start);
// Fit "_y" into a curve given by "easingFunction"
_y = easingFunction(_y);
// Expand "_y" into the original range
y = start + (to - start) * _y;
window.scrollTo(0, y);
window.requestAnimationFrame(move);
})();
}
You can catch an action when user scrolls using $(element).on( "scroll", handler ), then, using, for example this code
$('html, body').animate({
scrollTop: $("#target-element").offset().top
}, 1000);
scroll to some element using jQuery
I'm currently working on a smooth horizontal mousewheel scroll and firefox is giving me quite a a bit of a headache.
basically, whenever one fires the mouse wheel event that would execute the scroll, firefox replies with very disparate values, as in a scroll which should fire negative events fires the odd positive value (i.e. 30, 40, 43, -20, 30, -4), especially on smaller movements. that results more or less in the opposite of the desired result, as you can imagine.
is there any way to avoid that? I've tried throttling it a little bit, but the loss of fluidity in the movement makes it a no go.
for reference, the code for the scroll:
var scroll = function(e, d){
console.log(d);
$('html, body').animate(
{scrollLeft: '-=' + d},
10
);
e.preventDefault();
e.returnValue = false;
}
$('html, body').bind('mousewheel', scroll);
I found that the best, most consistent way of calculating mousewheel distance is to use three events: mousewheel, MozMousePixelScroll and DOMMouseScroll.
The last two events are mozilla-specific and they are available in different FF versions. They are more precise than the mousewheel event, but you need to adjust the delta to normalize the speed. Here is some code I used in the past:
DEMO: http://jsfiddle.net/6b28X/
var distance = 0;
var scroll = function(e){
e.preventDefault();
var original = e.originalEvent,
delta = 0,
hasPixelEvent = false;
// FF 3.6+
if ( 'HORIZONTAL_AXIS' in original && original.axis == original.HORIZONTAL_AXIS ) {
return; // prevents horizontal scroll in FF
}
if ( e.type == 'MozMousePixelScroll' ) {
hasPixelEvent = true;
delta = original.detail / -7;
// other gecko
} else if ( e.type == 'DOMMouseScroll' ) {
if ( hasPixelEvent ) {
return;
} else {
delta = original.detail * -3;
}
// webkit + IE
} else {
delta = original.wheelDelta / 7;
}
distance = Math.max(distance-delta, 0);
window.scrollTo( distance, 0 );
}
$(window).bind('mousewheel MozMousePixelScroll DOMMouseScroll', scroll);
I have this horizontal carousel component that I want to make it work for both Mouse and Swipe events.
Everything is working fine, except for one bit: in touch devices, I don't want the carousel to scroll horizontally if the user is trying to swipe vertically to scroll through the whole page.
What I am doing is,
on mousedown/touchstart - I stop the event from propagating to avoid page scroll, item selects, etc...
on the first move event, on the carousel, I set a 50ms timeout to determine if the user is moving vertically or horizontally.
If deltaX < deltaY, I stop my horizontal scrolling, manually fire the touchstart event, with a flag indicating that i fired it manually
on my mousedown/touchstart handler, I read that "manually" flag and, if it's true, I return true from my function so all the default browser events, like the page vertical scrolling, continue to work.
This is not working, everything I'm doing responds correctly but the browser doesn't pick up and scroll the page. I hope I am explaining myself correctly enough so you guys can help me... I don't have an online example because this is a "secret" project for my company...
I was trying to do the same thing as you (were?). The key is to check on touchmove if the current touch and the last touch is more vertical than horizontal. If the touch was more left to right or right to left, prevent the event's default, otherwise ignore it. Here's what I ended up writing. Hopefully it works for you!
var gestures = function() {
var self = this,
coords = {
startX: null,
startY: null,
endX: null,
endY: null
};
self.$el.on('touchstart', function(e) {
coords.startX = e.originalEvent.targetTouches[0].clientX;
coords.startY = e.originalEvent.targetTouches[0].clientY;
coords.endX = coords.startX;
coords.endY = coords.startY;
});
self.$el.on('touchmove', function(e) {
var newX = e.originalEvent.targetTouches[0].clientX,
newY = e.originalEvent.targetTouches[0].clientY,
absX = Math.abs(coords.endX - newX),
absY = Math.abs(coords.endY - newY);
// If we've moved more Y than X, we're scrolling vertically
if (absX < absY) {
return;
}
// Prevents the page from scrolling left/right
e.preventDefault();
coords.endX = newX;
coords.endY = newY;
});
self.$el.on('touchend', function(e) {
var swipe = {},
deltaX = coords.startX - coords.endX,
deltaY = coords.startY - coords.endY,
absX = Math.abs(deltaX),
absY = Math.abs(deltaY);
swipe.distance = (absX > absY) ? absX : absY;
swipe.direction = (absX < absY) ?
(deltaY < 0 ? 'down' : 'up') :
(deltaX < 0 ? 'right' : 'left');
// console.log(swipe.direction + ' ' + swipe.distance + 'px');
// If we have a swipe of > 50px, let's use it!
if (swipe.distance > 50) {
if (swipe.direction === 'left') {
self.advance();
} else if (swipe.direction === 'right') {
self.retreat();
}
}
});
};
this is my slider object and $el is the container element.