Reputation: 1307
what I need is interactive selection highlighting of text in the browser via Javascript.
To be more specific, lets just say I have a bunch of text in a div
element like so:
<div>The quick brown fox jumps over the lazy dog ... </div>
Now what I want to do is to select a certain range of this text and highlight it (i.e. "brown fox"). I already got that working by using rangy.js
.
Now I really want to make this selection / highlight more interactive so that the user can grab the beginning or the end of the selection (with some kind of handle) and resize the selection interactively by dragging the beginning or the end of the current selection to a new starting or endpoint.
I tried to google for this but couldn't find anything. Whats even worse is that I habe absolutely no idea how I should start to implement something like this.
Perhaps can someone can point me into the right direction or even better already has some idea on how to start implementing something like this.
Thanks a lot for any help ...
Upvotes: 4
Views: 3157
Reputation: 3819
This problem is rather complex and hampered by the lack of conformity across browsers. However, I have had a stab at the problem with some level of success.
I provide no guarantees that this works in all situations, my testing has been limited to text alone so far. There are also a few issues that need fixing, which I will address in the near future. However, I think it's ready to post here.
The problem can be broken down into several steps. First, we need to insert markers around the user's selection. This is not too difficult, although there are several design decisions that must be addressed. After some experimentation, I settled on inserting spans directly into the document at the marker positions; it is then easier to select content between these markers. Below is the insert marker method.
function insertMarker (isBefore) {
var range;
if (window.getSelection) {
// IE9+ and non-IE
var sel = window.getSelection();
if (sel.getRangeAt) {
range = window.getSelection().getRangeAt(0);
range.collapse(isBefore);
}
} else if (document.selection && document.selection.createRange) {
// IE < 9
range = document.selection.createRange();
range.collapse(isBefore);
}
// Create the marker element and insert it into the DOM.
if (range) {
range.insertNode(createMarker(isBefore));
}
}
Second, the user must be able to drag these markers around the document such that they lock between characters/selectable content. This can be achieved using document.caretPositionFromPoint(x, y)
(standard) or document.caretRangeFromPoint(x, y)
(WebKit).
Third, when the markers are moved, the selection must be updated to reflect this change. This can be achieved using range.setStartAfter
and range.setEndBefore
, which each are supported in most browsers, despite varying Range implementations.
function selectSelection () {
if (window.getSelection) {
// IE9+ and non-IE
var sel = window.getSelection();
var range = document.createRange();
range.setStartAfter($(".marker").get(0));
range.setEndBefore($(".marker").get(1));
sel.removeAllRanges();
sel.addRange(range);
}
else if (document.selection && document.selection.createRange) {
// IE < 9
var range = document.selection.createRange();
range.setStartAfter($(".marker").get(0));
range.setEndBefore($(".marker").get(1));
}
}
Upvotes: 5
Reputation: 1
Try
html
<div id="selectable"></div>
<br />
<hr />
<button id="clear">clear selections</button>
<hr />
<br />
<div id="selections">
<div class="selected"></div>
</div>
css
#selectable .ui-selecting {
background: #FECA40;
}
#selectable .ui-selected {
background: #F39814;
color: white;
}
#selections {
width: 100%;
height: 400px;
}
#clear {
position: relative;
left: calc(37%);
}
.selected {
width: 150px;
height: 150px;
overflow: hidden;
}
.selected,
.text {
display: block;
background-color: rgb(225, 225, 225);
}
.ui-widget-content {
border: 1px dotted rgba(170, 170, 170, .25);
}
.ui-widget-conent span {
display: inline-block;
padding: 4px;
margin: 4px;
}
js
var selectable = $("#selectable") // selectable words
, selections = $("#selections") // selected words container parent
, selected = $(".selected") // selected words container
, clear = $("#clear") // clear all
, sel = ".ui-selected" // selected word
, content = ".ui-widget-content" // selectable words element `class`
, text = ".text" // selected words draggable, resizable container `class`
, handles = {} // resize handles
, str = "The quick brown fox jumps over the lazy dog ..." // text, `string`
// split `str`, return array of words
, words = str.split(" ").map(function(word, i) {
$("<span />", {
"class": content.slice(1),
"text": word,
"css": {
"margin": "2px"
}
}).appendTo(selectable)
});
// append `handles` to `text` container
$.each(["nw", "ne", "sw", "se"], function(_, handle) {
var elem = $("<div />", {
"class": "ui-resizable-handle ui-resizable-" + handle,
"css": {
"width": "calc(12px + 5%)",
"height": "calc(12px + 5%)",
"border-radius": "50%",
"background": "#000"
}
})
.appendTo(selected)
.parent().find(":last")
.css({
"right": "-5px",
"bottom": "-5px"
});
handles[handle] = elem[0];
});
// `selected` settings
// set `selected` `display` to `none`
selected
.hide(0)
.resizable({
handles: handles
})
.draggable({
containment: "parent"
});
// collect selected words at `selectable.data("selections", [])` array,
// append words to `text`,
// append `text` to `selected`,
// set `selected` `display` to `block`
selectable.data("selections", [])
.selectable({
selected: function(event, ui) {
$(this).data("selections").push($(ui.selected).text())
},
stop: function() {
selected
.find(".text")
.remove()
.addBack()
.prepend(
$("<div />", {
"class": "text",
"text": $(this).data("selections").join(" "),
"css": {
"position": "relative",
"display": "block",
"padding": "calc(15%)",
"height": "calc(50%)",
"white-space": "pre-line",
"overflow": "hidden"
}
})
).show(0)
}
});
// remove selected words from `selectable.data("selections", [])` array,
// remove `sel` `class` from `selectable` words,
// remove `text`,
// set `selected` display to `none`
clear.on("click", function(e) {
selectable.data("selections", [])
.find(sel).removeClass(sel.slice(1));
selections.find(text).remove()
.addBack().find(selected).hide(0)
});
jsfiddle http://jsfiddle.net/guest271314/dpt9bn0n/
See jQuery UI Selectable , Draggable , Resizable
$(function() {
var selectable = $("#selectable") // selectable words
, selections = $("#selections") // selected words container parent
, selected = $(".selected") // selected words container
, clear = $("#clear") // clear all
, sel = ".ui-selected" // selected word
, content = ".ui-widget-content" // selectable words element `class`
, text = ".text" // selected words draggable, resizable container `class`
, handles = {} // resize handles
, str = "The quick brown fox jumps over the lazy dog ..." // text, `string`
// split `str`, return array of words
, words = str.split(" ").map(function(word, i) {
$("<span />", {
"class": content.slice(1),
"text": word,
"css": {
"margin": "2px"
}
}).appendTo(selectable)
});
// append `handles` to `text` container
$.each(["nw", "ne", "sw", "se"], function(_, handle) {
var elem = $("<div />", {
"class": "ui-resizable-handle ui-resizable-" + handle,
"css": {
"width": "calc(12px + 5%)",
"height": "calc(12px + 5%)",
"border-radius": "50%",
"background": "#000"
}
})
.appendTo(selected)
.parent().find(":last") // `se`
.css({
"right": "-5px",
"bottom": "-5px"
});
handles[handle] = elem[0];
});
// `selected` settings
// set `selected` `display` to `none`
selected
.hide(0)
.resizable({
handles: handles
})
.draggable({
containment: "parent"
});
// collect selected words at `selectable.data("selections", [])` array,
// append words to `text`,
// append `text` to `selected`,
// set `selected` `display` to `block`
selectable.data("selections", [])
.selectable({
selected: function(event, ui) {
$(this).data("selections").push($(ui.selected).text())
},
stop: function() {
selected
.find(".text")
.remove()
.addBack()
.prepend(
$("<div />", {
"class": "text",
"text": $(this).data("selections").join(" "),
"css": {
"position": "relative",
"display": "block",
"padding": "calc(15%)",
"height": "calc(50%)",
"white-space": "pre-line",
"overflow": "hidden"
}
})
).show(0)
}
});
// remove selected words from `selectable.data("selections", [])` array,
// remove `sel` `class` from `selectable` words,
// remove `text`,
// set `selected` display to `none`
clear.on("click", function(e) {
selectable.data("selections", [])
.find(sel).removeClass(sel.slice(1));
selections.find(text).remove()
.addBack().find(selected).hide(0)
});
// do stuff
var phrase = [0, 1, 2, 8, 4, 5, 6, 7, 3, 9];
setTimeout(function() {
$.when(
selectable.queue("phrase", $.map(phrase, function(word, i) {
return function(next) {
return $.when(!$(this).find(content).eq(word)
.addClass("ui-selecting").parent()
.data("ui-selectable")._mouseStop(false) && $(this)
).then(function(el) {
return el.delay(2000);
}).then(next)
}
})
)
.dequeue("phrase").promise("phrase")
, clear
)
.then(function(el, button) {
button.trigger("click")
});
}, 2713);
});
#selectable .ui-selecting {
background: #FECA40;
}
#selectable .ui-selected {
background: #F39814;
color: white;
}
#selections {
width: 100%;
height: 400px;
}
#clear {
position: relative;
left: calc(37%);
}
.selected {
width: 150px;
height: 150px;
overflow: hidden;
}
.selected,
.text {
display: block;
background-color: rgb(225, 225, 225);
}
.ui-widget-content {
border: 1px dotted rgba(170, 170, 170, .25);
}
.ui-widget-conent span {
display: inline-block;
padding: 4px;
margin: 4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<script src="http://code.jquery.com/ui/1.11.3/jquery-ui.js"></script>
<link href="http://code.jquery.com/ui/1.11.3/themes/smoothness/jquery-ui.css"
rel="stylesheet" />
<div id="selectable"></div>
<br />
<hr />
<button id="clear">clear selections</button>
<hr />
<br />
<div id="selections">
<div class="selected"></div>
</div>
Upvotes: 1