Strange lag which disappears by clicking anywhere on the html body - javascript

I have some overlay elements which are display: none initially but turn to display: inline when I hover over specific items on the page, and disappear again when the mouse hovers over something else. Exactly same behavior as tool-tips with the difference that this overlay objects have clickable and interactive elements (such as a jquery accordion).
Everything works perfectly, until I interact with these overlay elements, i.e. click on one of the clickable items in the overlay element. Then, once that overlay item becomes display:none again, the page becomes extremely laggy in terms of how long it takes when I hover over an item to find its corresponding overlay element (they are selected by their id) and for it to appear and disappear.
The strange thing is that if I click anywhere on the html body, the lag disappears and everything becomes fast as in the beginning.
Out of despair, I have tried to programmatically call blur, focus, trigger('click') once the overlay element is set back to display:none but none has helped so far, and I have to manually click on the page for the lag to go away.
Any idea what causes such behavior and how I can fix it? thanks,
Edit: code
CSS part:
span.overlay {
z-index:10;
display:none;
position:absolute;
}
span.visible { display:inline; }
HTML part: lots of such span elements, each with their own unique id.
<span class='overlay ui-widget-content' id='xyz'>
<!-- lots of stuff here -->
</span>
javascript part:
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').hover(
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
}
}, function() {
var elem = $('#' + $(this).parent().data('overlay-id'));
if (! elem.is(':hover') && ! elem.hasClass('pin')) {
$(elem).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
/* if mouse leaves span.visible and it is not pinned it will hide the span */
$('body').on('mouseleave', 'span.visible',
function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
});
Edit: profiling the code, it seems that get offsetHeight and get offsetWidth take way longer than before. Yet I do not know why this should happen and why it should go away by clicking on the page.
previously, when I do not observe the problem, these two functions each take less than 3%.

try binding the mouseleave event upon opening the "tooltip". Replace your code with this (not tested):
/* displays overlay element when user hovers over the first td */
$('table.foo > tbody > tr > td:first-child').on('mouseenter',
function(e) {
$(this).parent().tooltip('disable');
var elem = $('#' + $(this).parent().data('overlay-id'));
if (!elem.hasClass('visible')) {
elem.css('left', e.pageX + 20).css('top', e.pageY).addClass('visible');
elem.find('.accordion:first').accordion('refresh');
// notice the "ONE" handler, it'll unbind the event after execution
elem.one('mouseleave', function() {
if (!$(this).hasClass('pin')) {
$(this).removeClass('visible');
}
$(this).parent().tooltip('enable');
});
}
}
);
Notice the one listener to unbind the event after it's first execution.
I can't guarantee that this will fix your issue but I experienced lots of performance hits when a page has A LOT of elements and browsers need to check hover events that change very quickly.
This way the browser only needs to check one mouseleave event. And if it happened, it's gone again. It seems you may have too many bound events and don't clean them up properly.
I'm not sure if I replicated your desired functionality correctly so please add code if I missed something. I was unsure why exactly you'd need to bind a mouseleave event via body AND via .hover().

Related

Javascript eventListeners not firing

I am trying to get all buttons to do the same on click like the first button does.
Basically, it's just calling a function on click.
That function changes the innerHtml of the target div, adds a Css animation class, then removes the Css animation on mouseout.
This works exactly how i want it to on the first button, but not one the second and last.
function declaration() {
document.getElementById("me").innerHTML = "I am a function declaration and i
hoist to the top of the code, you
can call me before i get
declared.I look like this: < br > function declaration() {}
";
document.getElementById("me")
.classList.add("slideIt");
document.getElementById("fnDeclaration")
.addEventListener('mouseout', function() {
document.getElementById("me")
.classList.remove("slideIt");
});
}
document.getElementById("fnDeclaration").addEventListener("click", function() {
declaration();
});
http://codepen.io/damianocel/pen/xwJmwN
Why is this (not) happening?
Your content div (#me) is set-up last in the HTML (hence in DOM), so it sits on top of your buttons when sliding down.
Therefore you actually mouseout from your button as soon as the sliding down animation starts, which as per your code removes the slideIt class, hence stops the animation.
A quick fix could simply be to push your content div down below (z-index-wise) your buttons: #me {z-index: -10;}
Demo: http://codepen.io/anon/pen/vNvqYx

Passing mouseover through element

I'm working on a regex-analyzer that has syntax highlighting as a feature.
My site uses an overlay of two contenteditable divs.
Because of the difficulty in getting the cursor position and maintaining that even as tags are added and subtracted, I decided the best route was to have two contenteditable divs, one on top of the other. The first (#rInput) is pretty plain. If not for some nuisance problems that made me switch from a textarea, it could be a textarea. The second (#rSyntax) gets its value from rInput and provides syntax highlighting. I make sure that both are always scrolled to the same position so that the overlay is perfect (However, I also use a transparent (rgba(...)) font on rSyntax, so that if a momentary sync-delay should occur, the text is still legible.)
In the lower portion snapshot above, the code of the contenteditable rSyntax is this:
<span class="cglayer">(test<span class="cglayer">(this)</span>string)</span>
While rInput, positioned exactly on top, contains only this
(test(this)string)
The problem with this method is that I want to offer some alt-tooltips (or a javascript version) when a user mouses over. When the user mouses over rInput, I would like to pass the mouseover event to elements of rSyntax.
I'd like mousing over (test ... string) to show "Capturing group 1", while mousing over (this) would show "Capturing group 2", or if it were (?:test), it would say "Non-capturing group".
The terminology is a little tough because searching for things like "above" and "below" pulls up a lot of results that have nothing to do with z-index.
I did find the css property pointer-events which sounded ideal for the briefest moment but doesn't allow you to filter pointer events (set rInput to receive clicks, scrolls, but pass mouseover through to rSyntax and its child-elements.
I found document.elementFromPoint which may offer a solution but I'm not really sure how. I thought I would try document.getElementById('rInput').getElementFromPoint(mouseX,mouseY), but this function is not available to child elements.
Theoretically, I could hide the rInput on mousemove, using a setTimeout to put it back quickly, and then a tooltip should appear when mousing over child elements of rSyntax, but that doesn't seem like the best solution because I don't want rSyntax to be clickable. Clicks should always go to rInput.
It's possible to disable the mouseover wizardry while rInput has the focus, but then tooltips from rSyntax won't work, and though I haven't tried this method, I'm not sure if passing a click event from rSyntax to rInput would position the cursor properly.
Is there a method to make this work?
Update
The best solution was to move the syntax highlighter rSyntax to an iframe because then I can pass elementFromPoint() without flickering the div which really is a vast improvement.
$(document).on("mousemove", "#rInput", function (e) {
$element = $(document.getElementById('frSyntax').contentDocument.elementFromPoint(e.pageX,e.pageY));
if ($element.attr("id") != "frSyntax" && $element.attr("id") != "rSyntax" && $element.attr("title") && $element.attr("title").length) {
$mother.find(".dashed").removeClass("dashed")
$element.addClass("dashed")
$("#syntip").html($element.attr("title"))
$("#syntip").css({"top": e.pageY+10, "left": e.pageX, "display": "inline-block"})
} else {
$mother.find(".dashed").removeClass("dashed");
$("#syntip").hide()
}
})
And at the beginning of my $(document).ready(..., I added this
$('#frSyntax').load(function(){
$mother = $("#frSyntax").contents();
//frSyntax is the name of the iframe.
$syntax = $mother.find("#rSyntax")
});
First Solution
I'll leave this here in case it's more like what someone wants.
From further research, I feel like the only approach is mousemove wizardry making the element disappear, finding the element below, with document.elementFromPosition, and popping it back in.
I'm still looking for advice if anyone has a better option.
The trouble is that this "flickering" will cause mousemove and mouseover to keep triggering, which is needless. So I created a variable to log the coords and only apply a change when the mouse actually moved.
$(document).on("mousemove", "#rInput", function (e) {
if (holdmouse != (e.pageX + "," + e.pageY)) {
$("#rInput").hide();
element = $(document.elementFromPoint(e.pageX,e.pageY));
if (element.attr("id") != "rInput" && element.attr("id") != "rSyntax") {
$(".classes, .cglayer").css("border-bottom","none");
element.css("border-bottom","2px dashed black")
$("#syntip").html(element.attr("title"))
$("#syntip").css({"top": e.pageY+10, "left": e.pageX, "display": "inline-block"})
holdmouse = e.pageX + "," + e.pageY
} else {
$(".classes, .cglayer").css("border-bottom","none");
$("#syntip").hide()
}
$("#rInput").show()
}
})

Add DISPLAY: NONE to span via jQuery when: mouse-click outside span, OR click EXIT button, etc+

What I want is really simple, but every time I try to add the functionality I want, the more I'd mess things up, so I decided to ask help and stick with the working basic script I have now.
I already have a script in progress, that I would like to develop to work almost exactly like this:
https://stackoverflow.com/a/7133084/1399030 { http://jsfiddle.net/Paulpro/YpeeR/25/ } (by: PaulP.R.O.)
Open a hidden span
Hide a hidden span
Span has "CLOSE" button to exit span
Hide currently opened span when another span is triggered
Think... Image Gallery Preview functionality... Kind of.
"Preview" spans are triggered when either .popCover or a.thumbnail is clicked on the webpage, this hidden span will appear based on its specified unique id, by jQuery inserting display: block; to its css.
This is inside a loop with multiple items.
I've gotten this far and this is the working script that I use:
$(document).ready(function() {
$('.popCover').click(function(){
divID = $(this).attr('id');
$("#tooltip-"+divID).fadeIn('5000', function() {
$("#tooltip-"+divID).css("display", "block");
});
});
$("a.thumbnail").click(function() {
dvID = $(this).attr('id');
$("#tooltip-"+dvID).fadeIn('5000', function() {
$("#tooltip-"+dvID).css("display", "block");
});
});
});
But now, I need to add to these functions the trigger to make the span disappear again, (by inserting display: none; to its css.
I'd want the CURRENT SPAN to disappear when:
01. Mouse click is made outside of the span element
02. An exit or X button is clicked INSIDE the span. (like on image galleries, when they preview an image, and exit it by either clicking outside the element or an exit button provided within the preview)
03. .popCover or a.thumbnail is re-clicked (probably to trigger another span of a different ID to show.)
NOTES:
Currently, I can click as many anchors on the page and all these spans with different IDs just accumulate and stack up over each other on the page.
I don't really want that. I don't want more than 1 span to be open at one time, so I was hoping to add functionality that would make the current opened span exit itself when another anchor click is made.
I really did try to do this myself, but... I can't get the methods I've tried to work. It was too complicated to add all these functions together since I'm no jQuery expert. I could get one to work and then ruin it by trying to work in another.
Also, I was thinking of using this similar way of exiting the span:
$(".the_span").fadeOut("5000").css("display", "none");
The only reason I'm not willing to just use some plugin and uncomplicate things for me is, I already really like my "Preview" span css, I have it all ready. I just need the jquery part to work:
To display: block a span when triggered, and display: none it if mentioned conditions are met.
Hoping for assistance, and will be very grateful for each single one! Thank you.
You have to try to add a class on the opened / active element and then bind all the events to close it. Binds have to be done on elements with class .active for example, when closed, .active class have to be removed.
I've finally gotten this to work! :o)
By using if ($("span.the_span").is(":visible")) to check if span with class="the_span" was currently visible / open / or has display: block in its CSS, and if so, to:
- hide the currently open span, before proceeding to show the new span. -
Here's my working finished product that addresses all the functionality I wanted:
$(document).ready(function() {
// When clicks on either ".popCover" or "a.thumbnail" is made,
// Funcion clickPOP is triggered:
var clickPOP = function() {
divID = $(this).attr('id');
// Checks if "span.the_span" is already currently open:
if ($("span.the_span").is(":visible")) {
$("span.the_span").css("display", "none"); // If open, this is where it closes it..
$("#tooltip-"+divID).fadeIn('200', function() { // Then, proceeds to open the new clicked span here.
$("span.the_span #tooltip-"+divID).css("display", "block"); });
}
// But if no "span.the_span" is currently open:
// No need to close anything, it will directly open the new span...
else {
$("#tooltip-"+divID).fadeIn('5000', function() {
$("span.the_span #tooltip-"+divID).css("display", "block"); });
}
} // End of Function. Added functionality starts below...
// Exits "span.the_span" when mouse clicks outside of element
// ... ("Outside of element" means: outside of "span.the_span")
$(document).click(function(){
$("span.the_span").css("display", "none");
});
// Exit Button : Exits "span.the_span" when exit button is clicked
$('span.exitme').css('cursor', 'pointer').click(function(e){
$("span.the_span").css("display", "none");
e.stopPropagation();
});
// This makes sure that clicks inside "span.the_span" continue to work
$('span.the_span').click(function(e){
e.stopPropagation();
});
// This makes sure that clicks on ".popCover" continue to work
$(".popCover").click(function(e){
e.stopPropagation();
});
// This makes sure that clicks on "a.thumbnail" continue to work
$("a.thumbnail").click(function(e){
e.stopPropagation();
});
// Clicks on both ".popCover" & "a.thumbnail"
// ... will trigger actions specified on function: clickPOP.
$(".popCover").click(clickPOP);
$("a.thumbnail").click(clickPOP);
});
As you can see, I've also added the $(document).click(function() etc. to get my original desired functionality of hiding the span when mouse clicks outside of the element, but making sure that clicks can still be made if they are done on .popCover (div) or a.thumbnail (link) on the webpage.
Also, I wouldn't have been able to complete writing this method without the tips from these posts:
* Running same function / different triggers: https://stackoverflow.com/a/1191837/1399030
* Fix clicking inside element (including exit button): https://stackoverflow.com/a/4660691/1399030
* How to check if something is hidden or visible: https://stackoverflow.com/a/178450/1399030
I especially found the last post VERY helpful (and basically it made me understand what I was doing), because poster: Tsvetomir Tsonev included in his code comments:
// Checks for display:[none|block], ignores visible:[true|false]"
I didn't really initially understand that jQuery was able to check or connect with CSS that wasn't inline (being a jQuery noob myself), so that post was indeed very enlightening.
Of course, if there is a better, more efficient way to do this, I would be very happy to be enlightened some more! jQuery is still a learning curve for me, and I'm a very eager student!

Disable slideDown/Up on links inside div

Basicllay i have a div with a class called .li-level-1, and inside that i have differnt ul's with lists. i Have it set up so when you click on a li-level-1 div displays the ul's and li's inside that div by animating a drop down and when you click on the next one it closes the one previously opened and slidesDown the next one.
the only thing is the a links that are inside the div's seem to trigger the slideUp/Down on level-1 and animation as well.
any Suggestions?
$('.sitemap_page .li-level-1').each(function(){
$(this).find('ul.ul-level-2').hide();
$(this).click(function(){
var this_list = $(this);
this_list.parent().find('.open').each(function(){
$(this).slideUp(function(){
this_list.find('ul.ul-level-2').addClass("open").slideDown();
}).removeClass('open');
});
if(this_list.find('ul.ul-level-2.open').length == 0) {
this_list.find('ul.ul-level-2').addClass("open").slideDown();
}
});
});
That's because of event bubbling: the click event raised on the <a> elements bubble up to their containing <div> and cause your event handler to execute.
One way to work around that problem would be to use event.target to determine the event's origin, and only perform the sliding animations if the event did not originate on a link:
$(this).click(function(event) {
if (!$(event.target).is("a")) {
var this_list = $(this);
this_list.parent().find('.open').each(function() {
$(this).slideUp(function() {
this_list.find('ul.ul-level-2').addClass("open").slideDown();
}).removeClass('open');
});
if (this_list.find('ul.ul-level-2.open').length == 0) {
this_list.find('ul.ul-level-2').addClass("open").slideDown();
}
}
});
The problem is with event bubbling as sugested by Frederic. The other possible solution is to divide your div into title and content divs. Hold data in content and check click on title (not on the parent list). This means rebuilding the handler but the code will be clearer and it won't depend on event.target.

Javascript GUI issues with event bubbling

I am trying to setup a fairly simple (imo) Javascript GUI, and am running into issues. I'm using jQuery. Here's what I want to do:
Have a bunch of squares next to some text, like this:
[ ] Text next to square
[ ] Text next to square 2
[ ] Text next to square 3
[ ] Text next to square 4
When you click one of the squares, an absolutely positioned floating div shows up. That floating div has some interface options.
At this point, if the user clicks anywhere OUTSIDE the floating div, the div should disappear. If the user clicks another box, the original div should disappear, and a new one should appear.
I am able to get the floating divs to show up correctly, but I'm having trouble hiding them. What I'm currently doing is attached a click handler to $(document) in the function that show the floating div. Simplified code is below:
show(jqueryObj)
{
jqueryObj.show();
$(document).one("click", function () { jqueryObj.hide(); });
}
(Show is bound to a lick on one of the [ ] boxes elsewhere)
The problem I run into is that the click event seems to bubble up, and function () { jqueryObj.hide(); } is executed immediately. I've tried returning false in show(), but that didn't seem to resolve the issue. What should I be doing here?
$(document).click(function(e) {
e.stopPropagation();
var el = $(e.target);
alert( el.get(0).nodeName );
});
Off the top of my head, are you looking for something like this? I ignored your code because it isn't standard jQuery and a bit abstracted.
It could be that your click handler is getting called for the same click that it's added in. If that's the case then you would fix it by stopping the original click from bubbling (e.g. whatever is calling your function show).
Another option is to hack it:
show(jqueryObj)
{
jqueryObj.show();
setTimeout(function() {
$(document).one("click", function () { jqueryObj.hide(); });
}, 0);
}
If I'm getting you right, your floating div disappears when you interact with it because that document click catches the interaction and hides it. If thats the case you need to check the target of your event and exit out if it is the floating div. Something like this:
$(document).click(function(e) {
var target = $(e.target);
if (target.is("#floatingDiv")) return;
jqueryObj.hide();
});
Hope that helps
Edit: one thing to note is that you can check multiple conditions in the .is() call. So for example you could check for clicks on the floating div AND on one of your activation boxes:
if (target.is("#floatingDiv, .activateBox")) return;

Categories

Resources