Якщо я, наприклад, маю
<p> some long text </p>
на моїй HTML-сторінці, як я можу знати, що курсор миші знаходиться, наприклад, над словом "текст"?
Якщо я, наприклад, маю
<p> some long text </p>
на моїй HTML-сторінці, як я можу знати, що курсор миші знаходиться, наприклад, над словом "текст"?
Відповіді:
На додаток до двох інших відповідей ви можете розділити свої абзаци на проміжки, використовуючи jQuery (або javascript загалом).
Таким чином, вам не потрібно було б думати про виведення тексту з інтервалами навколо слів. Нехай ваш javascript зробить це за вас.
напр
<p>Each word will be wrapped in a span.</p>
<p>A second paragraph here.</p>
Word: <span id="word"></span>
<script type="text/javascript">
$(function() {
// wrap words in spans
$('p').each(function() {
var $this = $(this);
$this.html($this.text().replace(/\b(\w+)\b/g, "<span>$1</span>"));
});
// bind to each span
$('p span').hover(
function() { $('#word').text($(this).css('background-color','#ffff66').text()); },
function() { $('#word').text(''); $(this).css('background-color',''); }
);
});
</script>
Зверніть увагу, що вищевказаний код, хоча і працює, буде видаляти будь-який html всередині тегів абзаців.
$(this).text().replace(/\b(\w+)\b/g, "<span>$1</span>")
замість циклу. Це буде правильно обробляти всі пробіли.
$('p')
селектор $('p,h1,h2,h3')
і так далі. Подібним чином, щоб навести курсор на роботу, потрібно змінити другий селектор на $('p span,h1 span,h2 span,h3 span')
.
Інша моя відповідь працює лише у Firefox. Ця відповідь працює в Chrome. (Можливо, це теж працює у Firefox, я не знаю.)
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);
}
Зателефонуйте у своєму обробнику миші getWordAtPoint(e.target, e.x, e.y);
range.endOffset
(і закінчується на range.endOffset + 1
). Отже, якщо умовою не є насправді while(currentPos < endPos)
останній символ, ніколи не буде перевірятися.
Якщо у вас є кілька інтервалів та вкладений HTML, що розділяє слова (або навіть символи у словах), тоді всі наведені вище рішення матимуть проблеми з поверненням повного та правильного слова.
Ось приклад з щедрот питання: Х</span>rт0съ
. Як правильно повернути Хrт0съ
? Ці проблеми не розглядались ще у 2010 році, тому я зараз буду представляти два рішення (2015).
Одним із рішень є видалення тегів span у абзацах, але збереження їх тексту. Таким чином, розділені слова та фрази поєднуються як звичайний текст. Кожне слово знаходить пробіл (а не просто пробіл), і ці слова загортаються в проміжки, до яких можна отримати індивідуальний доступ.
У демонстраційній частині ви можете виділити ціле слово і таким чином отримати текст цілого слова.
Код:
$(function() {
// Get the HTML in #hoverText - just a wrapper for convenience
var $hoverText = $("#hoverText");
// Replace all spans inside paragraphs with their text
$("p span", $hoverText).each(function() {
var $this = $(this);
var text = $this.text(); // get span content
$this.replaceWith(text); // replace all span with just content
});
// Wrap words in spans AND preserve the whitespace
$("p", $hoverText).each(function() {
var $this = $(this);
var newText = $this.text().replace(/([\s])([^\s]+)/g, "$1<span>$2</span>");
newText = newText.replace(/^([^\s]+)/g, "<span>$1</span>");
$this.empty().append(newText);
});
// Demo - bind hover to each span
$('#hoverText span').hover(
function() { $(this).css('background-color', '#ffff66'); },
function() { $(this).css('background-color', ''); }
);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="hoverText">
<p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со
стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span>
</p>
</div>
Ось більш складне рішення. Це алгоритмічне рішення з використанням обходу вузла, яке точно фіксує повне і правильне слово під курсором у текстовому вузлі.
Тимчасове слово можна знайти, перевіривши позицію каретки (використовуючи caretPositionFromPoint
або caretRangeFromPoint
, кредити за ідею @chrisv). Це все ще може бути повним словом, а може і не бути.
Потім його аналізують, щоб побачити, чи знаходиться він на кожному краю текстового вузла (початку чи кінця). Якщо це так, попередній текстовий вузол або наступний текстовий вузол перевіряється, щоб перевірити, чи слід приєднувати його, щоб зробити цей фрагмент слова довшим.
Приклад:
Х</span>rт0съ
повинен повернутися Хrт0съ
, а не Х
ні rт0съ
.
Дерево DOM обходить, щоб отримати наступний неперешкодний текстовий вузол. Якщо два фрагменти слів відокремлені <p>
або іншим бар'єрним тегом, то вони не суміжні і, отже, не є частиною одного слова.
Приклад:
њб.)</p><p>Во
не повинен повертатися њб.)Во
У демо-версії лівий плаваючий div - це слово під курсором. Правий плаваючий div, якщо видно, показує, як утворилося слово на межі. Інші теги можуть бути безпечно вбудовані в текст із цим рішенням.
Код:
$(function() {
// Get the HTML in #hoverText - just a wrapper for convenience
var $hoverText = $("#hoverText");
// Get the full word the cursor is over regardless of span breaks
function getFullWord(event) {
var i, begin, end, range, textNode, offset;
// Internet Explorer
if (document.body.createTextRange) {
try {
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
range = getTextRangeBoundaryPosition(range, true);
textNode = range.node;
offset = range.offset;
} catch(e) {
return ""; // Sigh, IE
}
}
// Firefox, Safari
// REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
else if (document.caretPositionFromPoint) {
range = document.caretPositionFromPoint(event.clientX, event.clientY);
textNode = range.offsetNode;
offset = range.offset;
// Chrome
// REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
} else if (document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(event.clientX, event.clientY);
textNode = range.startContainer;
offset = range.startOffset;
}
// Only act on text nodes
if (!textNode || textNode.nodeType !== Node.TEXT_NODE) {
return "";
}
var data = textNode.textContent;
// Sometimes the offset can be at the 'length' of the data.
// It might be a bug with this 'experimental' feature
// Compensate for this below
if (offset >= data.length) {
offset = data.length - 1;
}
// Ignore the cursor on spaces - these aren't words
if (isW(data[offset])) {
return "";
}
// Scan behind the current character until whitespace is found, or beginning
i = begin = end = offset;
while (i > 0 && !isW(data[i - 1])) {
i--;
}
begin = i;
// Scan ahead of the current character until whitespace is found, or end
i = offset;
while (i < data.length - 1 && !isW(data[i + 1])) {
i++;
}
end = i;
// This is our temporary word
var word = data.substring(begin, end + 1);
// Demo only
showBridge(null, null, null);
// If at a node boundary, cross over and see what
// the next word is and check if this should be added to our temp word
if (end === data.length - 1 || begin === 0) {
var nextNode = getNextNode(textNode);
var prevNode = getPrevNode(textNode);
// Get the next node text
if (end == data.length - 1 && nextNode) {
var nextText = nextNode.textContent;
// Demo only
showBridge(word, nextText, null);
// Add the letters from the next text block until a whitespace, or end
i = 0;
while (i < nextText.length && !isW(nextText[i])) {
word += nextText[i++];
}
} else if (begin === 0 && prevNode) {
// Get the previous node text
var prevText = prevNode.textContent;
// Demo only
showBridge(word, null, prevText);
// Add the letters from the next text block until a whitespace, or end
i = prevText.length - 1;
while (i >= 0 && !isW(prevText[i])) {
word = prevText[i--] + word;
}
}
}
return word;
}
// Return the word the cursor is over
$hoverText.mousemove(function(e) {
var word = getFullWord(e);
if (word !== "") {
$("#result").text(word);
}
});
});
// Helper functions
// Whitespace checker
function isW(s) {
return /[ \f\n\r\t\v\u00A0\u2028\u2029]/.test(s);
}
// Barrier nodes are BR, DIV, P, PRE, TD, TR, ...
function isBarrierNode(node) {
return node ? /^(BR|DIV|P|PRE|TD|TR|TABLE)$/i.test(node.nodeName) : true;
}
// Try to find the next adjacent node
function getNextNode(node) {
var n = null;
// Does this node have a sibling?
if (node.nextSibling) {
n = node.nextSibling;
// Doe this node's container have a sibling?
} else if (node.parentNode && node.parentNode.nextSibling) {
n = node.parentNode.nextSibling;
}
return isBarrierNode(n) ? null : n;
}
// Try to find the prev adjacent node
function getPrevNode(node) {
var n = null;
// Does this node have a sibling?
if (node.previousSibling) {
n = node.previousSibling;
// Doe this node's container have a sibling?
} else if (node.parentNode && node.parentNode.previousSibling) {
n = node.parentNode.previousSibling;
}
return isBarrierNode(n) ? null : n;
}
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getChildIndex(node) {
var i = 0;
while( (node = node.previousSibling) ) {
i++;
}
return i;
}
// All this code just to make this work with IE, OTL
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
var workingRange = textRange.duplicate();
workingRange.collapse(isStart);
var containerElement = workingRange.parentElement();
var workingNode = document.createElement("span");
var comparison, workingComparisonType = isStart ?
"StartToStart" : "StartToEnd";
var boundaryPosition, boundaryNode;
// Move the working range through the container's children, starting at
// the end and working backwards, until the working range reaches or goes
// past the boundary we're interested in
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);
// We've now reached or gone past the boundary of the text range we're
// interested in so have identified the node we want
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
// This must be a data node (text, comment, cdata) since we've overshot.
// The working range is collapsed at the start of the node containing
// the text range's boundary, so we move the end of the working range
// to the boundary point and measure the length of its text to get
// the boundary's offset within the node
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
// We've hit the boundary exactly, so this must be an element
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}
// Clean up
workingNode.parentNode.removeChild(workingNode);
return boundaryPosition;
}
// DEMO-ONLY code - this shows how the word is recombined across boundaries
function showBridge(word, nextText, prevText) {
if (nextText) {
$("#bridge").html("<span class=\"word\">" + word + "</span> | " + nextText.substring(0, 20) + "...").show();
} else if (prevText) {
$("#bridge").html("..." + prevText.substring(prevText.length - 20, prevText.length) + " | <span class=\"word\">" + word + "</span>").show();
} else {
$("#bridge").hide();
}
}
.kinovar { color:red; font-size:20px;}.slavic { color: blue;}#result {top:10px;left:10px;}#bridge { top:10px; right:80px;}.floater { position: fixed; background-color:white; border:2px solid black; padding:4px;}.word { color:blue;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div id="bridge" class="floater"></div> <div id="result" class="floater"></div> <div id="hoverText"><p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span></p><div class="slavic"> <input value="Works around other tags!"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p><p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span> </p><p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.<input value="Works around inline tags too"></span></p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span></p></div>
( Примітка: Я дозволив собі застосовувати стилі до тегів span, які були у вашому зразку HTML, щоб висвітлити, де знаходяться межі текстового вузла.)
(Поки що працював у Chrome та IE. Для IE метод IERange повинен був використовуватися як прохідник для сумісності між браузерами)
Наскільки мені відомо, ви не можете.
Єдине, що я можу придумати, це помістити кожне зі слів у свій власний елемент, а потім застосувати до цих елементів курсор миші.
<p><span>Some</span> <span>long</span> <span>text</span></p>
<script>
$(document).ready(function () {
$('p span').bind('mouseenter', function () {
alert($(this).html() + " is what you're currently hovering over!");
});
});
</script>
Ось просте рішення, яке працює в Chrome для більшості випадків:
function getWordAtPoint(x, y) {
var range = document.caretRangeFromPoint(x, y);
if (range.startContainer.nodeType === Node.TEXT_NODE) {
range.expand('word');
return range.toString().trim();
}
return null;
}
Я залишаю фільтрування пунктуації та належну обробку перенесених слів як вправу для читача :).
Існує API для цього в поточному проекті CSSOM View :document.caretPositionFromPoint(x,y)
Однак вам доведеться перевірити, який браузер це підтримує. Firefox 7, здається, не підтримує його взагалі, тоді як звіти про помилки вказують, що Firefox 9 буде. Chrome 14 підтримує те, caretRangeFromPoint(x,y)
що по суті те саме, але зі старішої версії CSSOM.
Ось рішення для щедрості.
Як запропонував chrisv, ви можете використовувати document.caretRangeFromPoint
(chrome) або document.caretPositionFromPoint
(Firefox). Я думаю, що це рішення краще відповість на ваше запитання, оскільки воно не змінює ваш текст або DOM.
З document.caretRangeFromPoint
документації :
Метод caretRangeFromPoint () інтерфейсу документа повертає об'єкт Range для фрагмента документа під заданими координатами.
З document.caretPositionFromPoint
документації :
Цей метод використовується для отримання позиції каретки в документі на основі двох координат. Повертається CaretPosition, що містить знайдений вузол DOM та зміщення символів у цьому вузлі.
Ці дві функції дещо відрізняються, але обидва вони повертають вузол, що містить текст, і зміщення курсора в цьому тексті. Тож легко отримати слово під мишею.
$(function () {
function getWordUnderCursor(event) {
var range, textNode, offset;
if (document.body.createTextRange) { // Internet Explorer
try {
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
range = getTextRangeBoundaryPosition(range, true);
textNode = range.node;
offset = range.offset;
} catch(e) {
return "";
}
}
else if (document.caretPositionFromPoint) { // Firefox
range = document.caretPositionFromPoint(event.clientX, event.clientY);
textNode = range.offsetNode;
offset = range.offset;
} else if (document.caretRangeFromPoint) { // Chrome
range = document.caretRangeFromPoint(event.clientX, event.clientY);
textNode = range.startContainer;
offset = range.startOffset;
}
//data contains a full sentence
//offset represent the cursor position in this sentence
var data = textNode.data,
i = offset,
begin,
end;
//Find the begin of the word (space)
while (i > 0 && data[i] !== " ") { --i; };
begin = i;
//Find the end of the word
i = offset;
while (i < data.length && data[i] !== " ") { ++i; };
end = i;
//Return the word under the mouse cursor
return data.substring(begin, end);
}
//Get the HTML in a div #hoverText and detect mouse move on it
var $hoverText = $("#hoverText");
$hoverText.mousemove(function (e) {
var word = getWordUnderCursor(e);
//Show the word in a div so we can test the result
if (word !== "")
$("#testResult").text(word);
});
});
// This code make it works with IE
// REF: /programming/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
var workingRange = textRange.duplicate();
workingRange.collapse(isStart);
var containerElement = workingRange.parentElement();
var workingNode = document.createElement("span");
var comparison, workingComparisonType = isStart ?
"StartToStart" : "StartToEnd";
var boundaryPosition, boundaryNode;
// Move the working range through the container's children, starting at
// the end and working backwards, until the working range reaches or goes
// past the boundary we're interested in
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);
// We've now reached or gone past the boundary of the text range we're
// interested in so have identified the node we want
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
// This must be a data node (text, comment, cdata) since we've overshot.
// The working range is collapsed at the start of the node containing
// the text range's boundary, so we move the end of the working range
// to the boundary point and measure the length of its text to get
// the boundary's offset within the node
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
// We've hit the boundary exactly, so this must be an element
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}
// Clean up
workingNode.parentNode.removeChild(workingNode);
return boundaryPosition;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<b><div id="testResult"></div></b>
<div id="hoverText"> <p><span class="kinovar"><span id="selection_index3337" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">со стіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p> <div class="slavic"><p><span id="selection_index3737" class="selection_index"></span>(л. рo7з њб.)</p> <p><span class="kinovar"><span id="selection_index3738" class="selection_index"></span>Во вт0рникъ вeчера</span></p> <p><span class="kinovar"><span id="selection_index3739" class="selection_index"></span>tдaніе прaздника пaсхи.</span></p><p><span class="kinovar"><span id="selection_index3740" class="selection_index"></span>По f7-мъ часЁ твори1тъ сщ7eнникъ начaло съ кади1ломъ и3 со свэщeю, цrкимъ двeремъ tвeрзєннымъ, и3 поeтъ: Х</span>rт0съ воскRсе: <span class="kinovar">состіхи2. И# по стісёхъ pал0мъ: Б</span>лгcви2 душE моS гDа: <span class="kinovar">И# є3ктеніA. Тaже каfjсма nбhчнаz.</span> </p><p><span class="kinovar"><span id="selection_index3741" class="selection_index"></span>На ГDи воззвaхъ: поeмъ стіхи6ры самоглaсны, слэпaгw, на ѕ7. Глaсъ в7:</span> </p><p><span class="kinovar"><span id="selection_index3742" class="selection_index"></span>С</span>лэпhй роди1выйсz, въ своeмъ п0мыслэ глаг0лаше: є3дA ѓзъ грBхъ рaди роди1тельныхъ роди1хсz без8 џчію; (л. рo7и) є3дA ѓзъ за невёріе kзhкwвъ роди1хсz во њбличeніе; не домышлsюсz вопрошaти: когдA н0щь, когдA дeнь; не терпи1та ми2 н0зэ кaменнагw претыкaніz, не ви1дэхъ сlнца сіsюща, нижE во џбразэ менE создaвшагw. но молю1 ти сz хrтE б9е, при1зри на мS, и3 поми1луй мS.</p></div></div>
Ах ти! Ось хо!
Як це просто, без жодної Jquery чи будь-якої іншої фреймворкової скрипки: https://jsfiddle.net/703c96dr/
Він помістить прольоти на кожне слово та додасть функцію onmouseover та onomouseout. Я міг би створити простий клас, щоб зробити його більш корисним, але код настільки простий, що кожен може редагувати та використовувати.
<p>This is my text example of word highlighting or, if you want, word hovering</p>
<p>This is another text example of word highlighting or, if you want, word hovering</p>
Простий код
function onmouseoverspan(){
this.style.backgroundColor = "red";
}
function onmouseoutspan(){
this.style.backgroundColor = "transparent";
}
var spans,p = document.getElementsByTagName("p");
for(var i=0;i<p.length;i++) {
if(p[i]==undefined) continue;
p[i].innerHTML = p[i].innerHTML.replace(/\b(\w+)\b/g, "<span>$1</span>");
spans = p[i].getElementsByTagName("span")
for(var a=0;a<spans.length;a++) {
spans[a].onmouseover = onmouseoverspan;
spans[a].onmouseout = onmouseoutspan;
}
}
Можливо, вам доведеться розбити абзац так, щоб кожне слово містилося всередині свого окремого елемента <span>, а потім додавати onmouseover
атрибути подій до кожного з них.
..І я думаю, ви маєте на увазі "<p> якийсь довгий текст </p>"; зворотні скісні риски не є частиною HTML.
У Firefox ви можете підключити подію mousemove. Зворотний виклик має один аргумент, напр. У зворотному дзвінку зробіть наступне:
var range = HTTparent.ownerDocument.createRange();
range.selectNode(e.rangeParent);
var str = range.toString();
range.detach();
Тепер str має весь текст, над яким миша закінчилася. e.rangeOffset - це розташування вказівника миші всередині цього рядка. У вашому випадку str буде "довгим текстом", а e.rangeOffset буде 11, якщо ви перевищите "e" у "text".
Цей код трохи заплутається, якщо ви знаходитесь на полях, наприклад, коли вказівник миші знаходиться в тому самому рядку, що і текст, але після його закінчення. Щоб це виправити, потрібно перевірити, чи справді ви перебуваєте поверх тексту. Ось тест:
if(e && e.rangeParent && e.rangeParent.nodeType == e.rangeParent.TEXT_NODE
&& e.rangeParent.parentNode == e.target)
Ця техніка працює у Firefox. Не працює в Chrome.
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getChildIndex(node) {
var i = 0;
while( (node = node.previousSibling) ) {
i++;
}
return i;
}
// All this code just to make this work with IE, OTL
// REF: http://stackoverflow.com/questions/3127369/how-to-get-selected-textnode-in-contenteditable-div-in-ie
function getTextRangeBoundaryPosition(textRange, isStart) {
var workingRange = textRange.duplicate();
workingRange.collapse(isStart);
var containerElement = workingRange.parentElement();
var workingNode = document.createElement("span");
var comparison, workingComparisonType = isStart ?
"StartToStart" : "StartToEnd";
var boundaryPosition, boundaryNode;
// Move the working range through the container's children, starting at
// the end and working backwards, until the working range reaches or goes
// past the boundary we're interested in
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);
// We've now reached or gone past the boundary of the text range we're
// interested in so have identified the node we want
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
// This must be a data node (text, comment, cdata) since we've overshot.
// The working range is collapsed at the start of the node containing
// the text range's boundary, so we move the end of the working range
// to the boundary point and measure the length of its text to get
// the boundary's offset within the node
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
// We've hit the boundary exactly, so this must be an element
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}
// Clean up
workingNode.parentNode.removeChild(workingNode);
return boundaryPosition;
}
function onClick(event) {
var elt = document.getElementById('info');
elt.innerHTML = "";
var textNode;
var offset;
// Internet Explorer
if (document.body.createTextRange) {
elt.innerHTML = elt.innerHTML+("*************** IE **************<br/>");
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
range = getTextRangeBoundaryPosition(range, true);
textNode = range.node;
offset = range.offset;
elt.innerHTML = elt.innerHTML + "IE ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
// Internet Explorer method 2
if (document.body.createTextRange) {
elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>");
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
var sel = document.getSelection();
textNode = sel.anchorNode;
offset = sel.anchorOffset;
elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
// Firefox, Safari
// REF: https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint
if (document.caretPositionFromPoint) {
elt.innerHTML = elt.innerHTML+("*************** Firefox, Safari **************<br/>");
range = document.caretPositionFromPoint(event.clientX, event.clientY);
textNode = range.offsetNode;
offset = range.offset;
elt.innerHTML = elt.innerHTML + "caretPositionFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
// Chrome
// REF: https://developer.mozilla.org/en-US/docs/Web/API/document/caretRangeFromPoint
}
if (document.caretRangeFromPoint) {
elt.innerHTML = elt.innerHTML+("*************** Chrome **************<br/>");
range = document.caretRangeFromPoint(event.clientX, event.clientY);
textNode = range.startContainer;
offset = range.startOffset;
elt.innerHTML = elt.innerHTML + "caretRangeFromPoint ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}
}
document.addEventListener('click', onClick);
#info {
position: absolute;
bottom: 0;
background-color: cyan;
}
<div class="parent">
<div class="child">SPACE SPACE Bacon ipsum dolor amet <span>SPAN SPANTT SPOOR</span> meatball bresaola t-bone tri-tip brisket. Jowl pig picanha cupim SPAXE landjaeger, frankfurter spare ribs chicken. Porchetta jowl pancetta drumstick shankle cow spare ribs jerky
tail kevin biltong capicola brisket venison bresaola. Flank sirloin jowl andouille meatball venison salami ground round rump boudin turkey capicola t-bone. Sirloin filet mignon tenderloin beef, biltong doner bresaola brisket shoulder pork loin shankle
turducken shank cow. Bacon ball tip sirloin ham.
</div>
<div id="info">Click somewhere in the paragraph above</div>
</div>
Моя відповідь випливає з Дрейка "Рішення 2 - Інспекція Каре і обхід DOM". Дякуємо Дрейку за вказівку на це рішення!
Однак є дві проблеми з рішенням Дрейкса 2 під час роботи над IE. (1) зсув, як обчислюється, неправильний, і (2) занадто складний, багато коду.
Подивіться мою демонстрацію на JSFiddle тут .
Для задачі 1, якщо ви клацнете десь приблизно в останньому рядку тексту, наприклад, десь у "плечі свиняча корейка шерстяний корінь коров’ячий корінь. Бекон куля нарізка філе шинка.", Ви можете помітити, що розрахунок зсуву відрізняється від IE (оригінал рішення) та IE метод 2 (моє рішення). Крім того, результати методу IE 2 (моє рішення) та Chrome, Firefox однакові.
Моє рішення також набагато простіше. Фокус полягає в тому, що після використання TextRange, щоб зробити вибір у абсолютній позиції X / Y, отримати тип IHTMLSelection, викликавши document.getSelection (). Це не працює для IE <9, але якщо для вас це нормально, цей метод набагато простіший. Іншим застереженням є те, що при IE побічним ефектом методу (таким же, як оригінальний метод) є зміна виділення (тобто втрата оригінального вибору користувача).
// Internet Explorer method 2
if (document.body.createTextRange) {
elt.innerHTML = elt.innerHTML+("*************** IE, Method 2 **************<br/>");
range = document.body.createTextRange();
range.moveToPoint(event.clientX, event.clientY);
range.select();
var sel = document.getSelection();
textNode = sel.anchorNode;
offset = sel.anchorOffset;
elt.innerHTML = elt.innerHTML + "IE M2 ok, result: [" + escapeHtml(textNode.nodeName) + "]/[" + escapeHtml(textNode.textContent) + "] @" + offset + "</br>";
}