I need to highlight some words in a paragraph of text using spans. The resulting highlighted words come from an algorithm which returns a set of "annotations" representing the words to highlight. For instance, the algorithm might give back the following:
annotations = [{start: 5, end: 10}, {start: 12, end: 17}, ...]
For each annotation I insert in the paragraph a component Tag which wraps the corresponding word based on the offset. For example:
Hello this is an <Tag>example</Tag> :)
The problem is that the algorithm can make mistakes by generating annotations which aren't completely right, for example an offset might be too long or too short.
This is the reason I want the user to be able to change the offset for an annotation.
I was thinking to let the user drag an end of a Tag (to the left, or to the right) to basically change the offset with a very common and suitable interaction. The problem I have is restricting the drag based on the characters of the paragraph of text.
This is what I would hope to obtain:
I am a bit lost on how I could achieve this. Any help would be appreaciated!
i'm struggling to understand the following behaviour: i have two maps (based on topojson-data, visualised through d3), and on mouseover over certain parts of map1, the corresponding parts of map2 should light up. i got it to work with changing the style (opacity or fill), but now i wanted to highlight the borders of each map-part.
as seen for instance here one needs to move the specific path to the front to make all the borders visible. this is no problem for the area where i move the mouse across (using this), but when i select the corresponding part of the other map, it works one time and after that other parts get selected - so my guess is something is messing with the selection.
here is the code:
.on("mouseover",function(d){
var old=d.properties.iso; //this is the identifying number of the map-part(s)
sel=svg2.selectAll("path")
.data(datastore2015.features)
.filter(function(d){return d.properties.iso==old;})
.node(); //here the corresponding part(s) get filtered
d3.select(sel.parentNode.appendChild(sel)).classed("high2",true); //and this moves it to front and highlights the borders
on mouseout, it just resets:
.on("mouseout",function(d){
svg2.selectAll("path").classed("high2",false);
when i log the data to the console it seems that each mouseover moves +1 entry through the dataset, starting by the first entry the mouse moved over. i could not figure out why this happens and how to avoid it.
i'd appreciate any ideas you could give me, mainly i'd like to understand what's going wrong and why.
thanks
so i found my error, calling the data-variable once again seems to have messed things up - somehow i was under the impression that i need it, but it works just fine this way:
sel=svg2.selectAll("path").filter(function(d){return d.properties.iso==old;}).node();
d3.select(sel.parentNode.appendChild(sel)).classed("high2",true);
sorry for the bother, i didn't see this possibility before.
I know I can use the following line to get the offset of the caret (cursor position) relative to the current element:
offset = window.getSelection().getRangeAt(0).startOffset;
But sometimes the element will contain a lot of text, and the text will wrap. How can I find the offset relative to the nearest wrap (if any)?
For instance, given the following text which is all in one element:
<p>abcdefghjiklmnopqrstuvwxyz test</p>
Suppose it is automatically line-wrapped as follows:
abcdefghjiklmnopqrstuvwxyz
test
^cursor before this 't'
If the cursor is between the "s" and "t" in test, the above code will assign a value of 30 (26 characters on 1st line, plus space, plus t,e, and s.) to offset. But I want it to return 3 in this case: relative to the apparent start of the line, the cursor is only offset 3 ('t', 'e', and 's' come before the cursor.)
How can I accomplish this in plain, cross-platform javascript?
Update:
In case there is an easier way to accomplish what I am trying to do, here it is. I have a table of elements that I am individually turning into content editable blocks. When the down arrow is released and I see that the arrow was moved to the end of the TD, I move the focus (and the content editable) to the next cell down. However, I also need to position the cursor so it is below where it previously was on the previous line. I'm using a monospaced font, so just using the same offset as the previous apparent start of line would be sufficient.
Setting the entire table to be contentEditable doesn't work, because the behavior of the arrow keys isn't what I want. For instance, when I press down at the bottom of the field, it moves the cursor to the start of the next field to the right, which makes no sense to me.
You can use Range.prototype.getClientRects() to obtain the bounding client rects for a range. If there is a text node wrapping around to a second line, for instance, its range will have rects for each line. You can create a ‘sentinel’ range just to read its rects, then adjust it in a loop until the rect’s top or bottom changes, wind it back a bit, and check out its position. Then compare its position to your starting point and you will have the line-relative offset.
I am trying to design a little game using JS, and what I want it to do is make a random number say... 1-100 and then randomly scatter the dots (I used periods with the font size at 200) on the screen. By random, I just mean that I don't want them to be arranged in rows and columns. What I have so far achieves all but scattering the dots, so how do I do that?
var i=0;
var inhtml="."
var num=10
function exe(){
i=Math.floor(Math.random()*100)
//alert(i)
while (i<=100){
document.getElementById("dot").innerHTML = inhtml + "."
inhtml = document.getElementById("dot").innerHTML
if (inhtml.length>num){
inhtml=document.getElementById("dot").innerHTML+"<br />"
num=num+20
}
i++;
}
}
Instead of using a single element containing several periods at a large size, I'd recommend using separate elements for each dot. Then, (besides not having to use 200px periods), you can use CSS to position each element however you want. I have an example here.
Edit: I don't know what the exact problem with getting the dots to not overlap you're having is, but you basically need to do this:
First you pick a position. Then, you check that position against all the other positions (which you'd probably want to do using Manhattan distance). If the point is valid, you use that point and add it to the array of taken positions. Otherwise, go back to the first step.
You may want to check your syntax errors before progressing; I do not know whether you copied all of your code, but you are missing semicolons at the end of your lines. Syntactical issues aside, one possible way to achieve what you describe would be to assign the x- and y-coordinate of each point to a random number. Examination of the code reveals that only the initial value of i is assigned to the value of random(). Incrementing i will make the coordinates of future points dependent on the initial value of i, which is something you may want to take into account. But nowhere in your code do I see you changing the position of the each element based on the values you generate.
I strongly suggest that you use the HTML5 Canvas instead of attempting to move HTML elements; the latter would be cumbersome and lead to messy and inefficient code. If you still want to stick to the method you are trying to use now, check to make sure that the CSS display property is set to block for these elements. You are using the getElementById() method, which is not very useful in this circumstance, since IDs are unique in HTML files. I would suggest using getElementsByTagName() and using those returned elements with a specific class attribute instead.
You might want to look at the HTML 5 Canvas. It allows you draw arcs (aka circles) In any postion, size, and fill color.
Look here for details, here for a tutorial, and here for a demo.
To not be bound by the browser's text layout constraints, you pretty much have to absolutely position your dots:
<div style="position: absolute; top: {random number}; left: {random number}">.</div>
Any suggestion is highly appreciated.
Text selection has many components to it some visual, some non-visual.
First, make text selectable you must keep an array, of where the text is, what the text is, and what font was used. You will use this information with the Canvas function measureText.
By using measureText, with your text string, you can identify what letter the cursor should land on when you click on an image.
ctx.fillText("My String", 100, 100);
textWidth = ctx.measureText("My String").width;
You will still have to parse the font height from the "font" property,
as it is not currently included in text metrics. Canvas text is aligned
to the baseline by default.
With this information, you now have a bounding box, which you can check against.
If the cursor is inside of the bounding box, you now have the unfortunate task of deducing
which letter was intentionally selected; where the start of your cursor should be placed. This may involve calling measureText several times.
At that point you know where the cursor should go; you will need to store your
text string as a text string, in a variable, of course.
Once you have defined the start and stop points of your range, you have to draw
a selection indicator. This can be done in a new layer (a second canvas element),
or by drawing a rectangle using the XOR composition mode. It can also be done by
simply clearing and redrawing the text on top of a filled rectangle.
All told, text selection, text editing in Canvas are quite laborious to program, and it would be wise to re-use components already written, Bespin being an excellent example.
I'll edit my post should I come across other public examples. I believe that Bespin uses a grid-based selection method, possibly requiring a monospaced font. Ligatures, kerning, bi-directionality and other advanced features of font rendering require additional programming; it's a complex problem.
TextInput controls are complicated
Let me begin by saying I am not an expert in text controls, but by now I'm sure that this doesn't matter, because I can help you get into the woods and out safely. These things are complicated in nature and require plenty of intuition and knowledge of how things work. However, you can inspect the code that runs in the senpai-js/senpai-stage repository here.
We should define a few things up front:
Text can be any valid unicode character. You can parse that using this regex: /^.$/u
You need to keep track of three different sorts of text editing modes: Insert, Selection, Basic (I use the SelectionState enum in my library and inspect the insertMode property on stage)
You should implement sanity checks at every turn, or you will have undefined and unexpected behavior
Most people expect text inputs to be sizable by width, so make sure you use a pattern for the innards of the text box if you plan on using a texture
mouse/touch point collision detection is complicated unless you guarantee that the text input control won't rotate
The text should scroll when it's larger than the textbox in the horizontal direction. We will refer to this as textScroll which is always a negative number
Now I will go over each function to describe it's behavior to describe exactly how a textbox control should work.
Collision (broadPhase, and narrowPhase)
Collision detection is a monster. Normalizing point movement between mouse and touch events is a complicated beast not covered in this text. Once you handle point events, you have to perform some sort of general collision detection for a rectangle. This means doing AABB collision. If the textbox sprite itself is rotated, you will have to "un-rotate" the point itself. However, we bypass this check if the mouse/touch point is already down over the textbox. This is because once you start selecting text, you want this function to always return true. Then we move to narrowPhase collision, which actually checks to see if the "un-transformed" mouse/touch point is within the padding of the textbox. If it is, or the textbox is active, we return a truthy value here.
Once we know that the mouse/touch point is within the bounds of our textbox, we change the css of the canvas to cursor: text; visually.
pointCollision
When we press the mouse button down over the textbox, we need to calculate where to move the caret. The caret can exist in a range from 0 to text.length inclusive. Note that this isn't exactly right because unicode characters can have a length of 2. You must keep track of each character added to your text inside an array to assert that you aren't measuring faulty unicode characters. Calculating the target index means looping over each character of the current text and appending it to a temporary string, measuring each time until the measured width is greater than the current textScroll + the measured textWidth.
Once we have garunteed that the point has gone down on top of the textbox and the starting point is set, we can start the "selection" mode. Dragging the point should move the selection from the starting caretIndex to the new calculated end index. This goes in both directions.
An example of this is shown here.
keyPresses
The solution for web key presses is to inspect the key property on the KeyEvent. Despite a lot of what everyone says, it's possible to test that text property by testing it against the aforementioned unicode regex. If it matches, chances are that key has actually been pressed on the keyboard. This doesn't account for key combinations like ctrl + c and ctrl + v for copy and pasting. These features are trivial and are left up to the reader to decide how to implement these.
The few exceptions are the arrow keys: "ArrowLeft", "ArrowRight" etc. These keys actually modify the state of your control, and change how it functions. It's important to remember that key events should only be handled by the currently focused control. This means you should check and make sure the control is focused during text input. This of course happens at a higher level than I have coded in my library, so this is trivial.
The next problem that needs to be solved is how each character input should modify the state of your control. The keyDown method discerns the selectionState and calls a different function based on it's state. This is not optimized pseudo-code, but is used for clarity, and is perfect for our purposes in describing the behavior.
keydown on a selection
Normal key presses replace the content of the selected text
Splice out from selectionStart, and insert the new key into the text array
if "delete" or "backspace" is pressed, splice out the selection and return the selection mode to Normal or Caret
if the "left" or "right" key is pressed, move the cursor to the beginning or the end respectively and return the selection mode to Normal unless the shift key is pressed
if the shift key is pressed, then we actually want to extend the selection further
selection start will always be at the caretIndex, and we essentially move the selection end point left or right with this key combination
if selection end returns back to the caret index, we return the selectionState to Normal again
the "home" and "end" keys work the same way, only the caret is moved to 0 and text.length indexes respectively
also note that holding down the shift key extends selection from the caretIndex once again
keydown on normal mode (caret mode)
in caret mode, we don't replace any text, just insert new characters at the current position
keydowns that match the unicode regex are inserted using the splice method
move the caret to the right after splicing the text in (check and make sure that you don't go over text length)
Backspace removes one character before the index at caretIndex - 1
Delete removes one character after the index at caretIndex
text selection applies for left and right keys while the shift key is pressed
when shift isn't pressed, left and right move the caret to the left and right respectively
the home key sets the caretIndex to 0
the end key sets the caretIndex to text.length
keyDown on insert mode
in insert mode, we replace the currently selected character at caretIndex
keydowns that match the unicode regex are inserted using the splice method
move the caret to the right after splicing the text in (check and make sure that you don't go over text length)
the backspace remove the character BEFORE the current selection
delete removes the currently selected character
the arrow keys work as expected and described in normal mode
the home and end keys work as expected and described in normal mode
updating the textbox every frame
If the textbox is focused, you should start flashing the caret to let the user know they are editing text in the textbox
when moving the caret left or right in Caret mode, you should restart the flash mechanism so that it shows exactly where they are each time the caret moves
Flash the caret about once every 30 frames, or half a second
measure how far the caret is along the text by using ctx.measureText to the caret index by slicing the text to the caret position unless the mode is Selection
It's still useful to measure how far along the text is in selection mode Selection, because we always want the end of the text selection to be visible to the user
Make sure that the caret is always visible within the visible bounds of the textbox, taking into account the current textScroll
rendering the textbox
save the context first ctx.save() (basic canvas)
if you are not drawing the textbox with paths, draw the left cap of the textbox, draw the middle pattern, and the right cap respectively on the first layer
use a path defined by the padding and the size of the textbox to clip out a square to prevent the text from bleeding out
translate to the x textScroll value which should be a negative number
translate to the y midline value which should be the middle of the textbox vertically
set the font property
set the text baseline to middle and fill the text by calling text.join("") on your text array
if there is a selection, or insert mode, make sure to draw a "blue" square behind the selected text and invert the font color of the selected text (this is non-trivial and left to the reader as an exercise)
the text drawn in canvas elements cannot be selected, because of the nature of the canvas tag. But there are a few workarounds, like the one used in typefaceJS.
Another solution would be to add text with positioned div elements instead of useing strokeText or fillText.
If you need to have selectable text it would be a lot easier to just create a div or whatever, and position it on top of the canvas where you want the text to show.
canvas does not have any built-in mechanism for selecting text, so you would have to roll out your own text rendering and selecting code - which can be rather tricky to get right.
You may get some ideas from Bespin.
They implemented a text editor in javascript using canvas with text selection, scroll bars, cursor blinking, etc.
Source Code
I would suggest using EaselJS library, you can add each letter as a child and even add mouse events to that object, its an amazing library, go check it out!
A simple answer would be: either use HTML or SVG instead of canvas. Unless you really need the degree of lowlevel control canvas offers.
FabricJS now has the ability to interact with objects outside the canvas element - eg this demo shows a button that is linked to an image loaded in a canvas element.
The same approach can be used in other libs such as Raphael by hooking any move event, getting the bounding box of the element and re-positioning the HTML element.
canvas is just a drawing surface. You render and the result is pixels. So, you'd need to track the positions of all text you have rendered to the canvas in a some kind of data structure which you'd process during mouse events.