Detecting Word under the cursor - javascript

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.

Related

Collision detection of each side not working

I've been recently working on a 2D platformer that has multiple characters you can change between. I wanted to implement collision detection that cycles through each character and depending on which side they're touching stop it from moving. Basically so the characters could jump on top of one another and couldn't pass through each other. I used an AABB Intersect collision detection system and then to determine which side of the character I've hit I've used this code:
for (var a = 0; a < this.characters.length; a++) {
for (var b = 0; b < this.characters.length; b++) {
if (a !== b) {
if (this.characters[a].collision(this.characters[b])) {
var ab = this.characters[a].y + this.characters[a].height,
ar = this.characters[a].x + this.characters[a].width,
bb = this.characters[b].y + this.characters[b].height,
br = this.characters[b].x + this.characters[b].width;
var tc = ab - this.characters[b].y,
bc = bb - this.characters[a].y,
lc = ar - this.characters[b].x,
rc = br - this.characters[a].x;
// Bottom is touching something
if (tc < bc && tc < lc && tc < rc) {
this.characters[a].isJumping = false;
this.characters[a].vel.y = 0;
this.characters[a].y = this.characters[b].y - this.characters[a].height;
}
// Top is touching something
if (bc < tc && bc < lc && bc < rc) {
this.characters[a].isJumping = false;
this.characters[a].y = this.characters[b].y + this.characters[b].height;
}
// Right side is touching something
if (lc < rc && lc < tc && lc < bc) {
this.characters[a].x = this.characters[b].x - this.characters[a].width;
}
// Left side is touching something
if (rc < lc && rc < tc && rc < bc) {
this.characters[a].x = this.characters[b].x + this.characters[b].width;
}
}
}
}
}
It appears to work fine for the first character (block; I didn't mention this, but, all of the characters are squares) but this system glitches if the I attempt to use the second character (the characters are in an array) and test the collision on the first character. Things such as the jumping on top of not working and the walking left into the character causing the first character to move left (I know why that's happening, but, I'm not sure how to fix it). The third character doesn't work for the first two and then by the fourth it's complete chaos. Does anyone have any suggestions?
A single collision will cause two hits in your loop, first when a = 0 and the collided object b = 1 (for example), and then when a = 1 and b = 0, the same collision will be detected again.
I think it's better to have an array of inactive characters that you check against one active character.
You can create those variables at the moment the player switches characters. This way you only need a single loop and you don't need the if a != b check
This code illustrates the idea:
function characterChange(){
activeCharacter = objA;
inActiveCharacters = [objB, objC, objD];
}
function checkCollisions(){
for(let i = 0; i<inActiveCharacters.length; i++){
checkHit(activeCharacter, inActiveCharacters[i]);
}
}
function checkHit(a,b) {
// your a b check here
}

How can I make sure that when my ellipsis gets added lastindexof space it also checks its alphabet before it gets added and not special chars

The code looks like this:
function TrimLength(text, maxLength) {
text = $.trim(text);
if (text.length > maxLength) {
text = text.substring(0, maxLength - ellipsis.length)
return text.substring(0, text.lastIndexOf(" ")) + ellipsis;
}
else
return text;
}
The problem I have is that it does the following thing:
hello world and an...
The curse of the gaming backlog –...
I want to make sure that it instead does:
hello world and...
The curse of the gaming backlog...
I guess that I need to make sure that there is alphabet char like (a,b,c,d etc..) and no special characters.
Any kind of help is appreciated
You might want to start with this:
function cutoff(str, maxLen) {
// no need to cut off
if(str.length <= maxLen) {
return str;
}
// find the cutoff point
var oldPos = pos = 0;
while(pos!==-1 && pos <= maxLen) {
oldPos = pos;
pos = str.indexOf(" ",pos) + 1;
}
if (pos>maxLen) { pos = oldPos; }
// return cut off string with ellipsis
return str.substring(0,pos) + "...";
}
which at least gives you cutoffs based on words, rather than letters. If you need additional filtering, you can add it, but this will give you a cutoff such as "The curse of the gaming backlog – ..." which doesn't look wrong, honestly.

Keypress event for Japanese text

$(document).ready(function () {
$("#id").keydown(function () {
});
})
This code perfectly works for everything(number,alphabet,symbol etc) except Japanese text. On key press it doesn't pass through this event. Does anybody know any solution?
There's hardly anything you can do. "Japanese text" means an IME, which is a piece of software intercepting the keyboard input and helping you turn it into Japanese text. How this software interacts or doesn't interact with the browser and the browser's Javascript engine depends on the OS, IME, browser and the browser's Javascript engine. On some platforms the keypress is signaled through, in others it isn't. You can try binding to other events like keyup or keypress, some may be signaled even when using an IME.
The best you can do is to make sure you're not depending on keypress events and have fallback options if you can't intercept them; e.g. bind to change events on the text field as well and handle entire text changes, which will be triggered at the end of the IME input.
I got the same issue, instead of prevent user input the text i set the input value to null because the api event.preventDefault(); is not working correctly.
$(document).ready(function () {
$("#id").keyup(function () {
this.value = ''
});
})
I had the same issue and I solved it using the input event.
//calculate the length of a character
function getLen(str){
var result = 0;
for(var i=0;i<str.length;i++){
var chr = str.charCodeAt(i);
if((chr >= 0x00 && chr < 0x81) ||
(chr === 0xf8f0) ||
(chr >= 0xff61 && chr < 0xffa0) ||
(chr >= 0xf8f1 && chr < 0xf8f4)){
//half width counted as 1
result += 1;
}else{
//full width counted as 2
result += 2;
}
}
return result;
};
// trim the string by processing character by character
function getTrimmedString(theString, maxLength){
var tempLength = 0;
var trimmedString = "";
for (var i = 0; i < theString.length; i++) {
tempLength = getLen(theString.charAt(i)) + tempLength;
if(tempLength > maxLength){
break;
}else{
trimmedString = trimmedString + theString.charAt(i);
}
}
return trimmedString;
}
// limit the size of a field
function limitCity(){
var maxChars = 30;
var cityVal = $("#city").val();
var cityLength = getLen(cityVal);
if (cityLength >= maxChars) {
var trimmedString = getTrimmedString(cityVal, maxChars);
$("#city").val(trimmedString);
}
}
//bind the input event
$("#city").bind("input", limitCity);

Get DOM text node from point?

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.

Select spans accross multiple divs issue

I'm having the following issue - I'm trying to select the text inside spans located across multiple divs. To give an example
<div>asd<span>fgh</span></div>
<div><span>qwerty</span></div>
<div><span>uio</span>asd</div>
Now in this scenario, if the user clicks somewhere inside the word qwerty I'd like to select the text 'fghqwertuio' --> all the adjacent spans. I'm using the following code to do this:
var range = document.caretRangeFromPoint(lastTappedX, lastTappedY);
range.selectNodeContents(range.startContainer);
window.getSelection().addRange(range);
var containerNodes = document.body.children[0].children;
var whichChild = -1;
for ( var i = 0; i < containerNodes.length; ++i) {
if (containerNodes[i] === range.startContainer.parentNode.parentNode) {
whichChild = i;
break;
}
}
if (whichChild === -1) {
console.log("couldn't find the highlighted div");
}
// go right the dom tree
for ( var i = whichChild + 1; i < containerNodes.length; ++i) {
var containerChildren = containerNodes[i].children;
if (containerChildren[0]
&& containerChildren[0].style['background-color']) {
var newRange = document.createRange();
newRange.selectNodeContents(containerChildren[0]);
window.getSelection().addRange(newRange);
}
if (containerChildren.length > 1) {
break;
}
}
// go left the down tree
for ( var i = whichChild - 1; i >= 0; --i) {
var containerChildren = containerNodes[i].children;
if (containerChildren[containerChildren.length - 1].style['background-color']) {
var newRange = document.createRange();
newRange
.selectNodeContents(containerChildren[containerChildren.length - 1]);
window.getSelection().addRange(newRange);
}
if (containerChildren.length > 1) {
break;
}
}
When I log what happens - I'm correctly creating ranges containing the text I'd like to select but adding them to the selection object doesn't seem to work. The current selection is only the first added range. Any help on how to solve this will be greatly appreciated.
Of the major browsers, only Firefox allows multiple ranges per selection. In all other browsers you're limited to one range.
You need to tweak your code to create one range and use the range's setStart() and setEnd() methods. Also, properties of the style property of elements use camel case rather than hyphens (i.e. .backgroundColor rather than ['background-color']).

Categories

Resources