Pergunta

Ao selecionar um bloco de texto (possivelmente abrangendo toda a muitos nós DOM), é possível extrair o texto e os nós selecionado usando Javascript?

Imagine que este código HTML:

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

Se o usuário iniciou um evento mouseDown a partir de "World ..." e, em seguida, um mouseUp mesmo logo após "lá!", Eu estou esperando que ele voltaria:

Text : { selectedText: "WorldHi there!" },
Nodes: [ 
  { node: "h1", offset: 6, length: 5 }, 
  { node: "p", offset: 0, length: 16 }, 
  { node: "p > b", offset: 0, length: 6 } 
]

Eu tentei colocar o HTML em uma textarea, mas que só vai ficar me o SelectedText. Eu não tentei o elemento <canvas> mas que pode ser outra opção.

Se não JavaScript, existe uma maneira isso é possível usando uma extensão do Firefox?

Foi útil?

Solução

Você está em uma difícil jornada, mas isso é perfeitamente possível. O principal problema é que o IE e W3C expor as interfaces completamente diferentes para as seleções então se você quiser a funcionalidade do navegador cruz, então você basicamente tem que escrever a coisa toda duas vezes. Além disso, algumas funcionalidades básicas está faltando ambas as interfaces.

conexão de desenvolvimento Mozilla tem a história em W3C seleções . Microsoft tem seu sistema documentado no MSDN . Eu recomendo começar em do PPK para faixas .

Aqui estão algumas funções básicas que eu acredito que o trabalho:

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

Outras dicas

Rangy biblioteca terá sua parte do caminho até lá, unificando as diferentes APIs no IE <9 e todos os outros principais navegadores, e fornecendo uma função getNodes() na sua gama de objetos:

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;
}

Obter o texto selecionado é muito fácil em todos os navegadores. Em Rangy é apenas

var selectedText = rangy.getSelection().toString();

Sem Rangy:

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;
}

Quanto às compensações de caracteres, você pode fazer algo assim para qualquer node nó na seleção. Note que este não representa necessariamente o texto visível no documento porque não leva em conta os espaços em colapso, texto oculto via CSS, texto posicionado fora do fluxo de documentos normais via CSS, quebras de linha implícito <br> e bloquear elementos, além de outras sutilezas.

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

Isso retorna os nós selecionados como eu o entendo: Quando eu tiver

<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>...
<p> ... </p><p> ... </p><p> ... </p><p> ... </p><p> ... </p>

um monte de nós e eu selecionar apenas alguns então eu quero apenas esses nós para estar na lista.

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]];
  }
}

O código deve estar disponível em: https : //github.com/niccokunzmann/spiele-mit-kindern/blob/gh-pages/javascripts/feedback.js

Eu postei esta resposta aqui, porque eu teria gostado de encontrá-lo aqui.

Há uma maneira muito mais curto se você quiser apenas o intervalo.

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

código compatível com todos os padrões que as obras em IE11 +.

String de texto

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

O começar nó (para trás, mesmo que o texto é selecionado):

window.getSelection().anchorNode

O end nó (para trás, mesmo que o texto é selecionado):

window.getSelection().focusNode

Gostaria de saber mais? Selecione algum texto e execute o seguinte JavaScript no console:

console.log(window.getSelection());
console.log(window.getSelection().getRangeAt(0));
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top