How to get first visible DOM element currently showing on the screen ?
I tried something like
var el = document.elementFromPoint(x, y)
and increasing the y coordinates in a while loop, but the problem is it does not work when there are css multi columns on the document, in that case the < html > tag is returned, not the actual element. Is there any way I can really get the element on the (top,left) of the screen which works for css columns ?
This is a very basic example that just gets the element that is the furthest to the top. It's just a start, because you may want to take into consideration the left offset as well.
You'll want to exclude the body and html elements (as shown below), since they will always be first.
Fiddle
var first;
$(':visible').not('body, html').each(function() {
if (typeof first == 'undefined') {
first = $(this);
} else if ($(this).offset().top < first.offset().top) {
first = $(this);
}
});
first.addClass('highlight');
Related
I have a contenteditable div as follow (| = cursor position):
<div id="mydiv" contenteditable="true">lorem ipsum <spanclass="highlight">indol|or sit</span> amet consectetur <span class='tag'>adipiscing</span> elit</div>
I would like to get the current cursor position including html tags. My code :
var offset = document.getSelection().focusOffset;
Offset is returning 5 (full text from the last tag) but i need it to handle html tags. The expected return value is 40. The code has to work with all recents browsers.
(i also checked this : window.getSelection() offset with HTML tags? but it doesn't answer my question).
Any ideas ?
Another way to do it is by adding a temporary marker in the DOM and calculating the offset from this marker. The algorithm looks for the HTML serialization of the marker (its outerHTML) within the inner serialization (the innerHTML) of the div of interest. Repeated text is not a problem with this solution.
For this to work, the marker's serialization must be unique within its div. You cannot control what users type into a field but you can control what you put into the DOM so this should not be difficult to achieve. In my example, the marker is made unique statically: by choosing a class name unlikely to cause a clash ahead of time. It would also be possible to do it dynamically, by checking the DOM and changing the class until it is unique.
I have a fiddle for it (derived from Alvaro Montoro's own fiddle). The main part is:
function getOffset() {
if ($("." + unique).length)
throw new Error("marker present in document; or the unique class is not unique");
// We could also use rangy.getSelection() but there's no reason here to do this.
var sel = document.getSelection();
if (!sel.rangeCount)
return; // No ranges.
if (!sel.isCollapsed)
return; // We work only with collapsed selections.
if (sel.rangeCount > 1)
throw new Error("can't handle multiple ranges");
var range = sel.getRangeAt(0);
var saved = rangy.serializeSelection();
// See comment below.
$mydiv[0].normalize();
range.insertNode($marker[0]);
var offset = $mydiv.html().indexOf($marker[0].outerHTML);
$marker.remove();
// Normalizing before and after ensures that the DOM is in the same shape before
// and after the insertion and removal of the marker.
$mydiv[0].normalize();
rangy.deserializeSelection(saved);
return offset;
}
As you can see, the code has to compensate for the addition and removal of the marker into the DOM because this causes the current selection to get lost:
Rangy is used to save the selection and restore it afterwards. Note that the save and restore could be done with something lighter than Rangy but I did not want to load the answer with minutia. If you decide to use Rangy for this task, please read the documentation because it is possible to optimize the serialization and deserialization.
For Rangy to work, the DOM must be in exactly the same state before and after the save. This is why normalize() is called before we add the marker and after we remove it. What this does is merge immediately adjacent text nodes into a single text node. The issue is that adding a marker to the DOM can cause a text node to be broken into two new text nodes. This causes the selection to be lost and, if not undone with a normalization, would cause Rangy to be unable to restore the selection. Again, something lighter than calling normalize could do the trick but I did not want to load the answer with minutia.
EDIT: This is an old answer that doesn't work for OP's requirement of having nodes with the same text. But it's cleaner and lighter if you don't have that requirement.
Here is one option that you can use and that works in all major browsers:
Get the offset of the caret within its node (document.getSelection().anchorOffset)
Get the text of the node in which the caret is located (document.getSelection().anchorNode.data)
Get the offset of that text within #mydiv by using indexOf()
Add the values obtained in 1 and 3, to get the offset of the caret within the div.
The code would look like this for your particular case:
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = $("#mydiv").html().indexOf( text );
offsetCaret = textOffset + offset;
You can see a working demo on this JSFiddle (view the console to see the results).
And a more generic version of the function (that allows to pass the div as a parameter, so it can be used with different contenteditable) on this other JSFiddle:
function getCaretHTMLOffset(obj) {
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = obj.innerHTML.indexOf( text );
return textOffset + offset;
}
About this answer
It will work in all recent browsers as requested (tested on Chrome 42, Firefox 37, and Explorer 11).
It is short and light, and doesn't require any external library (not even jQuery)
Issue: If you have different nodes with the same text, it may return the offset of the first occurrence instead of the real position of the caret.
NOTE: This solution works even in nodes with repeated text, but it detects html entities (e.g.: ) as only one character.
I came up with a completely different solution based on processing the nodes. It is not as clean as the old answer (see other answer), but it works fine even when there are nodes with the same text (OP's requirement).
This is a description of how it works:
Create a stack with all the parent elements of the node in which the caret is located.
While the stack is not empty, traverse the nodes of the containing element (initially the content editable div).
If the node is not the same one at the top of the stack, add its size to the offset.
If the node is the same as the one at the top of the stack: pop it from the stack, go to step 2.
The code is like this:
function getCaretOffset(contentEditableDiv) {
// read the node in which the caret is and store it in a stack
var aux = document.getSelection().anchorNode;
var stack = [ aux ];
// add the parents to the stack until we get to the content editable div
while ($(aux).parent()[0] != contentEditableDiv) { aux = $(aux).parent()[0]; stack.push(aux); }
// traverse the contents of the editable div until we reach the one with the caret
var offset = 0;
var currObj = contentEditableDiv;
var children = $(currObj).contents();
while (stack.length) {
// add the lengths of the previous "siblings" to the offset
for (var x = 0; x < children.length; x++) {
if (children[x] == stack[stack.length-1]) {
// if the node is not a text node, then add the size of the opening tag
if (children[x].nodeType != 3) { offset += $(children[x])[0].outerHTML.indexOf(">") + 1; }
break;
} else {
if (children[x].nodeType == 3) {
// if it's a text node, add it's size to the offset
offset += children[x].length;
} else {
// if it's a tag node, add it's size + the size of the tags
offset += $(children[x])[0].outerHTML.length;
}
}
}
// move to a more inner container
currObj = stack.pop();
children = $(currObj).contents();
}
// finally add the offset within the last node
offset += document.getSelection().anchorOffset;
return offset;
}
You can see a working demo on this JSFiddle.
About this answer:
It works in all major browsers.
It is light and doesn't require external libraries (apart from jQuery)
It has an issue: html entities like are counted as one character only.
I have a contenteditable div as follow (| = cursor position):
<div id="mydiv" contenteditable="true">lorem ipsum <spanclass="highlight">indol|or sit</span> amet consectetur <span class='tag'>adipiscing</span> elit</div>
I would like to get the current cursor position including html tags. My code :
var offset = document.getSelection().focusOffset;
Offset is returning 5 (full text from the last tag) but i need it to handle html tags. The expected return value is 40. The code has to work with all recents browsers.
(i also checked this : window.getSelection() offset with HTML tags? but it doesn't answer my question).
Any ideas ?
Another way to do it is by adding a temporary marker in the DOM and calculating the offset from this marker. The algorithm looks for the HTML serialization of the marker (its outerHTML) within the inner serialization (the innerHTML) of the div of interest. Repeated text is not a problem with this solution.
For this to work, the marker's serialization must be unique within its div. You cannot control what users type into a field but you can control what you put into the DOM so this should not be difficult to achieve. In my example, the marker is made unique statically: by choosing a class name unlikely to cause a clash ahead of time. It would also be possible to do it dynamically, by checking the DOM and changing the class until it is unique.
I have a fiddle for it (derived from Alvaro Montoro's own fiddle). The main part is:
function getOffset() {
if ($("." + unique).length)
throw new Error("marker present in document; or the unique class is not unique");
// We could also use rangy.getSelection() but there's no reason here to do this.
var sel = document.getSelection();
if (!sel.rangeCount)
return; // No ranges.
if (!sel.isCollapsed)
return; // We work only with collapsed selections.
if (sel.rangeCount > 1)
throw new Error("can't handle multiple ranges");
var range = sel.getRangeAt(0);
var saved = rangy.serializeSelection();
// See comment below.
$mydiv[0].normalize();
range.insertNode($marker[0]);
var offset = $mydiv.html().indexOf($marker[0].outerHTML);
$marker.remove();
// Normalizing before and after ensures that the DOM is in the same shape before
// and after the insertion and removal of the marker.
$mydiv[0].normalize();
rangy.deserializeSelection(saved);
return offset;
}
As you can see, the code has to compensate for the addition and removal of the marker into the DOM because this causes the current selection to get lost:
Rangy is used to save the selection and restore it afterwards. Note that the save and restore could be done with something lighter than Rangy but I did not want to load the answer with minutia. If you decide to use Rangy for this task, please read the documentation because it is possible to optimize the serialization and deserialization.
For Rangy to work, the DOM must be in exactly the same state before and after the save. This is why normalize() is called before we add the marker and after we remove it. What this does is merge immediately adjacent text nodes into a single text node. The issue is that adding a marker to the DOM can cause a text node to be broken into two new text nodes. This causes the selection to be lost and, if not undone with a normalization, would cause Rangy to be unable to restore the selection. Again, something lighter than calling normalize could do the trick but I did not want to load the answer with minutia.
EDIT: This is an old answer that doesn't work for OP's requirement of having nodes with the same text. But it's cleaner and lighter if you don't have that requirement.
Here is one option that you can use and that works in all major browsers:
Get the offset of the caret within its node (document.getSelection().anchorOffset)
Get the text of the node in which the caret is located (document.getSelection().anchorNode.data)
Get the offset of that text within #mydiv by using indexOf()
Add the values obtained in 1 and 3, to get the offset of the caret within the div.
The code would look like this for your particular case:
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = $("#mydiv").html().indexOf( text );
offsetCaret = textOffset + offset;
You can see a working demo on this JSFiddle (view the console to see the results).
And a more generic version of the function (that allows to pass the div as a parameter, so it can be used with different contenteditable) on this other JSFiddle:
function getCaretHTMLOffset(obj) {
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = obj.innerHTML.indexOf( text );
return textOffset + offset;
}
About this answer
It will work in all recent browsers as requested (tested on Chrome 42, Firefox 37, and Explorer 11).
It is short and light, and doesn't require any external library (not even jQuery)
Issue: If you have different nodes with the same text, it may return the offset of the first occurrence instead of the real position of the caret.
NOTE: This solution works even in nodes with repeated text, but it detects html entities (e.g.: ) as only one character.
I came up with a completely different solution based on processing the nodes. It is not as clean as the old answer (see other answer), but it works fine even when there are nodes with the same text (OP's requirement).
This is a description of how it works:
Create a stack with all the parent elements of the node in which the caret is located.
While the stack is not empty, traverse the nodes of the containing element (initially the content editable div).
If the node is not the same one at the top of the stack, add its size to the offset.
If the node is the same as the one at the top of the stack: pop it from the stack, go to step 2.
The code is like this:
function getCaretOffset(contentEditableDiv) {
// read the node in which the caret is and store it in a stack
var aux = document.getSelection().anchorNode;
var stack = [ aux ];
// add the parents to the stack until we get to the content editable div
while ($(aux).parent()[0] != contentEditableDiv) { aux = $(aux).parent()[0]; stack.push(aux); }
// traverse the contents of the editable div until we reach the one with the caret
var offset = 0;
var currObj = contentEditableDiv;
var children = $(currObj).contents();
while (stack.length) {
// add the lengths of the previous "siblings" to the offset
for (var x = 0; x < children.length; x++) {
if (children[x] == stack[stack.length-1]) {
// if the node is not a text node, then add the size of the opening tag
if (children[x].nodeType != 3) { offset += $(children[x])[0].outerHTML.indexOf(">") + 1; }
break;
} else {
if (children[x].nodeType == 3) {
// if it's a text node, add it's size to the offset
offset += children[x].length;
} else {
// if it's a tag node, add it's size + the size of the tags
offset += $(children[x])[0].outerHTML.length;
}
}
}
// move to a more inner container
currObj = stack.pop();
children = $(currObj).contents();
}
// finally add the offset within the last node
offset += document.getSelection().anchorOffset;
return offset;
}
You can see a working demo on this JSFiddle.
About this answer:
It works in all major browsers.
It is light and doesn't require external libraries (apart from jQuery)
It has an issue: html entities like are counted as one character only.
I'm having a simple (I hope it's simple) question. I'm using 10 divs (div1, div2...) and I want to calculate the distance between two of them. I've already clicked one, that takes the class (img.home). I have the function to calculate the distance between two objects showdistance(div1, div2), but How can I use it to calculate showdistance($('img.home').parent()[0], $(this) ) where (this) is the div, that my cursor is point at at the moment ( .mouseenter(function() {$(this).text(showdistance)}); )
Thanks in advance,
If needed, I can make a jsfiddle with the whole code, but as I believe it is a rather easy thing, that I don't know and the code is a large piece it's easier this way.
Best regards.
Store your last clicked element in a variable.
Then compare the position when you enter an element.
You can apply top, left, right or bottom, if you like.
var clicked = null;
$("someSelection").click(function() {
clicked = $(this);
});
$("someSelection").mouseenter(function() {
// if-statement to ensure that the event won't fire if you haven't clicked one
if(clicked !== null) {
console.log($(this).position().top - clicked.position().top);
console.log($(this).position().left - clicked.position().left);
}
});
The following piece of code is used to check how many lines (clarify: lines of text) a target div can contain before overflowing (y-overflow).
It works by making a clone of the DOM element, emptying its contents, then appending <br> tags one at a time (to force new line), checking each time for overflow (by checking whether scrollHeight > clientHeight).
While debugging I noticed that it works in Chome and FF, but in IE9 the vertical scroll bar never appears no matter how many tags are inserted, and thus the loop never exits properly. Any idea how to make it work with IE?
http://jsfiddle.net/zbdbq/5/
function checkNumRows (obj){
var overflow = false, rows = 0;
var measureDiv = obj.clone().empty().css('overflow', 'auto'); //make a clone of the original
measureDiv.appendTo(obj.parent()); //append it to DOM
while (!overflow && rows < 500) {//set upper limit to 500 rows to prevent infinite looping for whatever reason
measureDiv.append('<br>');
overflow = measureDiv[0].scrollHeight > measureDiv[0].clientHeight ? true : false;
rows = rows + 1;
}
measureDiv.remove(); //cleanup
return rows - 1; //return max rows before overflow occured
}
alert(checkNumRows($("#testDiv"))); //IE incorrectly returns num rows as the max 499
This should work:
function checkNumRows (obj){
var measureDiv = obj.clone().empty().appendTo(obj.parent());
$span = $('<span>X</span>').appendTo(measureDiv);
rows = Math.floor(measureDiv.height() / $span.height());
measureDiv.remove();
return rows;
}
alert(checkNumRows($("#testDiv")));
http://jsfiddle.net/zbdbq/13/
maybe play around with $span.outerHeight() to get better results..
With minimal changes to your code try appending a bit of "content" when you add the items to your cloned element. I can't 100% recall, but I seem to remember that IE enjoys collapsing white space which can lead to some perceived funky behavior --
In your fiddle you call empty() (which empties the element) and then proceed to append some br elements which are empty themselves --
var measureDiv = obj.clone().empty().css('overflow', 'auto');
One option is to add some content to prevent this -- ie .append(' <br>') http://jsfiddle.net/zbdbq/9
I suppose in the same vein you could just make sure you don't have an empty element to begin with -- obj.clone().empty().html(' ')... http://jsfiddle.net/zbdbq/10/
If by "lines" you mean automatically sized inline elements, you could try checking based on the lineHeight CSS style.
function checkNumRows(obj) {
var line_height = parseInt(obj.css('lineHeight').replace('/em|px/', ''));
var max_rows = Math.floor(obj.height()/line_height);
return max_rows;
}
Since you're using jQuery by the looks of things you should just do this directly:
$("#myTable > tbody > tr").length
That function will only count the rows in the immediate table specified with "myTable" here. If you wish to account for nested tables too then you should use:
$('#myTable tr').length;
I am trying to create a simple text accordion which calculates each panel's height and return this value as a variable. I can get values with if statements like if ( i === 0 ) { $(this).height(); } but can't get this variable to the outside.I can do this without using variable but it became useless in long term.
Brıefly: I want to calculate each element's height and use this variable inside click function.
Here is jsFiddle which includes the problem.
var panel = $('.holder div');
var trigger = $('a');
panel.each(function(i) {
//problem starts when i try to calculate each ele's height
var eachEleHeight = i.height();
trigger.click(function() {
$(this).prev('div').animate({'height':eachEleHeight+'px'},500);
//this works widthout var eachEleHeight but became useless
//$(this).prev('div').animate({'height':'300px'},500);
});
//this is for hiding text at doc.ready
panel.css('height','56px');
});
If I well understood, try this :
panel.each(function(i, el) {
// create a reference to te current panel
var $el = $(el);
// execute a function immediately, passing both the panel and its height
(function(p, h) {
// the trigger is targeted as the next link after the current panel
p.next('a').click(function() {
p.animate({ height : h + 'px'},500);
});
}($el, $el.height()));
// set each panel height
$el.css('height','56px');
});
Example fiddle : http://jsfiddle.net/Aw39W/55/
There is a problem with the code you posted. i is the index of the current element. Not the element itself.
Try var eachEleHeight = $(this).height();
Fiddle
The sequence of events you've used seems a bit weird. You're actually binding a click event to all a tags for each panel element. Try this: Fiddle
I just used the min-height and max-height css attributes to store the initial to/from height variables. This will automatically get you your starting heights.
If I understand the question, you want to have the elements height for the accordion animation function. This is a trick but it works. I frequently use Mootools so I'll write using it. You should be able to figure it out.
var orgHeight = i.offsetHeight;
i.setStyle('height','100%');
var eachEleHeight = i.offsetHeight;
i.setStyle('height',orgHeight);
Essentially you would be flipping the element to reveal it's height and flipping it back before the document has time to make it visible on screen.
Not sure this is exactly what you were looking for, but hope it helps.