Получить выделенный текст и выбранные узлы на странице?
При выборе блока текста (возможно, охватывающего множество узлов DOM), возможно ли извлечь выделенный текст и узлы, используя Javascript?
Представьте себе этот HTML-код:
<h1>Hello World</h1><p>Hi <b>there!</b></p>
Если пользователь инициировал событие mouseDown, начинающееся с "World...", а затем mouseUp даже сразу после "there!", Я надеюсь, что оно вернется:
Text : { selectedText: "WorldHi there!" },
Nodes: [
{ node: "h1", offset: 6, length: 5 },
{ node: "p", offset: 0, length: 16 },
{ node: "p > b", offset: 0, length: 6 }
]
Я пытался поместить HTML-код в текстовую область, но это даст мне только выбранный текст. Я не пробовал <canvas>
элемент, но это может быть другой вариант.
Если не JavaScript, есть ли способ, с помощью которого возможно расширение Firefox?
6 ответов
Вас ждет ухабистая поездка, но это вполне возможно. Основная проблема заключается в том, что IE и W3C предоставляют совершенно разные интерфейсы для выбора, поэтому, если вы хотите использовать кросс-браузерную функциональность, вам придется написать все дважды. Кроме того, некоторые базовые функции отсутствуют в обоих интерфейсах.
У связи с разработчиками Mozilla есть история выбора W3C. У Microsoft их система документирована на MSDN. Я рекомендую начать с ознакомления PPK с диапазонами.
Вот некоторые основные функции, которые, я считаю, работают:
// selection objects will differ between browsers
function getSelection () {
return ( msie )
? document.selection
: ( window.getSelection || document.getSelection )();
}
// range objects will differ between browsers
function getRange () {
return ( msie )
? getSelection().createRange()
: getSelection().getRangeAt( 0 )
}
// abstract getting a parent container from a range
function parentContainer ( range ) {
return ( msie )
? range.parentElement()
: range.commonAncestorContainer;
}
Моя библиотека Rangy поможет вам в этом, объединив различные API в IE < 9 и всех других основных браузерах и предоставив getNodes()
функция на своих объектах Range:
function getSelectedNodes() {
var selectedNodes = [];
var sel = rangy.getSelection();
for (var i = 0; i < sel.rangeCount; ++i) {
selectedNodes = selectedNodes.concat( sel.getRangeAt(i).getNodes() );
}
return selectedNodes;
}
Получить выделенный текст довольно легко во всех браузерах. В Ранги это просто
var selectedText = rangy.getSelection().toString();
Без ранги
function getSelectedText() {
var sel, text = "";
if (window.getSelection) {
text = "" + window.getSelection();
} else if ( (sel = document.selection) && sel.type == "Text") {
text = sel.createRange().text;
}
return text;
}
Что касается смещения символов, вы можете сделать что-то подобное для любого узла node
в выборе. Обратите внимание, что это не обязательно представляет видимый текст в документе, потому что он не учитывает свернутые пробелы, текст, скрытый с помощью CSS, текст, расположенный вне нормального потока документов с помощью CSS, разрывы строк, подразумеваемые <br>
и блочные элементы, плюс другие тонкости.
var sel = rangy.getSelection();
var selRange = sel.getRangeAt(0);
var rangePrecedingNode = rangy.createRange();
rangePrecedingNode.setStart(selRange.startContainer, selRange.startOffset);
rangePrecedingNode.setEndBefore(node);
var startIndex = rangePrecedingNode.toString().length;
rangePrecedingNode.setEndAfter(node);
var endIndex = rangePrecedingNode.toString().length;
alert(startIndex + ", " + endIndex);
Это возвращает выбранные узлы, как я понимаю: когда у меня есть
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>...
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>
много узлов, и я выбираю только несколько, тогда я хочу, чтобы только эти узлы были в списке.
function getSelectedNodes() {
// from https://developer.mozilla.org/en-US/docs/Web/API/Selection
var selection = window.getSelection();
if (selection.isCollapsed) {
return [];
};
var node1 = selection.anchorNode;
var node2 = selection.focusNode;
var selectionAncestor = get_common_ancestor(node1, node2);
if (selectionAncestor == null) {
return [];
}
return getNodesBetween(selectionAncestor, node1, node2);
}
function get_common_ancestor(a, b)
{
// from http://stackru.com/questions/3960843/how-to-find-the-nearest-common-ancestors-of-two-or-more-nodes
$parentsa = $(a).parents();
$parentsb = $(b).parents();
var found = null;
$parentsa.each(function() {
var thisa = this;
$parentsb.each(function() {
if (thisa == this)
{
found = this;
return false;
}
});
if (found) return false;
});
return found;
}
function isDescendant(parent, child) {
// from http://stackru.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another
var node = child;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
function getNodesBetween(rootNode, node1, node2) {
var resultNodes = [];
var isBetweenNodes = false;
for (var i = 0; i < rootNode.childNodes.length; i+= 1) {
if (isDescendant(rootNode.childNodes[i], node1) || isDescendant(rootNode.childNodes[i], node2)) {
if (resultNodes.length == 0) {
isBetweenNodes = true;
} else {
isBetweenNodes = false;
}
resultNodes.push(rootNode.childNodes[i]);
} else if (resultNodes.length == 0) {
} else if (isBetweenNodes) {
resultNodes.push(rootNode.childNodes[i]);
} else {
return resultNodes;
}
};
if (resultNodes.length == 0) {
return [rootNode];
} else if (isDescendant(resultNodes[resultNodes.length - 1], node1) || isDescendant(resultNodes[resultNodes.length - 1], node2)) {
return resultNodes;
} else {
// same child node for both should never happen
return [resultNodes[0]];
}
}
Код должен быть доступен по адресу: https://github.com/niccokunzmann/spiele-mit-kindern/blob/gh-pages/javascripts/feedback.js
Я разместил этот ответ здесь, потому что я хотел бы найти его здесь.
Есть гораздо более короткий путь, если вы просто хотите диапазон.
function getRange(){
return (navigator.appName=="Microsoft Internet Explorer")
? document.selection.createRange().parentElement()
: (getSelection||document.getSelection)().getRangeAt(0).commonAncestorContainer
}
Весь совместимый со стандартами код, который работает в IE11+.
Текстовая строка
window.getSelection().getRangeAt(0).toString()
Начальный узел (даже если текст выделен задом наперед):
window.getSelection().anchorNode
Конечный узел (даже если текст выделен задом наперед):
window.getSelection().focusNode
Хочешь узнать больше? Выделите текст и запустите в консоли следующий JavaScript:
console.log(window.getSelection());
console.log(window.getSelection().getRangeAt(0));
Вы можете начать с http://javascript.internet.com/page-details/copy-selected-text.html