2008-12-11 10 views
7

При выборе блока текста (возможно, охватывающего многие узлы DOM) можно ли извлечь выделенный текст и узлы с помощью Javascript?Получить выделенный текст и выбранные узлы на странице?

Представьте этот HTML код:

<h1>Hello World</h1><p>Hi <b>there!</b></p> 

Если пользователь инициировал событие MouseDown, начиная с «Мир ...», а затем MouseUp даже сразу после того, как «там!», Я надеюсь, что он вернется :

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?

ответ

12

Вы находитесь в тряской езды, но это вполне возможно. Основная проблема заключается в том, что IE и W3C выставляют совершенно разные интерфейсы для выбора, поэтому, если вы хотите использовать функцию перекрестного браузера, вам в основном нужно написать все это дважды. Кроме того, некоторые базовые функции отсутствуют на обоих интерфейсах.

Связь с разработчиком Mozilla имеет историю на W3C selections. Microsoft имеет свою систему documented on MSDN. Я рекомендую начинать с PPK introduction to ranges.

Вот некоторые основные функции, которые я считаю работу:

// 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; 
} 
+0

что вы пройти parentContainer как г? Я не получил его работу (метод parentContainer) –

+0

Упс. Не очень понятно, но это должен быть диапазон, я исправил имя переменной. Я думал, что он может быть использован следующим образом: var container = parentContainer (getRange()); Это не означает, что он будет работать на 100%. Код предназначен в качестве примера того типа работы, который необходим для этого, и может быть найден желательным. Вы хотите понять API, с которыми имеете дело (см. Ссылки). – Borgar

+0

'parentContainer()' бесполезен: обе ветви не гарантируют возврата одной и той же вещи, потому что метод 'parentElement()' 'TextRange' IE всегда будет возвращать элемент, а 'commonAncestorContainer' может быть текстовым узлом. Кроме того, нет необходимости в обнюхивании браузеров (как это подразумевается при использовании «msie»): вы можете легко обнаружить объекты и методы, которые вам нужны. –

0

Там есть гораздо более короткий путь, если вы просто хотите диапазон.

function getRange(){ 
    return (navigator.appName=="Microsoft Internet Explorer") 
     ? document.selection.createRange().parentElement() 
     : (getSelection||document.getSelection)().getRangeAt(0).commonAncestorContainer 
} 
+1

Это не идеально. Во-первых, браузерный нюанс бесполезен, так как IE 9 и более поздние версии поддерживают стандартные API 'Selection' и' Range', и вы так же легко обнаруживаете нужные вам функции. Во-вторых, обе ветви не гарантируют возврата к одному и тому же: метод 'parentElement()' метода IE TextRange' всегда возвращает элемент, а 'commonAncestorContainer' может быть текстовым узлом. В-третьих, именование нечетное: то, что возвращает функция, является узлом, а не диапазоном. –

+0

Короче не обязательно лучше. – displayname

7

Моя Rangy библиотека получит свою часть пути там, объединяя различные интерфейсы в IE < 9 и всех других основных браузерах, и предоставляя getNodes() функцию на его диапазон объектов:

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); 
3

Это возвращает выбранные узлы, как я понимаю: Когда я

<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://stackoverflow.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://stackoverflow.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

Я отправил этот ответ здесь, потому что я хотел бы найти его здесь.

0

Все стандарты, совместимые со стандартами, которые работают в IE11 +.

Текстовая строка

window.getSelection().getRangeAt(0).toString() 

начальный узел (даже если текст выбран в обратном направлении):

window.getSelection().anchorNode 

конечный узел (даже если текст выбран в обратном направлении):

window.getSelection().focusNode 

Хотите узнать больше? Выберите какой-нибудь текст и выполните следующую JavaScript в консоли:

console.log(window.getSelection()); 
console.log(window.getSelection().getRangeAt(0));