最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

html - How do I extend selection to word boundary using JavaScript, once only? - Stack Overflow

programmeradmin4浏览0评论

I'm using the technique shown in this answer to extend a web page's selection to a word boundary:

function snapSelectionToWord() {
    var sel;

    // Check for existence of window.getSelection() and that it has a
    // modify() method. IE 9 has both selection APIs but no modify() method.
    if (window.getSelection && (sel = window.getSelection()).modify) {
        sel = window.getSelection();
        if (!sel.isCollapsed) {

            // Detect if selection is backwards
            var range = document.createRange();
            range.setStart(sel.anchorNode, sel.anchorOffset);
            range.setEnd(sel.focusNode, sel.focusOffset);
            var backwards = range.collapsed;
            range.detach();

            // modify() works on the focus of the selection
            var endNode = sel.focusNode, endOffset = sel.focusOffset;
            sel.collapse(sel.anchorNode, sel.anchorOffset);
            if (backwards) {
                sel.modify("move", "forward", "word");
                sel.extend(endNode, endOffset);
                sel.modify("extend", "backward", "word");

            } else {
                sel.modify("move", "backward", "word");
                sel.extend(endNode, endOffset);
                sel.modify("extend", "forward", "word");
            }
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        if (textRange.text) {
            textRange.expand("word");
            // Move the end back to not include the word's trailing space(s),
            // if necessary
            while (/\s$/.test(textRange.text)) {
                textRange.moveEnd("character", -1);
            }
            textRange.select();
        }
    }
}​

So far, so good. But if you call the snapSelectionToWord function more than once on the selection, it's expanded outward by one word in both directions on each call, which is not good if you want to call it more than once while text is selected.

Here's a live jsFiddle example that allows you to repeatedly click a 'Snap' button, which demonstrates the problem.

How can the original solution be fixed so that it doesn't expand the selection if it's already on a word boundary?

  • I'd prefer to leave a comment on the original solution but, sadly, I've not yet been graced with sufficient karma by the StackOverflow karma brigade--otherwise, I'd just ask there. And I'm not sure how to fix the problem, so I won't edit the original solution.

Edit: Adding code snippet per request

I'm using the technique shown in this answer to extend a web page's selection to a word boundary:

function snapSelectionToWord() {
    var sel;

    // Check for existence of window.getSelection() and that it has a
    // modify() method. IE 9 has both selection APIs but no modify() method.
    if (window.getSelection && (sel = window.getSelection()).modify) {
        sel = window.getSelection();
        if (!sel.isCollapsed) {

            // Detect if selection is backwards
            var range = document.createRange();
            range.setStart(sel.anchorNode, sel.anchorOffset);
            range.setEnd(sel.focusNode, sel.focusOffset);
            var backwards = range.collapsed;
            range.detach();

            // modify() works on the focus of the selection
            var endNode = sel.focusNode, endOffset = sel.focusOffset;
            sel.collapse(sel.anchorNode, sel.anchorOffset);
            if (backwards) {
                sel.modify("move", "forward", "word");
                sel.extend(endNode, endOffset);
                sel.modify("extend", "backward", "word");

            } else {
                sel.modify("move", "backward", "word");
                sel.extend(endNode, endOffset);
                sel.modify("extend", "forward", "word");
            }
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        if (textRange.text) {
            textRange.expand("word");
            // Move the end back to not include the word's trailing space(s),
            // if necessary
            while (/\s$/.test(textRange.text)) {
                textRange.moveEnd("character", -1);
            }
            textRange.select();
        }
    }
}​

So far, so good. But if you call the snapSelectionToWord function more than once on the selection, it's expanded outward by one word in both directions on each call, which is not good if you want to call it more than once while text is selected.

Here's a live jsFiddle example that allows you to repeatedly click a 'Snap' button, which demonstrates the problem.

How can the original solution be fixed so that it doesn't expand the selection if it's already on a word boundary?

  • I'd prefer to leave a comment on the original solution but, sadly, I've not yet been graced with sufficient karma by the StackOverflow karma brigade--otherwise, I'd just ask there. And I'm not sure how to fix the problem, so I won't edit the original solution.

Edit: Adding code snippet per request

Share Improve this question edited May 23, 2017 at 10:29 CommunityBot 11 silver badge asked Jun 9, 2012 at 19:40 InactivistInactivist 10.5k6 gold badges31 silver badges42 bronze badges 3
  • 1 Please put all relevant code in this post instead of linking to it. – Caspar Kleijne Commented Jun 9, 2012 at 19:44
  • 4 Done. Still learning StackOverflow etiquette. – Inactivist Commented Jun 9, 2012 at 20:07
  • 1 I've updated the original solution with Matt M's change. – Tim Down Commented Jun 11, 2012 at 9:32
Add a comment  | 

3 Answers 3

Reset to default 10

I wrote that sample. I've never been happy with it, for the reason you point out, and also because it doesn't work consistently in all browsers (or at all in Opera).

I've been working on a cross-browser solution to this for my Rangy library. The current release is described as an alpha but it works pretty well. Here's a demo:

http://rangy.googlecode.com/svn/trunk/demos/textrange.html

And here's your demo, modified to use Rangy:

http://jsfiddle.net/timdown/RgZ8r/

The crucial line is

rangy.getSelection().expand("word");

If you don't want to use something as heavyweight as Rangy (it's something like 50KB of code to use the TextRange module) then it's possible to improve the original code (as Matt M has in his answer) but it will still have limitations.

Maybe try popping a character off in either direction before you snap to words:

        if (backwards) {
            sel.modify("move", "backward", "character");
            sel.modify("move", "forward", "word");
            sel.extend(endNode, endOffset);
            sel.modify("extend", "forward", "character");
            sel.modify("extend", "backward", "word");

        } else {
            sel.modify("move", "forward", "character");
            sel.modify("move", "backward", "word");
            sel.extend(endNode, endOffset);
            sel.modify("extend", "backward", "character");
            sel.modify("extend", "forward", "word");
        }

http://jsfiddle.net/3RAkZ/

your code does not work properly in arabic text you can try this snippet nstead

function snapSelectionToWord() {
    var sel;

    // Check for existence of window.getSelection() and that it has a
    // modify() method. IE 9 has both selection APIs but no modify() method.
    if (window.getSelection && (sel = window.getSelection()).modify) {
        sel = window.getSelection();
        if (sel.isCollapsed) {
           
           var rng2 = sel.getRangeAt(0);
           var startOffset = rng2.startOffset;
           startOffset = 0
           for (var i = rng2.startOffset; i >= 0; i--) {
              if (rng2.startContainer.data[i].match(/\S/) != null) {
                 startOffset++;
              } else
                 break;
           }
           var endOffset = rng2.endOffset;
           endOffset = 0;
           for (var i = rng2.endOffset; i < rng2.endContainer.data.length; i++)
              if (rng2.endContainer.data[i].match(/\S/)) {
                 endOffset++;
              } else
                 break;

           startOffset = rng2.startOffset - startOffset;
           startOffset = startOffset < 0 ? 0 : startOffset;

           endOffset = rng2.endOffset + endOffset;
           endOffset = endOffset >= rng2.endContainer.data.length ? rng2.endContainer.data.length - 1 : endOffset;

           rng2.setStart(rng2.startContainer, startOffset);
           rng2.setEnd(rng2.endContainer, endOffset);
           sel.removeAllRanges();
           sel.addRange(rng2);
           
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        if (textRange.text) {
            textRange.expand("word");
            // Move the end back to not include the word's trailing space(s),
            // if necessary
            while (/\s$/.test(textRange.text)) {
                textRange.moveEnd("character", -1);
            }
            textRange.select();
        }
    }
}

发布评论

评论列表(0)

  1. 暂无评论