I've read around that a 300ms delay is inserted between clicks in order to detect double clicks.(on mobile devices) However I want to eliminate this delay, or at least shrink it to about 5ms.
The reason I need this, is because I currently loop through a set of elements and add click events to them, they are then clicked and expanded which reveals the data ready to be scraped. The website i am scraping is a mobile version of the desktop site.
I tried to include a library called FastClick, but couldn't get the working unfortunately and currently lost for ideas on how to eliminate the delay. Although it could be my code, any suggestions on how to speed it up?
var SELECTOR = 'li h2';
.evaluate(function (selector) {
$(function() {
FastClick.attach(document.body);
});
var els = $(selector);
$.each(els, function (idx, el) {
var event = document.createEvent('MouseEvent');
event.initEvent('click', true, true);
el.dispatchEvent(event);
});
}, SELECTOR)
Related
This is my first question, so i try to describe it precisely as possible and follow the stackoverflow guideline "How do I ask a good question?"
My Problem:
Im using the "old but gold" infinite-scroll jQuery plugin to load the next posts on a wordpress site. Im also using the lazyframe plugin to load youtube videos "lazy" with a click on the play button of a video. To ensure the lazyframe function after loading a set of new posts with the infinte scroll function, im initializing the lazyframe in the following way with adding the lazyframe('.lazyframe'); initializing code to the function(newElements) part of the infinite-scroll js:
//infinite scroll
if ($masonry.length && obj_testsite.infinitescroll != 'disable') {
nextSelector = obj_testsite.nextselector;
if (document.URL.indexOf('/source/') != -1) {
nextSelector = '#navigation #navigation-next a';
}
$masonry.infinitescroll({
navSelector : '#navigation',
nextSelector : nextSelector,
itemSelector : '.thumb',
prefill: true,
bufferPx : 500,
loading: {
msgText: '', // load spinner icon instead of gif images - see below
finishedMsg: obj_testsite.__allitemsloaded,
img: '',
finished: function() {}
}
}, function(newElements) {
lazyframe('.lazyframe');
var $newElems = $(newElements);
$('#infscr-loading').fadeOut('normal');
$masonry.masonry('appended', $newElems, true);
});
}
So far, this works fine. BUT, when i scroll down and load a few set of posts with infinite-scroll and then click the the play button sometimes the lazyframe plugin creates multiple youtube iframes.
My research so far showed me, that the reason why the event is firing multiple times is, because the event is attached to the element multiple times. The following code shows the part where the lazyframe plugin uses the addEventListener function to target an element in a document:
function n(e) {
if (e instanceof HTMLElement != !1) {
var t = {
el: e,
settings: i(e)
};
t.el.addEventListener("click", function() {
return t.el.appendChild(t.iframe)
}), d.videolazyload ? l(t) : s(t, !!t.settings.thumbnail)
}
}
Any idea how to solve this problem? I tried to stop the event propagation, but i was not able to fix it because of lack of knowledge. Any help would be appreciated.
Plugin author here. Thanks for using it!
There was a bug in the plugin that would attach multiple event listeners on the element/elements upon reinitialization. That's sorted in v1.1.1 so just update the plugin and it should work.
You use event capturing in your eventListener. Actually, not all browsers support event capturing (for example, Internet Explorer versions less than 9 don't) but all do support event bubbling, which is why it is the phase used to bind handlers to events in all cross-browser abstractions, jQuery's included.
The nearest to what you are looking for in jQuery is using on().
So you can change your code to this (event):
$(t.el).off('click').on('click', function() {
return t.el.appendChild(t.iframe)
});
off() function remove event listener (in our case 'click') on element that you specified, so you can try this solution.
I have a slider that I am currently making. I am making slow progress, but I am making progress nonetheless!
Currently I have this:
http://codepen.io/r3plica/pen/mEKyGG?editors=1011#0
There are 2 things you can do with this control, the first thing is you can drag left or right. The second thing you can do is click a "point" and it will scroll to the center.
The problem I have is that if I start dragging from a point, when I let go it will invoke the moveToCenter method.
I have tried to prevent this by adding
// Stop from accessing any child events
e.preventDefault();
e.stopPropagation();
to the end of the dragEventHandler, but this did not work.
I also have 2 boolean values options.drag and options.start. I though I might be able to use them somehow (if the drag has started and is enabled then don't perform the moveToCenter but this didn't work either.
Do anyone have any idea how to get this to work?
Maybe this will help. You can register your events in bubbling or capturing mode, using addEventListener method. It defines orders of processing your events - child -> parent (bubbling), or vice versa (capturing).
http://www.quirksmode.org/js/events_advanced.html
So, if you use addEventListener(event, handler, true), it will use capturing event mode.
Codepen:
http://codepen.io/anon/pen/bZKdqV?editors=1011
divs.forEach(function (div) {
div.addEventListener('click', function(e) {
e.stopPropagation();
console.log('parent');
}, true);
});
Be aware of browser support (IE9+). All modern browsers - yes, of course.
http://caniuse.com/#search=addeventlistener
Update
So it turned out to be easier than first approach. (no need for capturing)
Check out codepen:
http://codepen.io/anon/pen/QExjzV?editors=1010
Changes from your sample:
At the beginning of moveToCenter: function(e, options, animate) function
if (options.started) {
return;
}
In if (['mouseup', 'mouseleave'].indexOf(e.type) > -1):
setTimeout(function() {
options.started = false;
} , 100);
instead of
options.started = false;
Hope this helps.
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.
I am trying to use JS/JQuery to make tiles that will fade out then fade in with different data (In this case it is pictures) when you over on it and then reverse it when you mouse off of it. Now my code works fine in Chrome but when I test it in FireFox it keeps executing the fade in/out commands. I looked up similar situations where people use the $(this).stop().fadeOut(function() code but since I am doing multiple fades and loading information it won't do the animation correctly. Does anyone know a solution to this?
<script>
$(document).ready(function()
{
var hov = false;
$(".previewBox").mouseenter(function()
{
if(hov === false)
{
hov = true;
$(this).fadeOut(function()
{
$(this).load(function()
{
$(this).fadeIn();
});
$(this).attr("src", "Images/Portfolio/Art_Bike_Flip.png");
});
};
});
$(".previewBox").mouseleave(function()
{
if(hov === true)
{
hov = false;
$(this).fadeOut(function()
{
$(this).load(function()
{
$(this).fadeIn();
});
$(this).attr("src", "Images/Portfolio/Art_Bike_Preview.png");
});
};
});
});
</script>`enter code here`
There are several problems here. First .load is not a reliable way to detect image loading. Some browsers, when the image is cached, wont fire a load event, so the script will fail. You need to use a plugin like waitForImages or imageLoaded.
https://github.com/alexanderdickson/waitForImages I recommend this one.
Also .stop() will work fine for your needs, if it seems to cancel fades in some instances, try .stop(true, true), it should animate just fine, even with loading data and multiple fades. You may need to tune it so that the stop command only is placed on the last fade to occur.
also you are making a ton of jQuery objects when you only need one. Limiting it to one object will make your script substantially more efficient.
var previewBox = $('.previewBox');
Then you can use that one everywhere:
previewBox.mouseenter(function()
{
if(hov === false)
{
hov = true;
previewBox.stop().fadeOut(function(){
previewBox.imagesLoaded(function...
In your case with the multiple instances using a class, you need to isolate your events from one another. You can do this with .each
$('.previewBox').each(function(){
var previewBox = $(this);
previewBox.mouseenter(function(){ ....
By wrapping all your current logic in a .each you will avoid interaction of events between elements. In this way the events mouseenter mouseleave and the attached logic will bind isolated to each instance of an element with that class, instead of binding to all elements of that class.
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!