Jquery touch enable slider - javascript

I have setup a slider, here is jsfiddler http://jsfiddle.net/zZv5B/. How can I enable it for touch devises, I want to be able to swipe through panel area to slide next and prev slide. any idea would be really appreciated.
var currentIndex = 0;// store current pane index displayed
var ePanes = $('#slider .panel');// store panes collection
function showPane(index){// generic showPane
// hide current pane
ePanes.eq(currentIndex).stop(true, true).fadeOut();
// set current index : check in panes collection length
currentIndex = index;
if(currentIndex < 0) currentIndex = ePanes.length-1;
else if(currentIndex >= ePanes.length) currentIndex = 0;
// display pane
ePanes.eq(currentIndex).stop(true, true).fadeIn();
// menu selection
$('.nav li').removeClass('current').eq(currentIndex).addClass('current');
}
// bind ul links
$('.nav li').click(function(ev){ showPane($(this).index());});
// bind previous & next links
$('.previous').click(function(){ showPane(currentIndex-1);});
$('.next').click(function(){ showPane(currentIndex+1);});
// apply start pane
showPane(0);

Use the events (more here) 'touchstart' and 'touchend' and get the start X position and the end x position. You can then compare the two and determine which direction the touch/swipe has happend.
var xStart, xEnd;
$('.wrap').on('mousedown touchstart', function (e) {
//get start x position
xStart = e.pageX;
}).on('mouseup touchend', function (e) {
//get the end x position
xEnd = e.originalEvent.pageX;
if (xStart != xEnd) {
//swiped
if (xStart < xEnd) {
console.log('Right');
showPane(currentIndex + 1);
}
if (xStart > xEnd) {
console.log('Left');
showPane(currentIndex - 1);
}
}
});
example fiddle - not sure how browser compatibile this is.
Or you could just use my fave touch enabled slider swipejs
UPDATE:
To make it work correctly for mobile changed xEnd = e.originalEvent.pageX as per #User543294's comment
Also a new fiddle example to using e.changedTouches[0].pageX as per MDN documetation

I just came across TouchSwipe. It seems pretty nice, straightforward, and robust.
Maybe something like this?
var swipe_obj = {
swipeLeft: function(event, direction, distance, duration, fingerCount) {
$(".next").click(); },
swipeRight: function(event, direction, distance, duration, fingerCount) {
$(".previous").click(); },
};
$(".nav li").swipe(swipe_obj);

Many touch devices also trigger a click, after a touch event. They usually follow a secuence similar to this: a touch happens, then it will be fired the events "touchstart" -> "touchend" -> "click". Where that last click it's a fake one, and has a huge delay respect native events. But it also works.
So, if efficiencie don't care, don't worry. Your slider will work as you expected it will do.
But if you are interested in to achieve a good performance, I recommend you to handle native events directly. Which can be done using Modernizr dinamic testing.
It's a simple idea:
1 Download and link the Modernizr library into your code.
2 Test at the first line of your code which kind of events you can expect, based on device capabilities. A single line of javascript will be enough:
var actualEvent = (Modernizr.touch) ? 'touchstart' : 'click';
Extra points if you do this before onReady occurs.
3 Move your current handlers into the "on" form:
$('selector').on(actualEvent, callback)
Then you will be always handling the real event, not the simulated click fired by tablets and phones. Because the test at the beginning ensures you that "actualEvent" will be "touchstart" just on touchable devices and "click" for the rest.
Of course, you can substitute "touchstar" by the touch event you want. But I think that "touchstar" is the one who best suits your needs.

Related

Large Isotope Gallery is Very Slow

