Make text “more” selectable

前端 未结 6 1945
心在旅途
心在旅途 2021-02-01 17:48

I have text in a

tag:

Hello world... and goodbye mind A B!

How do I increase the area in which the te

相关标签:
6条回答
  • 2021-02-01 18:19

    Here is one approach that can be considered if you are ok to have some extra clicks

    1. user needs to first click somewhere around the text that s/he wants to select.
    2. based on the click position we find out the word that was clicked
    3. now we take the previous word, current (clicked) word and next word and show it in a popup within H1 (or any other means to show bigger area to make it more selectable)
    4. when user selects the text from this popup, we close the popup and select appropriate text in the original element that user had clicked

    Here is my attempt: https://jsfiddle.net/vnathalye/rtw5bvLx/6/

    $('.expandable').click(function(e){
        var clicked = findClickedWord(e.target.childNodes[0], e.clientX, e.clientY);
      if(clicked){
        var $expanded = $('<span>')
                        .appendTo('body')
                        .addClass('expanded')
                        .css({
                          position: "absolute",
                          left: clicked[3].left,
                          top: clicked[3].top,
                          //width: "100px",
                          //height: "100px"
                        })
                        .append($("<h1>").text(clicked[0]));
    
            var data = {originalElem: e.target.childNodes[0], index: clicked[1], starts: clicked[2]};
    
        $expanded.data("parentData", data);
        $expanded.on('mouseup', selectionChanged);
        $expanded.on('touchend touchcancel', selectionChanged);
    
        //alert(JSON.stringify(clicked));
      }
    });
    
    function selectionChanged(e){
    try {
        var $expanded = $(e.target);
      var data = $expanded.parents(".expanded").data("parentData");
      var selection = window.getSelection();
      if(selection.rangeCount){
        var range1 = selection.getRangeAt(0);
        //alert(range1.startOffset + ":" + range1.endOffset);
    
        var range2 = document.createRange();
        var originalOffset = data.index>0? data.starts[data.index-1] : data.starts[0];
        range2.setStart(data.originalElem, originalOffset + range1.startOffset);
        range2.setEnd(data.originalElem, originalOffset + range1.endOffset);
        selection.removeAllRanges();
          selection.addRange(range2);
      }
     } catch(err){
     alert(err);
     }
      $expanded.parents(".expanded").remove();
    }
    
    function findClickedWord(parentElt, x, y) {
        if (parentElt.nodeName !== '#text') {
            console.log('didn\'t click on text node');
            return null;
        }
        var range = document.createRange();
        var words = parentElt.textContent.split(' ');
        var start = 0;
        var end = 0;
        var starts=[];
        var ends=[];
        for (var i = 0; i < words.length; i++) {
            var word = words[i];
            end = start+word.length;
            starts.push(start);
            ends.push(end);
    
            range.setStart(parentElt, start);
            range.setEnd(parentElt, end);
            // not getBoundingClientRect as word could wrap
            var rects = range.getClientRects();
            var clickedRect = isClickInRects(rects);
    
            if (clickedRect) {
                    var str = (i==0)? word : words[i-1] + " " + word;
                if(i!=words.length-1) str += " " + words[i+1];
                return [str, i, starts, clickedRect];
            }
            start = end + 1;
        }
    
        function isClickInRects(rects) {
            for (var i = 0; i < rects.length; ++i) {
                var r = rects[i]
                if (r.left<x && r.right>x && r.top<y && r.bottom>y)
                {            
                    return r;
                }
            }
            return false;
        }
        return null;
    }
    

    Note:

    1. Positioning of the popup can be improved to suit your needs. I've focused on getting the code for text selection working and have not fine-tuned popup position logic.
    2. With limited time I could test it only in FF on PC and Chrome on Android.
    3. Its the first time I've used touch events, so I'm not sure if I've used them in best possible way. Any criticism, suggestions are most welcome.

    Credits

    1. This code is based on the idea that @mmm started with, to use a dummy element to show the extra area for selection
    2. I've used modified version of the code @ https://jsfiddle.net/abrady0/ggr5mu7o/ shared by @TeoDragovic

    Let me know your thoughts

    0 讨论(0)
  • 2021-02-01 18:20

    I'm assuming the scenario where you have a body of text, and inside that body of text is a fairly important or relevant piece of information to an end user and you would like them to be able to easily highlight and copy the information.

    This would be considered as a last option if no other solution was found,

    <p class="surroundingText"> BLAH BLAH BLAH  <span class="importantText"> This is the information you would like users to be able to highlight </span> BLAH BLAH BLAH BLAH ETC ETC ETC </p>
    

    If you wrap the text around it in separate paragraph tags and give them a class then set the following in CSS:

    .surroundingText {
    
      -webkit-user-select: none;  /* Chrome all / Safari all */
      -moz-user-select: none;     /* Firefox all */
      -ms-user-select: none;      /* IE 10+ */
      user-select: none;         
    }
    
    .importantText {
    
            -webkit-user-select: all;  /* Chrome all / Safari all */
          -moz-user-select: all;     /* Firefox all */
          -ms-user-select: all;      /* IE 10+ */
          user-select: all;
        }
    

    So the end result is only the text between the span tag is able to be selected.

    0 讨论(0)
  • 2021-02-01 18:29

    One approach is to increase the line-height of the <p> element. On a mobile device, you can better select a text fragment, because of the larger spacing between the lines. If you define the values in em, the spacing is always relative to the font-size.

    A second approach is to increase the word-spacing of the text element to a level which is still acceptable. I would recommend a maximal value of of 0.2em.

    HTML:

    <p class="extendedSelection">Extended Selection</p>
    

    CSS:

    p.extendedSelection {
       line-height: 2em;
       word-spacing: 0.2em;
    }
    

    JSFiddle here

    If those two approaches are not good enough, you could of course create an absolute positioned element for each word, which overlays the text element, but has an opacity of 0. The text inside of this element should be equal to the text behind but with a larger font-size. This approach has several drawbacks: You need to calculate the positions of every word and duplicate the text content. It is a rather large calculation for just a little effect.

    0 讨论(0)
  • 2021-02-01 18:31

    Exaggerated for effect but what about:

    <head>
        <style>   
        p {
            letter-spacing: 2px;
            line-height: 3em;
            word-spacing: 1.5em;
            vertical-align: middle;
        }
        </style>
    </head>
    <body >
    
        <p>this is a line.</p>
        <p>this is a line.</p>
        <p>this is a line.</p>
        <p>this is a line.</p>
        <p>this is a line.</p>
        <p>this is a line.</p>
        <p>this is a line.</p>
    </body>

    0 讨论(0)
  • You could add padding around paragraphs as someone already suggested but also use negative margin of the same value to prevent it from affecting layout.

    Here is DEMO (double-clicking or long-tapping anywhere inside gray area should select text)

    Relevant code:

    HTML:

    <p>normal paragraph</p>
    <hr />
    <p class="fat-fingers">fat fingers paragraph</p>
    

    CSS:

    p {
        //resetting default browser styles for brevity
        //otherwise adjust negative margin value so it's == default margin - padding
        margin: 0;
    }
    
    .fat-fingers {
        padding: 10px;
        margin: -10px;
    }
    

    note: I didn't test case of two areas overlapping but I assume that the one with higher stacking order wins.

    0 讨论(0)
  • 2021-02-01 18:42

    From my test it works on the iphone as well as ff and chrome - if someone can test on android I'll appreciate feedback!

    The border obviously can be removed.

    This code uses code from this answer (part of the SelectText() function): Selecting text in an element (akin to highlighting with your mouse)

    Fiddle

    Code:

    function extendSelection() {
        var extendBy = arguments.length <= 0 || arguments[0] === undefined ? 15 : arguments[0];
    
        var extended = document.getElementsByClassName('extendedSelection');
        [].slice.call(extended).forEach(function (v) {
            var bounds = v.getBoundingClientRect();
            var x = bounds.left;
            var r = textWidth(v.innerHTML, ''+ css(v, 'font-weight') +' ' + css(v, 'font-size') + ' ' + css(v, 'font-family') );
            var y = bounds.top;
            var w = bounds.width;
            var h = bounds.height;
            var element = document.createElement('div');
            element.style.position = 'absolute';
            element.style.height = h + extendBy + 'px';
            element.style.width = r + extendBy + 'px';
            element.style.left = x - extendBy / 2 + 'px';
            element.style.top = y - extendBy / 2 + 'px';
            element.style.border = '1px dotted black';
            document.body.appendChild(element);
            element.addEventListener('click', function (e) {
                SelectText(v);
            });
            element.addEventListener('touchend', function (e) {
                SelectText(v);
            });
        });
    }
    
    function css(element, property) {
        return window.getComputedStyle(element, null).getPropertyValue(property);
    }
    
    function textWidth(text, font) {
        var el = textWidth.canvas || (textWidth.canvas = document.createElement("canvas"));
        var draw = el.getContext("2d");
        draw.font = font;
        var m = draw.measureText(text);
        return m.width;
    };
    
    function SelectText(element) {
        var doc = document,
            text = element,
            range,
            selection;
        if (doc.body.createTextRange) {
            range = document.body.createTextRange();
            range.moveToElementText(text);
            range.select();
        } else if (window.getSelection) {
            selection = window.getSelection();
            range = document.createRange();
            range.selectNodeContents(text);
            selection.removeAllRanges();
            selection.addRange(range);
            selection.setSelectionRange(0, element.value.length)
        }
    }
    
    extendSelection();
    
    0 讨论(0)
提交回复
热议问题