Get DOM text node from point? - javascript

Just like I can get an element from a point with document.elementFromPoint or document.getElementFromPoint, is it possible to somehow get a text node if the point is at a text node? I guess if at least I could get the text node's position and size I could then figure out which of them contains the point. But then DOM nodes don't have position properties. Is it possible to do this at all?

Here is an implementation that works in all current browsers:
https://github.com/nuxodin/q1/blob/master/q1.dom.js
document.betaNodeFromPoint = function(x, y){
var el = document.elementFromPoint(x, y);
var nodes = el.childNodes;
for ( var i = 0, n; n = nodes[i++];) {
if (n.nodeType === 3) {
var r = document.createRange();
r.selectNode(n);
var rects = r.getClientRects();
for ( var j = 0, rect; rect = rects[j++];) {
if (x > rect.left && x < rect.right && y > rect.top && y < rect.bottom) {
return n;
}
}
}
}
return el;
};

For Firefox, you should use document.caretPositionFromPoint
Here's a greap demo: https://developer.mozilla.org/en-US/docs/Web/API/document.caretPositionFromPoint
For Chrome and Edge, try document.caretRangeFromPoint(x,y)

You can use element.nodeName to see if it's a text node, and then element.nodeValue for its value.

Considering this document (fiddle):
<html>
<body>
some text here
<p id="para1">lalala</p>
bla bla
</body>
</html>​
And this code:
$(document).on('click', function(evt) {
var elem = document.elementFromPoint(evt.clientX, evt.clientY);
console.log(elem);
});
When you click anywhere inside the <p> tag, the tag element itself is logged. However, when the surrounding text is clicked, the <body> is returned because text fragments are not considered elements.
Conclusion
It's not possible to accomplish what you want with elementFromPoint() and because text fragments don't receive click events, I don't think it's possible at all.

Related

How can I check if my mouse x position is in my image?

I have an onmouseenter event, and in that I get an array of images via the jquery selector $('.imageTile'). In Chrome I was able to do this to get the index of the element I was really interested in:
for(var i = 0; i < $('.imageTile').length; i++)
{
if(e.y >= $('.imageTile')[i].y && e.y <= $('.imageTile')[i].y + $('.imageTile')[i].height) {
index = i;
break;
}
}
but in IE, this isn't working. Is there some weird hack I need to use?
I think this will work, but I haven't tested it.
var index = -1;
$(function(){
$('.imageTile img').hover(function(){
index = $('.imageTile').index($(this));
},
function(){
index = -1; // reset index on mouseout
});
});
You got to love jQuery, https://api.jquery.com/index/

Index of Child DOM element within Parent

I'm looking for a pure JS way of finding the offset of a child within it's parent.
Given the below sample:
<div>
A
<br>
short space elapsed, <b>and</b> up into this noiselessness came Ahab alone from his cabin.
<span>Taking a few turns on the quarter-deck, he paused to gaze over the side</span>
</div>
I would get 3 children, a br, b, and span. Each would need to have an offset to the start of the div - So the index of how many characters into the div the start of the tag is.
So the br would have an offset of 2.
My initial idea was to get all the children of the div, then somehow from that be able to easily get an index.
function getChildrenOffset(parent){
var childNodes = parent.childNodes;
var childrenLocations = [];
var offset = 0;
var tagIndex = 0;
for(var d = 0; d < childNodes.length; d++){
var node = childNodes[d];
if(node.tagName !== undefined){
// This is a tag
tagIndex += 1;
var curLocation = new OffsetData(offset, tagIndex, node.tagName);
childrenLocations.push(curLocation);
offset += node.outerHTML.length;
}else{
// Just text
offset += node.length;
}
}
return childrenLocations;
}
function OffsetData(offset, index, tag){
this.Offset = offset;
this.Index = index;
this.TagName = tag;
}

Detecting Word under the cursor