I have an Isotope gallery (version 2) with almost 400 elements. A typical gallery item looks like this:
<div class="element hybrid a" data-category="hybrid">
<p class="type">H</p>
<h2 class="name">Name</h2>
<p class="strain-info">No Info Available</p>
<p class="review"><a class="button radius small black review-form-lb" href="#review-form-lightbox">Review</a></p>
</div>
For instance when I run the code below, which basically adds a class to the element clicked, it takes several seconds for the element to enlarge.
$container.on( 'click', '.element', function() {
$( this ).toggleClass('large');
$container.isotope('layout');
});
Another example is if I have a button group that contains several options to filter the gallery, again it takes several seconds. Filter JS:
$('#filters').on( 'click', '.button', function() {
var $this = $(this);
// get group key
var $buttonGroup = $this.parents('.button-group');
var filterGroup = $buttonGroup.attr('data-filter-group');
// set filter for group
filters[ filterGroup ] = $this.attr('data-filter');
// combine filters
var filterValue = '';
for ( var prop in filters ) {
filterValue += filters[ prop ];
}
// set filter for Isotope
$container.isotope({ filter: filterValue });
});
Here is the variable for $container
// init Isotope
var $container = $('.isotope').isotope({
itemSelector: '.element',
layoutMode: 'fitRows',
filter: function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
});
I should note that the above code works great when there is a small number of items. How could I improve the performance of the gallery?
I should note that in version 1 of Isotopes I have the same gallery and it works fine. I am upgrading because of enhanced abilities of v2 of isotopes.
Here is the live site - IMPORTANT! - This site is for a marijuana dispensary in Colorado, USA. Website may not be appropriate for work.
Update
I tried changing all divs to li elements. Didn't see much improvement.
I tried turning on hardware acceleration per mfirdaus's answer but it didn't appear to work.
Note: The above link to the live site is a stripped down version where I removed everything not required for the Isotope gallery. It is slightly faster than when I originally posted this, however, it needs to be more responsive. Try using it on a tablet and it takes several seconds for the Isotope item to respond.
Update 2
Version One of Isotopes performs much better with a large gallery and I ended up using version one because of this.
I have a suggestion. First of all, you can add the css property transform:translate3d(0,0,0) to your .element css selector. This turns on hardware acceleration and should speed up the page reflow. This is a tip often used to make highly animated pages smoother, especially on mobile. so something ilke:
.element {
...
/* add this. prefixes for compabtibility */
transform:translate3d(0,0,0);
-webkit-transform:translate3d(0,0,0);
-moz-transform:translate3d(0,0,0);
}
In my tests it seems to speed it up quite nicely.
Another suggestion would be to replace .on("click") with .on("mousedown"). click fires after the mouse is released whereas mousedown would fire immediately after the user press their mouse. there is like a ~0.2s difference but it's somewhat noticable.
Here's an example made from isotope's default example which has 400 elements.
Update
After testing out touchstart, it kinda helps but it fires even when scrolling. So this helps only if you disable scroll/have fixed viewport. Maybe you could add iScroll, but I reckon that's even more bloat.
One option you could try is to force an element to resize first, before waiting for isotope to recalculate the positions. We can do this by using setTimeout on the isotope refresh. Not sure if the behaviour is acceptable but this will allow the user to get feedback faster. So your toggling code will be something like:
$container.on( 'click', '.element',function() {
$( this ).toggleClass('large');
setTimeout(function(){ $container.isotope('layout'); },20); //queue this later
});
Demo. Tested this on my android device and there seemed to be an improvement.
Well, the author of the isotope plugin recommends 50 items max if you're doing filtering or sorting. You have 400!.. So I'm afraid you'll need to decrease the num. of items for better performance.
Some side notes:
Try touchstart or mousedown events instead of click for a bit more quick responses on mobile.
Check for Object.keys(filters).forEach performance on mobile (instead of for in)..
jQuery's closest() method might be faster than parents() in some browsers.
Instead of using toggleClass, try directly manipulating the style.
$( this ).css({ /* styles for large */});
While I understand that this may not be an issue directly related to your scenario, I had the same experience (mostly on mobile and tablet devices) where I had only 36 images in an Isotope gallery, and I was waiting anywhere from 3-5 seconds for the reflow to occur.
In my case it was due to the fact that I had multiple (3) event listeners on the 'resize' event. Bad move! The resize event is fired several hundred times as it keeps firing until the user stops resizing the screen.
It also turns out that scrolling on iOS devices fires the resize event as well, so I had a resource intensive function (cloning a complex mega menu and rebuilding it for mobile devices) queuing hundreds of times, thus causing all js related tasks to lockup or become intensely slow.
The solution in my case was to implement a debounce function to debounce the functions attached to the 'resize' event, thus firing them once and only when the resize is complete.
For what it's worth, I adapted the Underscore.js debounce function so that I could use it locally and outside of Underscore.js, which was borrowed from this blog post by David Walsh. Here is an example of how I implemented it:
var debounce = function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var checkWindowWidthForNav = debounce(function(e) {
sdWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
if(sdWidth <= 990){
window.simulateMobileNav = true;
sdMobileSnapMenus();
}else{
window.mobileScaffoldingDone = false;
window.simulateMobileNav = false;
sdMobileSnapMenus();
$("[data-action=\"hover-toggle\"]").bind("mouseenter", function(e){
var $target = $(e.currentTarget).attr("data-target");
var $hide = $(e.currentTarget).attr("data-hide");
$($hide).hide();
$($target).show();
});
$(".subMenuWrapper:visible").hide();
$(".subMenuWrapper").bind("mouseleave", function(e){
$(".subMenuWrapper").fadeOut(500, function(){
$(".sdSubMenu .sdHideFilters, #sdArchitecturalFilters, #sdInteriorFilters, #sdUrbanFilters").hide();
});
return false;
e.preventDefault();
e.returnValue = false;
});
$(".sdMainMenu > ul li a[data-action], .sdSubMenu a[data-action]").bind("click", function(e){
e.preventDefault();
e.returnValue = false;
return false;
});
$(".sdMainMenu > ul .sdSubMenu").hide();
}
}, 600); // Debounced function occurs 600ms after event ends
window.addEventListener("resize", checkWindowWidthForNav, false);
Debouncing my events resulted in a DRAMATIC increase in speed for all JS related events. I no longer had the Isotope lag, my reflow was instant.
This may help someone who was in the same boat I was in.

