I have text in a tag:
Hello world... and goodbye mind A B!
How do I increase the area in which the te
Here is one approach that can be considered if you are ok to have some extra clicks
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:
Credits
Let me know your thoughts
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.
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.
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>
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.
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();