My draggable div becomes draggable on touch devices, but it "flickers" to weird positions when starting to move it. Works like a charm om desktop devices but not on iPad or Android.
Any suggestions for a solution?
Thanks in advance!
Unfortunately, I can't share the code since it will go into a product, and I am not able to reproduce the issue in a jsfiddle or similar.
But I have made a fix for the draggable flickering on touch devices, which I will share here if someone else encounters the problem.
var prevPos = null, diffX, diffY, maxDiff;
$( '#draggable' ).draggable( {
...,
...,
drag: function ( event, ui ) {
if ( prevPos ) {
diffX = Math.abs( prevPos.left - ui.position.left );
diffY = Math.abs( prevPos.top - ui.position.top );
maxDiff = Math.max( diffX, diffY );
if ( maxDiff > 60 ) {
ui.position = prevPos;
}
}
prevPos = ui.position;
},
stop: function ( event, ui ) {
prevPos = null;
}
} );
Related
I'm trying to migrate some jQuery code to vanilla JS that modifies the mouse wheel behavior to scroll a site horizontally instead of vertically:
jQuery:
var wheel = function() {
var width = $( window ).width();
if ( width > 954 ) {
$( 'html' ).on( 'wheel', function( e ) {
e.preventDefault();
if ( Math.abs( e.originalEvent.deltaY ) >= Math.abs( e.originalEvent.deltaX ) ) {
this.scrollLeft += ( e.originalEvent.deltaY * 10 );
} else {
this.scrollLeft -= ( e.originalEvent.deltaX * 10 );
}
} );
} else {
$( 'html' ).off( 'wheel' );
}
}
wheel();
$( window ).on( 'resize', wheel );
As you can see ( if (width > 954) ), the new behavior is just set on desktops, not mobile nor tablet devices.
This is the vanilla JS code I came up with:
Vanilla JS:
var wheel = function() {
var width = window.innerWidth;
var scroll = function( e ) {
e.preventDefault();
if ( Math.abs( e.deltaY ) >= Math.abs( e.deltaX ) ) {
this.scrollLeft += ( e.deltaY * 10 );
} else {
this.scrollLeft -= ( e.deltaX * 10 );
}
}
if ( width > 954 ) {
document.documentElement.addEventListener( 'wheel', scroll );
} else {
document.documentElement.removeEventListener( 'wheel', scroll );
}
}
wheel();
window.addEventListener( 'resize', wheel );
But, when I resize the window to the tablet/mobile width, the horizontal scrolling is not disabled and I cannot scroll the site vertically. It seems as if the removeEventListener() function is not really removing my listener function.
Any ideas about what's going on here?
You are binding wheel handler many times as you resize the window. I would suggest to bind it once and then check the window width in it. Maybe something like this:
var scroll = function( e ) {
var width = window.innerWidth;
if(width <= 954) return;
e.preventDefault();
if ( Math.abs( e.deltaY ) >= Math.abs( e.deltaX ) ) {
this.scrollLeft += ( e.deltaY * 10 );
} else {
this.scrollLeft -= ( e.deltaX * 10 );
}
}
document.documentElement.addEventListener( 'wheel', scroll);
If anyone could help me figure out how to make the draggable elements contained in a div that changes scale based on window size, i'd really appreciate any guidance.
If I do:
element.draggable({
cursor: "move",
containment: '#container'
});
What will happen is it gives me the containment for the regular size of the container. So if I have a transform: scale(1.5), there will be space in the container that the draggable element can not go.
I've also tried containment: 'parent' but that get's very glitchy.
EDIT
I've found out how to get the top and left containment but I can't figure out how to get the right and bottom.
var containmentArea = $("#container");
containment: [containmentArea.offset().left, containmentArea.offset().top, ???, ???]
I've tried width and height from containmentArea[0].getBoundingClientRect() but that doesn't seem to be the right move either.
Here is a jsfiddle of some example code.
A version with resetting the coordinates in the drag event (since they were being recalculated already for the scale transformations), without using the containment:
var percent = 1, containmentArea = $("#container");
function dragFix(event, ui) {
var contWidth = containmentArea.width(), contHeight = containmentArea.height();
ui.position.left = Math.max(0, Math.min(ui.position.left / percent , contWidth - ui.helper.width()));
ui.position.top = Math.max(0, Math.min(ui.position.top / percent, contHeight- ui.helper.height()));
}
$(".draggable").draggable({
cursor: "move",
drag: dragFix,
});
//scaling here (where the percent variable is set too)
Fiddle
In the example width and height of the container are obtained inside the dragevent, you could also store them when scaling for better performance. By having them calculated inside the event, they still work after rescaling, although the percent variable still has to be set. To be truly generic, it could be obtained inside the event as well (and instead of a fixed container, ui.helper.parent() could be used)
Since the offset inside the dragevent is (0,0) related to the container (at least it is for the current setup), took the liberty of simplifying originalleft + (position - originalposition)/percent to position / percent
Start offset didn't seem to be necessary any more, so left it out in the fiddle, but can be re-added if needed.
Take a look to this :
http://jsfiddle.net/z0gqy9w2/3/
The edited code is the following one :
// Matrix regex to take the scale value property of $('#container') element
var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/,
matches = $('#container').css('transform').match(matrixRegex);
// Matches have this value : ["matrix(1.5, 0, 0, 1.5, 0, 0)", "1.5", "1.5"] , so we need matches[1] value :
var scaleValue = matches[1];
$(".draggable").draggable({
cursor: "move",
start: startFix,
drag: dragFix,
containment: [containmentArea.offset().left, containmentArea.offset().top,
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) ) ,
( ( containmentArea.offset().top + ( containmentArea.height() * scaleValue ) ) - ( $(".draggable").height() * scaleValue ) ) ]
});
As you see, here is the trick :
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) )
Your right max position will be : The main container left offset + the true width of the container ( with scale ) - the item true width (to let it inside the container).
(Tip: Be free to change the "percent" var value as you want too see the results)
regex ref
Here is my solution:
var _zoom = 1.2,
$element = $('.draggable-element'),
$container = $('#container');
var containmentW,
containmentH,
objW,
objH;
$element.draggable({
start: function(evt, ui) {
ui.position.left = 0;
ui.position.top = 0;
containmentW = $container.width() * _zoom;
containmentH = $container.height() * _zoom;
objW = $(this).outerWidth() * _zoom;
objH = $(this).outerHeight() * _zoom;
},
drag: function(evt, ui) {
var boundReached = false,
changeLeft = ui.position.left - ui.originalPosition.left,
newLeft = ui.originalPosition.left + changeLeft / _zoom,
changeTop = ui.position.top - ui.originalPosition.top,
newTop = ui.originalPosition.top + changeTop / _zoom;
// right bound check
if(ui.position.left > containmentW - objW) {
newLeft = (containmentW - objW) / _zoom;
boundReached = true;
}
// left bound check
if(newLeft < 0) {
newLeft = 0;
boundReached = true;
}
// bottom bound check
if(ui.position.top > containmentH - objH) {
newTop = (containmentH - objH) / _zoom;
boundReached = true;
}
// top bound check
if(newTop < 0) {
newTop = 0;
boundReached = true;
}
// fix position
ui.position.left = newLeft;
ui.position.top = newTop;
// inside bounds
if(!boundReached) {
// do stuff when element is dragged inside bounds
}
}
});
Link to fiddle
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.
I have a div which is resizable from the left (w) and the right (e) using jQuery UI. Resizing on the right works perfectly, but on the left two things behave different:
I can't resize the element to 0 width
It jumps when resizing starts
I made this fiddle
$("#test").resizable({
handles: 'e,w',
grid: 32,
maxWidth: null,
minWidth: 0
});
Why is that so?
Thanks
Edit
Grid 32 is needed, but you are right, it would work without.
Remove grid: 32. It causes the problem you are facing.
I found a solution.
I overwrite jQuery UI's $.ui.plugin.add("resizable", "grid") on resize start.
Not the best solution I think but it works.
Here you can see the changes I made in the last if-else:
Old:
if ( newWidth - gridX > 0 ) {
that.size.width = newWidth;
that.position.left = op.left - ox;
} else {
that.size.width = gridX;
that.position.left = op.left + os.width - gridX;
}
Customised:
if ( newWidth - gridX >= 0 ) {
that.size.width = newWidth;
that.position.left = op.left - ox;
} else {
that.size.width = o.minWidth;
that.position.left = op.left + os.width - gridX;
}
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);