How best to detect the word under the cursor using JavaScript?
I know that this question has been asked before, but the solutions that I have seen do not work in all scenarios.
I have isolated some locations where the code does not work and placed it in a JSFiddle container (http://jsfiddle.net/ohaf4ytL/). Follow along in console, and you can see that "Блгcви2 душE моS" only lists "Блгcви2" and not "душE" and "моS" when hovered over.
The current code in use is taken from How to get a word under cursor using JavaScript? answer by Eyal. It is not an accepted answer, but there's not a large choice of options. Adding spans is a hack and also does not handle cases like Abc. The range.expand('word') feature cuts off when there are characters such as ) inside a word (and my text has this), but I think that's not the only issue.
function getWordAtPoint(elem, x, y) {
if(elem.nodeType == elem.TEXT_NODE) {
var range = elem.ownerDocument.createRange();
range.selectNodeContents(elem);
var currentPos = 0;
var endPos = range.endOffset;
while(currentPos+1 < endPos) {
range.setStart(elem, currentPos);
range.setEnd(elem, currentPos+1);
if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right >= x &&
range.getBoundingClientRect().top <= y && range.getBoundingClientRect().bottom >= y) {
range.expand("word");
var ret = range.toString();
range.detach();
return(ret);
}
currentPos += 1;
}
} else {
for(var i = 0; i < elem.childNodes.length; i++) {
var range = elem.childNodes[i].ownerDocument.createRange();
range.selectNodeContents(elem.childNodes[i]);
if(range.getBoundingClientRect().left <= x && range.getBoundingClientRect().right >= x &&
range.getBoundingClientRect().top <= y && range.getBoundingClientRect().bottom >= y) {
range.detach();
return(getWordAtPoint(elem.childNodes[i], x, y));
} else {
range.detach();
}
}
}
return(null);
}
A way of accomplishing what you want would be to split the sentence into html elements and then listening to a hover event. For example, using jQuery:
HTML:
<div id="text"> Блгcви2 душE Блгcви2 This is a test</div>
JavaScript:
var div = $("#text");
div.html(div.text().replace(/(\S+)/g, "<span>$1</span>"));
$("#text span").hover(function(event){
console.log($(this).text());
});
I looked for anything that's not a space with the regex \S and the HTML is now updated to:
<div id="text">
<span>Блгcви2</span>
<span>душE</span>
<span>Блгcви2</span>
<span>This</span>
<span>is</span>
<span>a</span>
<span>test</span>
</div>
When you hover over a word, the word will printed out in the console of your browser.

Convert all SVG text nodes into path nodes with Raphael JS

