Is there a way to bind to a double-tap event (in a single line of code) for mobile safari? Or, the alternative is to implement it intercepting two single-tap events that happened in some short given time (example: http://appcropolis.com/blog/implementing-doubletap-on-iphones-and-ipads/)?
Short answer: you must implement via two clicks.
Actual answer: Here is a jQuery-free implementation of double-tap for mobile safari which only requires one line of code (to enable dblclick event):
https://gist.github.com/2927073
In addition, you will likely need to disable mobile Safari's default zoom with this meta tag:
<meta name="viewport" content="width=device-width,user-scalable=no" />
If you want to have working double click both on browser and IOS platform, you should have the following code:
jQuery.event.special.dblclick = {
setup: function(data, namespaces) {
var agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0 || agent.indexOf('ipod') >= 0) {
var elem = this,
$elem = jQuery(elem);
$elem.bind('touchend.dblclick', jQuery.event.special.dblclick.handler);
} else {
var elem = this,
$elem = jQuery(elem);
$elem.bind('click.dblclick', jQuery.event.special.dblclick.handler);
}
},
teardown: function(namespaces) {
var agent = navigator.userAgent.toLowerCase();
if (agent.indexOf('iphone') >= 0 || agent.indexOf('ipad') >= 0 || agent.indexOf('ipod') >= 0) {
var elem = this,
$elem = jQuery(elem);
$elem.unbind('touchend.dblclick');
} else {
var elem = this,
$elem = jQuery(elem);
$elem.unbind('click.dblclick', jQuery.event.special.dblclick.handler);
}
},
handler: function(event) {
var elem = event.target,
$elem = jQuery(elem),
lastTouch = $elem.data('lastTouch') || 0,
now = new Date().getTime();
var delta = now - lastTouch;
if (delta > 20 && delta < 500) {
$elem.data('lastTouch', 0);
$elem.trigger('dblclick');
} else {
$elem.data('lastTouch', now);
}
}
};
Try it here:
http://jsfiddle.net/UXRF8/
Override dblclick event and use bind, live, on, delegate like for any other event:
jQuery.event.special.dblclick = {
setup: function(data, namespaces) {
var elem = this,
$elem = jQuery(elem);
$elem.bind('touchend.dblclick', jQuery.event.special.dblclick.handler);
},
teardown: function(namespaces) {
var elem = this,
$elem = jQuery(elem);
$elem.unbind('touchend.dblclick');
},
handler: function(event) {
var elem = event.target,
$elem = jQuery(elem),
lastTouch = $elem.data('lastTouch') || 0,
now = new Date().getTime();
var delta = now - lastTouch;
if(delta > 20 && delta<500){
$elem.data('lastTouch', 0);
$elem.trigger('dblclick');
}else
$elem.data('lastTouch', now);
}
};
Related
I have some javascript code that fires a click event when the user scrolls up or down past a specific element (.closemenu). I'm using this to automatically open and close a header menu when the user scrolls the page.
The problem I have is that this is firing too many times and causing lagging and glitching on scroll, I've found this post which shows the usage of throttling for scroll events but I can't get it to work with my current javascript which is:
<script>
jQuery(window).scroll(function() {
var hT = jQuery('.closemenu').offset().top,
hH = jQuery('.closemenu').outerHeight(),
wH = jQuery(window).height(),
wS = jQuery(this).scrollTop();
console.log((hT-wH) , wS);
if (wS > (hT+hH-wH) && (hT > wS) && (wS+wH > hT+hH)){
jQuery('.menu-bar').trigger('click');
}
});
</script>
I've tried a few variations but I can't work out what the issue is, does anyone know how can I throttle this event by 30ms or so?
Try if the below code works for you. You can change the ES6 throttle function to ES5 if your browser doesn't support ES6.
var func = function(){
var hT = jQuery('.closemenu').offset().top,
hH = jQuery('.closemenu').outerHeight(),
wH = jQuery(window).height(),
wS = jQuery(this).scrollTop();
console.log((hT-wH) , wS);
if (wS > (hT+hH-wH) && (hT > wS) && (wS+wH > hT+hH)){
jQuery('.menu-bar').trigger('click');
}
}
jQuery(window).scroll(function() {
throttle(func,30);
});
const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
WORKING ANSWER:
<script>
jQuery(window).scroll(function() {
var hT = jQuery('.closemenu').offset().top,
hH = jQuery('.closemenu').outerHeight(),
wH = jQuery(window).height(),
wS = jQuery(this).scrollTop();
console.log((hT-wH) , wS);
if (wS > (hT+hH-wH) && (hT > wS) && (wS+wH > hT+hH)){
jQuery('.menu-bar').trigger('click');
}
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 1250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
});
</script>
I'm trying to use the jQuery appear plugin. I'm having trouble making it work. I tried to attach it to the (window).scroll event but it makes the page slow. If I don't use the scroll, it only fires once. I need it to work again whenever the element becomes visible. Can you give me some tips on how to make it work.
Here's my code:
jQuery('.home-section-1').appear(function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
As ɴ-ᴀ-ᴛ-ʜ said in his comment, you need to be using .on to listen for the appear event.
jQuery('.home-section-1').on('appear', function(){
jQuery('.page-scroll-indicator .fa.fa-circle').removeClass('active-ind');
jQuery('.page-scroll-indicator .section-1').addClass('active-ind');
});
Here's a code snippit showing it working, you'll notice that your method (Method 1) doesn't fire, while the method above (Method 2) does:
/*
* jQuery appear plugin
*
* Copyright (c) 2012 Andrey Sidorov
* licensed under MIT license.
*
* https://github.com/morr/jquery.appear/
*
* Version: 0.3.4
*/
(function($) {
var selectors = [];
var check_binded = false;
var check_lock = false;
var defaults = {
interval: 250,
force_process: false
}
var $window = $(window);
var $prior_appeared;
function process() {
check_lock = false;
for (var index = 0, selectorsLength = selectors.length; index < selectorsLength; index++) {
var $appeared = $(selectors[index]).filter(function() {
return $(this).is(':appeared');
});
$appeared.trigger('appear', [$appeared]);
if ($prior_appeared) {
var $disappeared = $prior_appeared.not($appeared);
$disappeared.trigger('disappear', [$disappeared]);
}
$prior_appeared = $appeared;
}
}
// "appeared" custom filter
$.expr[':']['appeared'] = function(element) {
var $element = $(element);
if (!$element.is(':visible')) {
return false;
}
var window_left = $window.scrollLeft();
var window_top = $window.scrollTop();
var offset = $element.offset();
var left = offset.left;
var top = offset.top;
if (top + $element.height() >= window_top &&
top - ($element.data('appear-top-offset') || 0) <= window_top + $window.height() &&
left + $element.width() >= window_left &&
left - ($element.data('appear-left-offset') || 0) <= window_left + $window.width()) {
return true;
} else {
return false;
}
}
$.fn.extend({
// watching for element's appearance in browser viewport
appear: function(options) {
var opts = $.extend({}, defaults, options || {});
var selector = this.selector || this;
if (!check_binded) {
var on_check = function() {
if (check_lock) {
return;
}
check_lock = true;
setTimeout(process, opts.interval);
};
$(window).scroll(on_check).resize(on_check);
check_binded = true;
}
if (opts.force_process) {
setTimeout(process, opts.interval);
}
selectors.push(selector);
return $(selector);
}
});
$.extend({
// force elements's appearance check
force_appear: function() {
if (check_binded) {
process();
return true;
};
return false;
}
});
})(jQuery);
// Your method
jQuery('.home-section-1').appear(function(){
alert('Method 1');
});
// Using .on
jQuery('.home-section-1').on('appear', function(){
alert('Method 2');
});
.home-section-1 {
margin-top: 2000px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="home-section-1">Hello World</div>
i'm trying to make a magento extension work on android mobile devices. I bought this extension :
http://ecommerce.aheadworks.com/magento-extensions/layered-navigation.html
to get ajax layered navigation. It works well, but when it comes to android compatibility i get an issue on the range selector (slider selector, used for price range).
The feature works well on all device (ios included), but on android, i always got NaN instead of numbers values on the range selector. After digging the plugin's code, i found the origin in the javascript :
startDrag: function(event) {
var isLeftClick = Event.isLeftClick(event);
if (Prototype.Browser.IE) {
var ieVersion = parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5));
if (ieVersion > 8) {
isLeftClick = event.which === 1;
}
}
if (isLeftClick || event.type === 'touchstart') {
if (!this.disabled){
this.active = true;
var handle = Event.element(event);
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var track = handle;
if (track==this.track) {
var offsets = Position.cumulativeOffset(this.track);
this.event = event;
this.setValue(this.translateToValue(
(this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
));
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
} else {
// find the handle (prevents issues with Safari)
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
handle = handle.parentNode;
if (this.handles.indexOf(handle)!=-1) {
this.activeHandle = handle;
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
this.updateStyles();
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
}
}
}
Event.stop(event);
}
}
this line :
var pointer = [Event.pointerX(event), Event.pointerY(event)];
return a correct array of value (mouse coordinates) on all devices, except on android, where i get NaN. Is this a known bug of prototype.js, and can I bypass it ?
thanks,
Problem solved by digging the event object, then manually retrieving the X and Y pos (tested clientX, screenX and pageX) :
var pointer = [Event.pointerX(event), Event.pointerY(event)];
if(navigator.userAgent.match(/Android/i)){
pointer = [parseInt(event.targetTouches[0].clientX),parseInt(event.targetTouches[0].clientY)];
}
I had the same issue, I know this is an old thread but want to post my solution anyway. I needed to override the functions in swiper.js.
update: function(event) {
if (this.active) {
if (!this.dragging) this.dragging = true;
this.draw(event);
if (Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
}
},
draw: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
if(navigator.userAgent.match(/Android/i)){
pointer = [parseInt(event.targetTouches[0].clientX),parseInt(event.targetTouches[0].clientY)];
}
var offsets = Position.cumulativeOffset(this.track);
pointer[0] -= this.offsetX + offsets[0];
pointer[1] -= this.offsetY + offsets[1];
this.event = event;
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
if (this.initialized && this.options.onSlide)
this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
},
And this fixed my issue
I'm using Zepto.js on a current project. Zepto doesn't support the scrollTop() method that jQuery has in it.
Is it possible to kind of extend Zepto to work with scrollTop() too?
Update: All I want is to create my own small and simple "animated scroll" function like I have used before with jQuery. See the working example here. However I have no idea how to make the same function work without the scrollTop() function available in Zepto.js.
scrollTop isn't animatable using Zepto's .animate method, as it uses CSS transitions.
Try something like this: http://jsfiddle.net/DVDLM/5/
function scroll(scrollTo, time) {
var scrollFrom = parseInt(document.body.scrollTop),
i = 0,
runEvery = 5; // run every 5ms
scrollTo = parseInt(scrollTo);
time /= runEvery;
var interval = setInterval(function () {
i++;
document.body.scrollTop = (scrollTo - scrollFrom) / time * i + scrollFrom;
if (i >= time) {
clearInterval(interval);
}
}, runEvery);
}
$('#trigger').click(function () {
scroll('600px', 500);
});
EDIT: I added a runEvery variable, which specifies how often the interval should be ran. The lower this is, the smoother the animation is, but it could affect performance.
EDIT2: I think I misread the question. Here is the answer to the new question:
$.zepto.scrollTop = function (pixels) {
this[0].scrollTop = pixels;
};
dont want to steel nobody work so here is the short answer
Porting from jQuery to Zepto
Use the DOM native scrollTop property:
$('#el')[0].scrollTop = 0;
(function ($) {
['width', 'height'].forEach(function(dimension) {
var offset, Dimension = dimension.replace(/./, function(m) { return m[0].toUpperCase() });
$.fn['outer' + Dimension] = function(margin) {
var elem = this;
if (elem) {
var size = elem[dimension]();
var sides = {'width': ['left', 'right'], 'height': ['top', 'bottom']};
sides[dimension].forEach(function(side) {
if (margin) size += parseInt(elem.css('margin-' + side), 10);
});
return size;
}
else {
return null;
}
};
});
["Left", "Top"].forEach(function(name, i) {
var method = "scroll" + name;
function isWindow( obj ) {
return obj && typeof obj === "object" && "setInterval" in obj;
}
function getWindow( elem ) {
return isWindow( elem ) ? elem : elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false;
}
$.fn[method] = function( val ) {
var elem, win;
if (val === undefined) {
elem = this[0];
if (!elem) {
return null;
}
win = getWindow(elem);
// Return the scroll offset
return win ? ("pageXOffset" in win) ? win[i ? "pageYOffset" : "pageXOffset"] :
win.document.documentElement[method] ||
win.document.body[method] :
elem[method];
}
// Set the scroll offset
this.each(function() {
win = getWindow(this);
if (win) {
var xCoord = !i ? val : $(win).scrollLeft();
var yCoord = i ? val : $(win).scrollTop();
win.scrollTo(xCoord, yCoord);
}
else {
this[method] = val;
}
});
}
});
})(Zepto);
The answer is simple, Zepto dose not use timeout style animation, it uses css3, so here is a basic implementation for a scroll function:
HTML:
Animated Scroll
Hello You
CSS:
#page { height:5000px; position:relative; }
#element { position:absolute; top:600px }
JS:
function scroll(selector, animate, viewOffset) {
$('body').scrollToBottom (600, '800');
}
$('#trigger').click(function(e) {
e.preventDefault();
scroll( $('#element'), true, 30 );
});
$.fn.scrollToBottom = function(scrollHeight ,duration) {
var $el = this;
var el = $el[0];
var startPosition = el.scrollTop;
var delta = scrollHeight - startPosition;
var startTime = Date.now();
function scroll() {
var fraction = Math.min(1, (Date.now() - startTime) / duration);
el.scrollTop = delta * fraction + startPosition;
if(fraction < 1) {
setTimeout(scroll, 10);
}
}
scroll();
};
Note that version 1.0 of Zeptos now supports scrollTop(). See Documentation:
http://zeptojs.com/#scrollTop
im very new to jquery/javascript. So here goes, I found this js code for swipedown and swipeup. But don't know how to use it or call it.
The js code:
(function() {
// initializes touch and scroll events
var supportTouch = $.support.touch,
scrollEvent = "touchmove scroll",
touchStartEvent = supportTouch ? "touchstart" : "mousedown",
touchStopEvent = supportTouch ? "touchend" : "mouseup",
touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
// handles swipeup and swipedown
$.event.special.swipeupdown = {
setup: function() {
var thisObject = this;
var $this = $(thisObject);
$this.bind(touchStartEvent, function(event) {
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] :
event,
start = {
time: (new Date).getTime(),
coords: [ data.pageX, data.pageY ],
origin: $(event.target)
},
stop;
function moveHandler(event) {
if (!start) {
return;
}
var data = event.originalEvent.touches ?
event.originalEvent.touches[ 0 ] :
event;
stop = {
time: (new Date).getTime(),
coords: [ data.pageX, data.pageY ]
};
// prevent scrolling
if (Math.abs(start.coords[1] - stop.coords[1]) > 10) {
event.preventDefault();
}
}
$this
.bind(touchMoveEvent, moveHandler)
.one(touchStopEvent, function(event) {
$this.unbind(touchMoveEvent, moveHandler);
if (start && stop) {
if (stop.time - start.time < 1000 &&
Math.abs(start.coords[1] - stop.coords[1]) > 30 &&
Math.abs(start.coords[0] - stop.coords[0]) < 75) {
start.origin
.trigger("swipeupdown")
.trigger(start.coords[1] > stop.coords[1] ? "swipeup" : "swipedown");
}
}
start = stop = undefined;
});
});
}
};
//Adds the events to the jQuery events special collection
$.each({
swipedown: "swipeupdown",
swipeup: "swipeupdown"
}, function(event, sourceEvent){
$.event.special[event] = {
setup: function(){
$(this).bind(sourceEvent, $.noop);
}
};
});
})();
this is the anchor element that I want to trigger using the above code
tried call the function like this:
$('#swipedown').live('swipedown', function(event, data){
alert('swipes')
});
but it didn't prompt me the alert. >.<.
Any help will be much appreciated.
The last () are calling the funcion after it's created. The funcion adds a new possibility to jquery. So you can probably call it doing:
$('#swipedown').bind('swipedown', function(e){
alert('test');
});
Your code will be added (as documented) to the jQuery events special collection this means that you could call swipeup or swipedown as any other jQuery event.
example:
$("div p").live('swipedown',function() {
alert("swiped down");
});
That will be triggered on a swipedown action happening to a p element like:
<body>
<div>
<h1>hello world</h1>
<p>pull me down</p>
</div>
</body>