jQuery - auto input focus when scroll hit certain element

I am using jQuery plugin called Waypoints
to work with scroll action.
What I want to achieve is setting focus on the first input element of a section that is in the viewport and move the focus to the input of next respective sections when scrolled down. And, when scroll back to the first section up above, it should set the focus back to the input of the first section.
The following is what I have on my actual working setup that is using the aforementioned plugin.
Unfortunately, I can't really get the plugin up and running in my JS Fiddle.
This code block works in terms of setting focus on page load and changing focus to the targeted input when scrolled down
but scrolling back to the top section does not set the focus back.
(function($) {
var firstInput = $('section').find('input[type=text]').filter(':visible:first');
if (firstInput != null) {
firstInput.focus();
}
$('section').waypoint(function () {
var getFocus = $(this).find('input[type=text]').filter(':visible:first');
getFocus.focus();
});
$('section').waypoint(function () {
var getFocus = $(this).find('input[type=text]').filter(':visible:first');
getFocus.focus();
}, {
offset: function () {
return -$(this).height();
}
});
});
Here is my JS Fiddle
that doesn't have the plugin part.
As long as somebody can explain how they should be done in normal jQuery
if not familiar with this plugin.
jQuery Waypoints is very straightforward to use. I haven't tried with your code but I got it done with this:
$('input:first').focus();
$('section').waypoint(function() {
$(this).find('input:first').focus();
});
Please see this fiddle.
However, using mousewheel on scrolling down, the scroll sometimes jumps back at the mid part. It might be how the browser reacts on input focus. I haven't gone through the whole documentation but there is no example for input focus.
If you're only looking to do just this simple task I suggest you drop the plugin. I can show you how to get this done with jQuery.
You can handle the scrolling with jQuery's scroll() function. You need to get the scrollTop() on scrolling event then compare it with the <input>'s top offset().
$(window).scroll(function(){
var st = $(this).scrollTop();
$('input[type=text]').each(function(){
var offset = $(this).offset();
if(st >= offset.top -20 && st < offset.top + $(this).height()){
$(this).focus();
}
});
});
Here is the fiddle.

Scroll event doesn't fire unless page moves

I'm looking to get an event to fire when one scrolls "up" from $(window).scrollTop == 0.
If you have the following code:
$(window).scroll(function(){
console.log("scrolling")
});
On a page where the document < window height then that event never fires because $(window).scrollTop isn't changing, but this doesn't mean that there's no mouse scroll input. I want an event to fire on mouse scroll regardless if the page is moving or not.
Seems like what you are looking for:
http://jsfiddle.net/n8eVQ/
$(document).on('mousewheel DOMMouseScroll MozMousePixelScroll', function(event, delta) {
console.log('mousewheel');
//you could trigger window scroll handler
$(window).triggerHandler('scroll');
});
Other way is to capture scroll event on modern browsers which support event capturing phase (IE>8). This can be used for any dynamic element. As jQuery doesn't implement capturing phase, you have to use javascript addEventListener() method. Here an example implementing logic to get scrolling direction for a textarea:
document.addEventListener('scroll', function (event) {
var $elm = $(event.target);
if ($elm.is('textarea')) { // or any other filtering condition
// do some stuff
var direction = $elm.scrollTop() > ($elm.data('scrollTop') || 0) ? "down" : "up";
$elm.data('scrollTop', $elm.scrollTop());
console.log('scrolling', direction);
}
}, true);
-DEMO-
document.addEventListener('DOMMouseScroll', callbackFunction, false);
Solution for firefox; for other browsers see #roasted solution

jQueryUI cannot work immediately after I set it

