Since the touch events are handled by Hammer, I am unable to scroll the container that I have muurified, on mobile safari on iOS.
I believe it has to do with touch-actions = none.
One solution I can think of for this problem is to require that the user taps and holds a post before the dragging starts. That way swiping a post will continue to scroll the container per normal, and a tap+hold would activate dragging.
Actually I though about this issue a lot when creating Muuri and originally developed a solution exactly as you described. For desktop browsers the dragging started as it starts now, but on touch devices you needed to hold the item a while before dragging would start. However, the result turned out to be a bit of an unreliable hack and then I also started thinking that what if the user wants to use some other logic for starting the drag.
So I created the dragPredicate option to solve the problem. It's not documented well (there's only a mention of it in the docs) but with it you can define when the dragging should start.
For starters you can check out the default predicate here: https://github.com/haltu/muuri/blob/master/muuri.js#L3004
Here's a bit more in-depth explanation:
var grid = new Muuri({
container: document.getElementsByClassName('grid')[0],
items: [].slice.call(document.getElementsByClassName('item')),
dragEnabled: true,
dragPredicate: function (hammerEvent, item, resolve) {
// Whenever the user starts to drag an item this function is called
// on each move until the the resolve callback is executed (with the
// hammerEvent as it's argument). When the resolve callback is
// executed Muuri's own dragging process starts and the item starts
// to actually move.
// Let's do nothing if the predicate is already resolved.
if (this.isResolved) {
return;
}
// For mouse interactions let's just start dragging immediately.
if (hammerEvent.pointerType === 'mouse') {
this.isResolved = true;
resolve(hammerEvent);
}
// For other type of interactions (touch) let's wait 500ms before dragging starts.
else {
if (hammerEvent.deltaTime >= 500) {
this.isResolved = true;
resolve(hammerEvent);
}
}
}
});
Alrighty, so with the dragPredicate you can create any kind of custom logic for starting the drag, and also target mouse and touch events. Cool. But that still leaves us with the problem of "touch-action: none". That style should be only set to the items so you should be able to scroll the page when swiping the grid container but not the items. If you want to allow swiping over the items also you have to manually remove the "touch-action:none" style from the items after initiating the muuri instance or changing it something else: https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action. I reckon "pan-y" would be good for vertically scrolling page and "pan-x" for horizontally scrolling page.
So yep, it's a bit of a hassle to make it work nicely with scrolling, but should be doable. I'm open to any suggestions to make this process easier for the users of Muuri ;) Btw. the drag predicate system has been completely revamped in v0.3.0 and has some nice improvements.
Just for kicks, here's the v0.3.0 dragStartPredicate documentation: https://github.com/haltu/muuri/tree/dev#dragstartpredicate-
@niklasramo Thanks. That really helped. One last trouble I am having is that the page isn't scrolling as I am dragging an item below the viewport. The same problem happens on desktop sometimes.
Yep, that's a known issue or more specifically a feature that is not yet implemented. There's already an open issue related to that: https://github.com/haltu/muuri/issues/9
If you need the the scrolling while dragging feature asap you could try to implement it yourself outside muuri, as a muuri plugin. Should not be massively hard.
More than happy to contribute. I wonder if the right approach, though, would be to let muuri handle all the positioning logic (including how items move as other items are dragged around) and outsource the dragging and dropping functionality to something like jquery draggable.
Awesome!! :+1:
The right approach would be not including jQuery into this =) In all it's simplicity I'd just hook on to the muuri's drag events and (based on the position of the dragged item) scroll the viewport (or whatever element you wish) whenever necessary.
Here's a good plugin boilerplate to get you started:
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['muuri'], function (Muuri) {
factory(global, Muuri);
});
}
else if (typeof module === 'object' && module.exports) {
factory(global, require('muuri'));
}
else {
factory(global, global.Muuri);
}
}(this, function (global, Muuri) {
'use strict';
// Add the plugin code/logic here...
Muuri.prototype.scrollOnDrag = function (options) {
this
.on('dragstart', function (item, data) {
})
.on('dragmove', function (item, data) {
})
.on('dragscroll', function (item, data) {
})
.on('dragend', function (item, data) {
});
return this;
};
return Muuri;
}));
Then you would init this plugin, just by including the plugin file after muuri.js and calling muuri.scrollOnDrag() to any muuri instance you want to bind the plugin functionality to.
The above boilerplate is for v0.2.0, but it should be easily portable to v0.3.0 after it's released. The only big difference is that in v0.3.0 you just get the item and hammer event as the arguments for the drag events. In the current v0.2.0 version you receive some additional data.
Thanks very much.
The reason I suggested that approach is because dragging well requires a lot of little things that a good dragging library would have taken care of. So muuri can do the hard parts of binpacking and rearranging.
Guys I drew a lot from what @niklasramo had wrote, and I came up with the following. The key winner for me was actually setting the touchAction as advised but via the dragHammerSetttings.
This has allowed dragging via touch and hold and scrolling on mobile.
let grid = new Muuri('.grid', {
dragEnabled: true,
dragHammerSettings: {
touchAction: 'pan-y'
},
dragStartPredicate: (item, hammerEvent) => {
// Whenever the user starts to drag an item this function is called
// on each move until the the resolve callback is executed (with the
// hammerEvent as it's argument). When the resolve callback is
// executed Muuri's own dragging process starts and the item starts
// to actually move.
//console.log(hammerEvent.pointerType);
// For mouse interactions let's just start dragging immediately.
if (hammerEvent.pointerType === 'mouse') {
this.isResolved = true;
return true;
}
// For other type of interactions (touch) let's wait 500ms before dragging starts.
else {
if (hammerEvent.deltaTime >= 500) {
this.isResolved = true;
return true;
}
}
}
});
Not working! Please pay attention
@niklasramo What do you suggest for this issue in muuri 0.8 since hammerjs is not used anymore?
@niklasramo @goneale - The approach you suggested seems to work fine on iOS (pan-y is supported in versions > 13.1) but on android we are having to initiate the drag in the horizontal direction before we can move it vertically. I am unable to drag it vertically directly after the long press. Do you have any workaround for this issue?
Most helpful comment
Guys I drew a lot from what @niklasramo had wrote, and I came up with the following. The key winner for me was actually setting the
touchActionas advised but via the dragHammerSetttings.This has allowed dragging via touch and hold and scrolling on mobile.