I'm attempting to write a RaphaelJS function that will take existing text nodes within a Raphael paper instance and convert them into paths.
The goal is to replicate the position, size and attribute of the text exactly as it appears on the page, but have it rendered using paths instead of text. I cannot initially render the text using the Raphael paper.print() function because the text is updated dynamically and requires "text" based attributes to do so. Converting existing text nodes to paths will occur as the "final" step in the process (after the text modifications are complete).
I am doing this to eliminate the need for having fonts installed to view or handle the SVG later.
The challenges I face are:
Text nodes may include tspans with x and dy definitions. The paths created must line it perfectly witch each of the childNode letters (tspans).
Retrieving the actual position data of text node, and each tspan. This is where I'm having trouble and hopefully someone with more experience can assist me. Since stroke widths and other attributes affect the positioning/bbox values, I'm not sure what's the most efficient method of obtaining the correct positioning data for the text.
What I have tried so far:
A simple breakdown of my code.
I wrote a custom attribute function, textFormat, that formats the text in a staggered formation. This function parses the text node, splits it by each letter adding a new line \n character, and adjusts the positioning to look staggered.
The textToPaths function is a paper function that is supposed to loop through the paper nodes, and convert all found text nodes into path using the Raphael paper.print() function. This is the function I am having trouble with.
View the Complete JSFiddle Example Here
The problem code
I'm not sure how to obtain accurate and consistent x and y values to pass into the paper.print() function. Right now, I am using getBoundingClientRect() but it's still off and skewed. My assumption is the stroke widths are affecting the x and y calculations.
//Loop through each tspan and print the path for each.
var i,
children = node.node.childNodes,
len = children.length;
for (i = 0; i < len; i++) {
var tspan = children[i],
tspanText = tspan.innerHTML,
x = tspan.getBoundingClientRect().left - node.node.getBoundingClientRect().left, //How do I get the correct x value?
y = tspan.getBoundingClientRect().top - node.node.getBoundingClientRect().top; //How do I get the correcy y value?
var path = paper.print(x, y, tspanText, font, fontSize),
attrs = node.attrs;
delete attrs.x;
delete attrs.y;
path.attr(attrs);
path.attr('fill', '#ff0000'); //Red, for testing purposes.
}
Complete Code View the JSFiddle Example
//Register Cufon Font
var paper = Raphael(document.getElementById('paper'), '600', '600');
var text1 = paper.text(100, 100, 'abc').attr({fill: 'none',stroke: '#000000',"stroke-width": '12',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text2 = paper.text(100, 100, 'abc').attr({fill: 'none',stroke: '#ffffff',"stroke-width": '8',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text3 = paper.text(100, 100, 'abc').attr({fill: '#000000',stroke: '#ffffff',"stroke-width": '0',"stroke-miterlimit": '1',"font-family" : "Lobster", "font-size": '30px','stroke-opacity': '1'});
var text = paper.set(text1, text2, text3);
text.attr('textFormat', 'stagger');
/* paper.textToPaths
* Description: Converts all text nodes to paths within a paper.
*
* Example: paper.textToPaths();
*/
(function(R) {
R.fn.textToPaths = function() {
var paper = this;
//Loop all nodes in the paper.
for (var node = paper.bottom; node != null; node = node.next ) {
if ( node.node.style.display === 'none' || node.type !== "text" || node.attrs.opacity == "0") continue; //skip non-text and hidden nodes.
//Get the font config for this text node.
var text = node.attr('text'),
fontFamily = node.attr('font-family'),
fontSize = parseInt(node.attr('font-size')),
fontWeight = node.attr('font-weight'),
font = paper.getFont(fontFamily, fontWeight);
//Loop through each tspan and print the path for each.
var i,
children = node.node.childNodes,
len = children.length;
for (i = 0; i < len; i++) {
var tspan = children[i],
tspanText = tspan.innerHTML,
x = tspan.getBoundingClientRect().left - node.node.getBoundingClientRect().left, //How do I get the correct x value?
y = tspan.getBoundingClientRect().top - node.node.getBoundingClientRect().top; //How do I get the correcy y value?
var path = paper.print(x, y, tspanText, font, fontSize),
attrs = node.attrs;
delete attrs.x;
delete attrs.y;
path.attr(attrs);
path.attr('fill', '#ff0000'); //Red, for testing purposes.
}
}
};
})(window.Raphael);
textToPaths = function() {
//Run textToPaths
paper.textToPaths();
};
/* Custom Element Attribute: textFormat
* Description: Formats a text element to either staggered or normal text.
*
* Example: element.attr('textFormat, 'stagger');
*/
paper.customAttributes.textFormat = function( value ) {
// Sets the SVG dy attribute, which Raphael doesn't control
var selector = Raphael.svg ? 'tspan' : 'v:textpath',
has = "hasOwnProperty",
$node = $(this.node),
text = $node.text(),
$tspans = $node.find(selector);
console.log('format');
switch(value)
{
case 'stagger' :
var stagger = function(el) {
var R = Raphael,
letters = '',
newline = '\n';
for (var c=0; c < text.length; c++) {
var letter = text[c],
append = '';
if(c < text.length - 1)
append = newline;
letters += letter+append;
}
el.attr('text', letters);
var children = el.node.childNodes;
var i,
a = el.attrs,
node = el.node,
len = children.length,
letterOffset = 0,
tspan,
tspanHeight,
tspanWidth,
tspanX,
prevTspan,
prevTspanRight = 0,
tspanDiff = 0,
tspanTemp,
fontSize,
leading = 1.2,
tempText;
for (i = 0; i < len; i++) {
tspan = children[i];
tspanHeight = tspan.getComputedTextLength();
tspanWidth = tspan.getComputedTextLength();
tspanX = tspan.getAttribute('x'),
prevTspanRight = tspan.getBoundingClientRect().right
if(tspanX !== null)
{
tspanDiff = tspanDiff + prevTspanRight - tspan.getBoundingClientRect().left;
var setX = parseInt(tspanX) + parseInt(tspanDiff);
tspan.setAttribute('x', setX);
tspan.setAttribute('dy', 15);
}
prevTspan = tspan;
}
}
stagger(this);
break;
case 'normal' :
this.attr('text', text);
break;
default :
this.attr('text', text);
break;
}
eve("raphael.attr.textFormat." + this.id, this, value);
// change no default Raphael attributes
return {};
};
staggerText = function() {
//Run textToPaths
text.attr('textFormat', 'stagger');
};
If anyone can help me solve this problem I would greatly appreciate it. Thanks!
You can convert fonts to SVG/Canvas path commands using Opentype.js.
The lib will return to you a series of path drawing commands; these are intended for drawing on an HTML5 <canvas> element.
However it is trivial to build an SVG path with those commands since the font-conversion does not include any commands that are compatible with Canvas path drawing that would be incompatible with an SVG path command.

Have to write full path when checking background image javascript

if ($("#canvas").css('background-image') == 'url(images/endOfGame.jpg)') {
does not work. But this does:
var element = document.getElementById('canvas');
var style = window.getComputedStyle(element);
var imagex = style.getPropertyValue('background-image');
console.log(imagex);
if (imagex === "url(file:///C:/Users/Jack/Documents/myGames/Pong/images/endOfGame.jpg)") {
and this does not:
var element = document.getElementById('canvas');
var style = window.getComputedStyle(element);
var imagex = style.getPropertyValue('background-image');
console.log(imagex);
if (imagex === "url(images/endOfGame.jpg)") {
why? I have to change the full file path code for every computer i run my game on. Not good.
Thanks.
You could use indexOf which returns the character position of the found text (0 and above) or -1 if not found:
if (imagex.indexOf("url(images/endOfGame.jpg)") >= 0) {
// yes, string contains that text
}
I would prefer:
if (imagex.indexOf("images/endOfGame.jpg") >= 0) {
// yes, string contains that text
}
ignoring url(..). The following version ignores differences in case (upper or lower):
if (imagex.toUpperCase().indexOf("images/endOfGame.jpg".toUpperCase()) >= 0) {
// yes, string contains that text
}

Categories

Resources