I want to integrate both jQueryUI draggable and sortable in my project and distinguish them using the time difference between the starting time of mousedown and first mousemove events. Part of my code is as follows:
if (self.mouse.endTime - self.mouse.startTime < 700){
var deltaX = self.mouse.startX - event.pageX,
deltaY = self.mouse.startY - event.pageY;
// Drag down
if(Math.abs(deltaX) < Math.abs(deltaY) && deltaY < 0){
$(self.el).draggable({disabled: false});
$(self.el).find('li').draggable({disabled: true});
$(self.el).sortable({disabled: true});
$(self.el).draggable({axis: 'y'});
$(self.el).draggable({revert: true});
....
else {
$(self.el).sortable({disabled: false});
$(self.el).draggable({disabled: true}); // global dragging
$(self.el).children().draggable({disabled: true}); // item dragging
$(self.el).sortable({containment: $(self.el).closest('.content')});
}
But things are not what I expected. Every time I start dragging the element, the html shown in firebug has the right draggable/sortable class setting but it's not effective for current dragging events. For example, when you drag for the first time, the element is neither draggable nor sortable although the html has already had the corresponding class setting. It will be draggable or sortable the second time you drag it. And if you set the element draggable/sortable for current dragging event, it will work the next time you drag it. In a word, the draggable/sortable event you expect for current event will only be effective for the next event. I wanna know if this is what the jQueryUI should be or there is sth wrong with my code. How should I fix it?

js/jQuery Drag'n'Drop, recalculate the drop targets

I have the following issue, I have a large tree which has subnodes which can be folded and unfolded on demand (the data within nodes gets fetched with AJAX). However, I use jquery.event.drop/drag to create my drag/drop targets.
However, when I fold/unfold the drop targets change position and I need to recalculate. This is how I wanted to do that:
function create_drop_targets() {
$('li a')
.bind('dropstart', function(event) {
})
.bind('drop', function(event) {
})
.bind('dropend', function(event) {
});
}
create_drop_targets() is called upon fold/unfold.
However, this doesn't work. I have located the following within jquery.event.drop:
var drop = $.event.special.drop = {
setup: function(){
drop.$elements = drop.$elements.add( this );
drop.data[ drop.data.length ] = drop.locate( this );
},
locate: function( elem ){ // return { L:left, R:right, T:top, B:bottom, H:height, W:width }
var $el = $(elem), pos = $el.offset(), h = $el.outerHeight(), w = $el.outerWidth();
return { elem: elem, L: pos.left, R: pos.left+w, T: pos.top, B: pos.top+h, W: w, H: h };
}
Now I need to know how I can call the setup() method again so it repopulates $elements with the new positions for the droppables.
Just had the same issue. I wandered around within the source-code of jQuery and found this (in ui.droppable.js):
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);
...
So, you'd just have to use
$(".cocktails").draggable({
refreshPositions: true,
});
Seems not to be documented very much... but it fixed my problem. Makes everything a bit slower of course, I would advise some usage-dependent tweaking (enable it before the changes occur, and disable it once the user has moved his mouse and the changes have occured).
Maybe it will be better to add live events introduced in jQuery 1.3?
$("li a").live("dropstart", function(){...});
I ran into the same issue when I tried to combine scrolling with draggable rows in liteGrid, but I found a work-around. Your mileage may vary, but what I did was add logic to my drag event handler that would check to see if the grid was being scrolled (which is when I needed to force the droppable positions to be refreshed), and if so, I set refreshPositions to true on the draggable. This doesn't immediately refresh the positions, but it will cause them to refresh the next time the drag handle moves. Since refreshPositions slows things down, I then re-disable it the next time my drag event handler fires. The net result is that refreshPositions is enabled only when the grid is scrolling in liteGrid, and its disabled the rest of the time. Here's some code to illustrate:
//This will be called every time the user moves the draggable helper.
function onDrag(event, ui) {
//We need to re-aquire the drag handle; we don't
//hardcode it to a selector, so this event can be
//used by multiple draggables.
var dragHandle = $(event.target);
//If refreshOptions *was* true, jQueryUI has already refreshed the droppables,
//so we can now switch this option back off.
if (dragHandle.draggable('option', 'refreshPositions')) {
dragHandle.draggable('option', 'refreshPositions', false)
}
//Your other drag handling code
if (/* logic to determine if your droppables need to be refreshed */) {
dragHandle.draggable('option', 'refreshPositions', true);
}
}
$("#mydraggable").draggable({
//Your options here, note that refreshPositions is off.
drag: onDrag
});
I hope that saves you from ramming your head into the keyboard as many times as I did...
I realize the original question is quite old now, but one little trick I came up with to refresh the position of draggable elements without much overhead (AFAICT) is to disable and immediately re-enable them wherever appropriate.
For instance, I noticed that resizing my browser window would not refresh the position of my draggable table rows, so I did this:
$(window).resize(function () {
$(".draggable").draggable("option", "disabled", true);
$(".draggable").draggable("option", "disabled", false);
});
I hope this helps someone out there!

Categories

Resources