I'm trying to develop a menu where I can hover over the icons using my hand and then click on them using a pushing forward movement.
To achieve that, I'm using the velocity my hand on the z-axis, plus the touch zone and the touch distance as you can see in this snippet of code.
var controller = new Leap.Controller({ enableGestures: flag });
controller.on('frame', function(frame) {
if (frame.pointables.length > 0) {
var pointable = frame.pointables[0];
// Params used to navigation and touching on menu interfaces
var touchZone = pointable.touchZone, // None, hovering, or touching
touchDistance = pointable.touchDistance, // [+1, 0, -1]
zNotFinger= pointable.tipVelocity[0], // For the case pointable isnn't a hand
zIndex = pointable.tipVelocity[1], // Index finger velocity on z-axis
zMiddle = pointable.tipVelocity[2], // Middle finger velocity on z-axis
x = pointable.tipPosition[0],
y = pointable.tipPosition[1],
// Getting highest tipVelocity
tempVelocity = zIndex >= zNotFinger ? zIndex : zNotFinger,
velocity = zMiddle > tempVelocity ? zMiddle : tempVelocity;
// The circle is defined as a gesture to go back to homepage
if (frame.hands.length === 1 && origin !== 'home' && frame.gestures.length > 0) {
var gesture = frame.gestures[0],
hand = frame.hands[0],
oneExtended = hand.fingers[1].extended && !hand.fingers[3].extended;
if (gesture.type === 'circle' && oneExtended && gesture.pointableIds.length >= 1) {
window.open('../html/home.html','_self');
}
}
// Sending data...
if (origin === 'home') {
homeHover(x, y, touchZone, touchDistance, velocity);
} else if (origin === 'bio') {
bioHover(x, y, touchZone, touchDistance, velocity);
} else if (origin === 'nature') {
natureHover(x, y, touchZone, touchDistance, velocity);
}
}
});
controller.connect();
}
and then...
if (velocity > 150) {
if ($(".hovered").attr("href") && touchZone === 'touching' && touchDistance <= -0.4) {
window.location.replace($(".hovered").attr("href"));
}
}
The main problem is to accidentally "click" on the links while hovering over the icons or set up the requires too high making difficult to click.
Could anyone give me a hand on that? Maybe new methods that I should use or even a completely different approach.
OBS: I already tried the screenTap and keyTap.
Many thanks!
The too hard or too easy to click is a common problem. The built-in taps have the same problem.
You could explore the stabilizedTipPosition instead of velocity (or in addition to velocity) and make the user move forward a predetermined amount after hovering. Using stabilizedTip position should make it easier for the user to push forward without accidentally moving off the target. Only clicking when the motion is primarily along the z axis should greatly reduce accidental clicks, both those that occur when the user is moving to a target menu item and those that occur when the user is just moving their hands (unrelated to the menu).
Other common approaches to menus include:
Hover to activate -- the cursor shows a countdown indicator and the user just has to hold the cursor over the menu item or button for the requisite amount of time. It works, but is a bit inelegant and makes the user wait a bit for each menu option when they've already made up their mind what to do. This is one of the most common methods and can be seen in many apps in the Leap Motion app store.
Pull-out to activate -- the user hovers over the menu item and moves their finger to "drag" the item to the side to activate. You can see an example of this type of menu in the Sculpting app.
Physical -- the menus and buttons are activated by "touch" (collision) in 3D space. This technique works well in VR-style apps because the user has a better sense of depth perception. It can also work for non-VR 3D apps (with careful design). You could do a hybrid 2D-3D web app where the content is essentially 2D, but the hands are 3D and shown above the content.
There are some menu design guidelines (older) here: https://developer.leapmotion.com/documentation/javascript/practices/Leap_Menu_Design_Guidelines.html
And several examples (not all menus, though): https://developer.leapmotion.com/gallery/category/javascript
Related
Example http://jsfiddle.net/5MsUd/
I am having issues with this code for the scroll up and down effect. The key issue is this:
//create the event listener of the users choice.
window.addEventListener(this.trigger,function(){
//get the current position of scroll and add it to the object
self.scrollPos= this.scrollY || this.pageYoffset;
//get the current position of the element
newPos = getPosition(self.target,self.dir);
//HERE IS THE BIG ISSUE!!!!!!!
//adjust the current position depending on the scroll direction
if(scrollex.direction == "down"){
self.target.style[self.dir]= newPos + (self.scrollPos * self.offset)+"px";
}else{
self.target.style[self.dir]= newPos - (this.scrollY * self.offset)+"px";
}
//run callback if there is one
if(callback !== null && typeof callback=='function'){
callback.call(self);
}
}, false);
I'm not getting the correct PXs to go back to the correct position. I added the fiddle as that has all the major code with notes. As well as extra functions that I've added to help assist this scrollex function
Can anyone suggestion a better mathematical equation to get the correct pxs back and forth. Try it out and play with it, let me know of any good suggestions. I've gone pretty deep with this and have more to add but as of now the scroll down and up part system is majorly buggy. You can see once you start scrolling back and forth it does move back and forth but moving up gives less pixels than what down did.
Windows 8 has this neat feature where you scroll through your apps by "pushing" the side of the screen.
I want to know if anyone has any ideas to accomplish this in JavaScript.
Essentially, the screen does should NOT scroll if you hover over the side of the screen, but should rather be able to detect when the user is attempting to go beyond the viewport and cannot.
Is such a thing possible?
Sure, you just need to figure out their algorithm if you want to duplicate it.
You can track the last several known locations of the pointer to determine velocity and direction and stop the scrolling as soon as the direction changes, for example.
I'm using something along the lines of:
$(window).mousemove(function (e) {
if (getIsPageEdge()) {
if (lastX == e.pageX) {
console.debug('pushing the page');
}
var now = new Date().getTime();
if (lastUpdate == null || now - lastUpdate > 500) {
lastUpdate = now;
lastX = e.pageX;
}
}
});
Essentially, onmousemove, if the cursor is at the edge of the viewport, and the X value is not changing (with a time delay added to compensate for the event processing delay), then change the scroll position of the containing div.
I am currently having an issue in allowing vertical scrolling when event.preventdefault is enabled.
I am trying to add swipe functionality to my mobile page, I have tried may frameworks like hammer.js, swipe.js etc, and all of them require event.preventDefault enabled to detect left and right swipes.
When event.preventDefault is enabled, the swipes detect perfectly, however you lose the ability to vertical scroll when you are on that element. i.e you cannot move the screen up or down on a mobile device, when your finger starts on the swipe element.
I have tried building my own little script which works well, but again has the issue of vertical scrolling, that is an issue.
var el = document.getElementById('navigation');
el.ontouchstart = function(e){
e.preventDefault();
el.innerHTML = "touch start";
};
el.ontouchend = function(e){
el.innerHTML = "touch end";
};
el.ontouchmove = function(e){
el.innerHTML = "touch moved";
};
el.ontouchcancel = function(e){
el.innerHTML = "touch cancel";
};
Any ideas???
It's a common issue where you want the native browser behaviour to work alongside the interaction behaviour that people come to expect from a touchscreen device.
If you want to use a library you might need to hack it open as you WILL need to prevent the defaults to prevent the page from jumping all over the place when using touch events, but at other times you want to omit it as you want to prevent the page from remaining in a static position, obscuring other content.
Ideally you want add a clause to the handler that instructs them whether or not to prevent the default behaviour the browser executes upon receiving the event.
Swiping for instance, is a behaviour that should occur in a short time frame (if you are taking for instance one whole second in moving your finger from one area to the other instead of, let's say, 120 ms, you're not actually swiping, but dragging. Thinking about time frames may help you here, for instance:
var threshold = 150, interactionStart;
el.ontouchstart = function( e )
{
// store timestamp of interaction start
interactionStart = +new Date;
};
el.touchmove = function( e )
{
// get elapsed time in ms since start
var delta = +new Date - interactionStart;
if ( delta > threshold )
{
e.preventDefault();
}
else {
// super cool magic here
}
};
Whether 150 ms is the threshold you want depends on the action, as you see there is no fixed answer for your question as the actual implementation depends on what your application needs in terms of touch interactions.
You could also consider not blocking the events default when the user is scrolling more along the vertical axis (i.e. compare whether the delta position of the events Y offset (relative to the start Y offset) is larger than the events X offset (relative to the start X offset) to detect whether the users is moving left or right or up or down (for instance if you have a carousel that can swipe horizontally (where the default behaviour is blocked so the page won't move up/down during the horizontal scroll) but want the page to scroll vertically when the user is obviously dragging the page among the vertical axis).
The library jquery.touchSwipe solves that.
The library: https://github.com/mattbryson/TouchSwipe-Jquery-Plugin
An example page where swiping and scrolling are combined: http://labs.rampinteractive.co.uk/touchSwipe/demos/Page_scrolling.html
Ok - here is what I am trying to do. I was looking online for a cool timeline that I can purchase - allowing zoom in zoom out, posting of events on it, and so on. However, all the examples I found are either too expensive or just downright useless.
So, I have decided to create my own, but there are two elements that I am having trouble with.
1) Converting the wheel scroll to left-right scrolling (so not up-down). I can't seem to find an easy and quick way to do this.
But, more importantly..
2) I need the area I will be showing the timeline on to automatically expand as I go about my scrolling. So, if I scroll down, it will add an "equivalent" area on the right, and down, on the left. So I was thinking like making an iFrame (already use these) and when you scroll it just adds more "timeline" on the left or the right, loads what ever it needs to load from the DB/list of events, and so on, ad infinitum, thus creating an ever-expanding list of blocks that are time-sized.
If I can do the two things above, then I am set - the rest (loading/positioning) I can figure out - just these two things are eluding my imagination and ability to find an answer.
Basically you need a horizontal infinite scroll script.
Take this plugin I wrote:
$.fn.hScroll = function( options )
{
function scroll( obj, e )
{
var evt = e.originalEvent;
var direction = evt.detail ? evt.detail * (-120) : evt.wheelDelta;
if( direction > 0)
{
direction = $(obj).scrollLeft() - 120;
}
else
{
direction = $(obj).scrollLeft() + 120;
}
$(obj).scrollLeft( direction );
e.preventDefault();
}
$(this).width( $(this).find('div').width() );
$(this).bind('DOMMouseScroll mousewheel', function( e )
{
scroll( this, e );
});
}
Initialize it with:
$('body').hScroll();
Makes your website a horizontally scrollable website.
Your content div must be wider than your body (ex. 3000px).
As for the infinite scrolling effect you pretty much gotta do that your self because I can't know what kind of data you'll input. But I'll explain.
Your children elements in the content div must be floated to left. (every new appended div will not go to new line).
Set an interval to check if the user's scrollLeft position is near the end of the content (just like pinterest and similar site).
function loadNewData(){ /* Your search for data and update here. */ }
setInterval('loadNewData', 500);
search for new data according to your last one with AJAX. When you get new data, append it into your content div (in a div that's floated left, as I wrote previously), and mark it as your last item.
Maybe you could use your ID to mark the last item on it's div.
<div data-id="467" class="item"> // your data here </div>
You can fetch it with
$('.item:last').attr('data-id');
with jQuery.
It seems that when using two jquery UI droppables that touch each other, the droppable events are not fired correctly. If, while dragging a draggable from over one of the elements to just below it onto the next element, then the out event is fired for the first droppable, but the over event is not fired for the second. If you drop at this point, no drop event is fired.
An example is best. Try this fiddle (tested in IE7,8,9 and Chrome11). Make sure your browser's console log is open. If you drag the draggable over the first row, then slowly drag towards the second row, you'll soon see in the log that the out event for the first row is fired, but the over event for the second row is not. If you drop when this happens, the drop event is not fired.
It seems to just be a 1 pixel line in between the rows that causes the problem. Dragging one more pixel causes the over event to be fired, and the drop event to work correctly.
This looks like a bug to me, but I can't find anyone else who has used table rows as droppables and has reported the problem. I styled the table so you can see that the rows are indeed flush together with no space in between.
This is a big problem for me because in our app, the table rows are nested greedy droppables. So if the user drops when this happens, the drop is actually picked up by the outer droppable instead.
Also, we give feedback to the user in the draggable helper in the form of an icon and message that changes depending on the droppable you are over. When you drag between rows, it flickers for a moment, as it thinks you are not over any droppable when you actually are.
My questions:
Is there any fix or workaround for this issue?
Should I report this as a bug?
Update
#davin,
We did end up changing the drag function in $.ui.ddmanager to fix the event ordering. Our issue was we have nested greedy droppables. When you moved from one of these nested droppables to the other from bottom to top, the over event would actually fire for the parent last, causing bad things to happen.
So we added logic to basically check if moving from one nested greedy to another, and if so, not fire parent events.
Would it be too much to ask to have you look this over real quick and make sure our logic makes sense? There are two logical changes. If we moved from greedy child to greedy child:
Don't unset parentInstance.greedyChild
Don't fire parentInstance._over event.
Here's the code. See the lines dealing with the isParentStateChanged closure var, which we added:
drag: function(draggable, event) {
//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event);
var isParentStateChanged = false;
//Run through all droppables and check their positions based on specific tolerance options
$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
if(this.options.disabled || this.greedyChild || !this.visible) return;
var intersects = $.ui.intersect(draggable, this, this.options.tolerance);
var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null);
if(!c) return;
var parentInstance;
if (this.options.greedy && !isParentStateChanged) {
var parent = this.element.parents(':data(droppable):eq(0)');
if (parent.length) {
parentInstance = $.data(parent[0], 'droppable');
parentInstance.greedyChild = (c == 'isover' ? 1 : 0);
}
}
// we just moved into a greedy child
if (parentInstance && c == 'isover') {
isParentStateChanged = true;
parentInstance['isover'] = 0;
parentInstance['isout'] = 1;
parentInstance._out.call(parentInstance, event);
}
this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0;
this[c == "isover" ? "_over" : "_out"].call(this, event);
// we just moved out of a greedy child
if (parentInstance && c == 'isout') {
if (!isParentStateChanged) {
parentInstance['isout'] = 0;
parentInstance['isover'] = 1;
parentInstance._over.call(parentInstance, event);
}
}
});
}
It's not a bug per se, it's a feature. It's all a matter of definitions. You've defined the tolerance of your droppable items to be pointer, which according to the docs is:
pointer: mouse pointer overlaps the droppable
When my mouse pointer is at (10,10) and the top left corner of my box ends at (10,10), is that overlapping? It depends on your definition. jQueryUI's definition is strong inequality, or strong overlap (see the relevant code). That makes sense (to me), since I'm not inside the box if I'm only on the edge, so I wouldn't want an event to fire.
Although if for your purposes you require weak inequality in the overlap condition (i.e. weak overlap), you can modify that line of code in your source, or override it, by adding:
$.ui.isOverAxis = function( x, reference, size ) {
return ( x >= reference ) && ( x <= ( reference + size ) );
};
Working example: http://jsfiddle.net/vwLhD/8/
Be aware that with weak inequality comes other bumps in the road: your out event will fire after your over event, so you might have two over events before a single out has fired. That's not so hard to handle, but you need to make sure you deal with that case.
UPDATE:
It's important to note that if you add the code I pasted above it is going to affect all other ui widgets in the scope of $ if that's important. Maybe subbing $ could avoid that.
In any case, I have a second workaround that will solve the above issue entirely, and now on every mouse movement the pointer is either in or out of every element exclusively:
$.ui.isOverAxis2 = function( x, reference, size ) {
return ( x >= reference ) && ( x < ( reference + size ) );
};
$.ui.isOver = function( y, x, top, left, height, width ) {
return $.ui.isOverAxis2( y, top, height ) && $.ui.isOverAxis( x, left, width );
};
Working example: http://jsfiddle.net/vwLhD/10/
Essentially I've made the upper condition a weak inequality and the lower one a strong one. So the borders are entirely adjacent. Now the events fire almost perfectly. Almost and not entirely because the plugin still loops through the droppables in order, so if I'm dragging from top to bottom the firing order is good because first it detects that I have left the higher element, and then detects that I have entered the lower element, whereas dragging from bottom to top the order of firing is reversed - first it fires entering the higher and only then leaving the lower.
The difference between this and the previous workaround is that even though half the time the order is not good, it all happens in one tick, i.e. over-out or out-over are always together, the user can never get stuck as in the original case and first workaround.
You can further hone this to be absolutely perfect by changing the ui code to loop through the items first according to those that have the mouse over them, and only then the rest (in the $.ui.ddmanager function). That way the mouse leave will always fire first. Alternatively you can swap the order and have the reverse order; whatever suits you better.
That certainly would solve your problem entirely.
Sounds like you might be dropping between rows which would mean you were dropping onto the table. Do you have your table borders collapsed? css border-collapse: collapsed;
I had this problem with a project i was working on.
My solution was to see check how far the draggable was over each droppable. If the draggable is 50% over the top droppable then i assume the user wants to drop on the top droppable.
Similar for the bottom.
To do this i changed $.ui.intersect;
added vars -
hw = droppable.proportions.width / 2, hh = droppable.proportions.height / 2,
lhw = l + hw,
thh = t + hh
then add some if statements
// going down
if(y2 < b && y2 >= thh){}
// going up
if(y1 > t && y1 <= thh){}
// covered
if(y1 <= t && y2 >= b){}