move fake cursor to position taken from clicked character - javascript

I have a div with text using monospace font, and I need to display a cursor in the place where I click, I have functions that display text with cursor:
function draw() {
var text = textarea.val();
var html;
if (pos == text.length) {
html = encode(text) + '<span class="cursor"> </span>';
} else {
html = encode(text.slice(0, pos)) + '<span class="cursor">' +
encode(text[pos+1]) + '</span>' + encode(text.slice(pos+1));
}
output.html(html);
}
and function that get cursor position based on x/y coordinate of the mouse event:
function get_char_pos(div, text, event) {
var num_chars = get_num_chars(div);
var cursor = div.find('.cursor');
var rect = cursor[0].getBoundingClientRect();
var width = rect.width;
var height = rect.height;
var offset = div.offset();
var col = Math.floor((event.pageX-offset.left)/width);
var row = Math.floor((event.pageY-offset.top)/height);
var try_pos = col + (row > 0 ? num_chars * row : 0);
return try_pos;
}
It almost working except when text contain tabs (tabs are replaced by 4 spaces by encode function). I've try to fix tabs using this:
var before = text.slice(0, try_pos);
var tabs = before.match(/\t/g);
var fix = tabs ? tabs * 3 : 0;
try_pos += fix;
return try_pos > text.length ? text.lenght : try_pos;
but this don't work. It should also work for a case when I click on space that may be part of tab. How to fix it when text contain tabs?
Here is codepen demo

The tab character is the issue. It's a single character which means the string it's not calculated as four characters in the text.slice. If you replace \t with four spaces your issue is solved.

Related

JavaScript mouseover/mousemove cusor postion without clicking in input text box

I'm attempting to combine a JavaScript mechanism for auto placing the users cursor inside of an input box through the mouseover and mousemove listeners.
I have an almost perfect working example here: http://codepen.io/anon/pen/doxNLm?editors=101
var current_element = document.getElementById("hover");
current_element.onmousemove = function showCoords(evt) {
var form = document.forms.form_coords;
var parent_id = this.id;
form.parentId.value = parent_id;
form.pageXCoords.value = evt.pageX;
form.pageYCoords.value = evt.pageY;
form.layerXCoords.value = evt.layerX;
form.layerYCoords.value = evt.layerY;
function getTextWidth(text, font) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
};
var element_base_browser_styles = window.getDefaultComputedStyle(current_element);
var total_text_pixal_length = getTextWidth(current_element.value, element_base_browser_styles.fontFamily + " " + element_base_browser_styles.fontSize);
var add_char_pixal_lengths = 0;
var myStringArray = current_element.value.split('');
var arrayLength = myStringArray.length;
for (var i = 0; i <= arrayLength; i++) {
var get_char_value = getTextWidth(myStringArray[i], element_base_browser_styles.fontFamily + " " + element_base_browser_styles.fontSize);
add_char_pixal_lengths = add_char_pixal_lengths + (get_char_value) + 1.311111111111; //every char value is added together.
// console.log("Total: " + x);
if ((add_char_pixal_lengths)> (evt.layerX)) {
this.setSelectionRange(i, i);
add_char_pixal_lengths = 0;
break;
}
}
}
current_element.onmouseover = function() {
this.focus()
}
The problem I'm having is like Geosynchronous orbit; the cursor shifts out of place sometimes a few pixels (left or right). My calculation probably sucks, but I'm not sure canvas is really the best way to do the measurement? Is there a better way?
mousemove listener to receive element cursor coordinates from e.pageX
font style using window.getComputedStyles(input_element)
arr.split('') from input_element.text string: x = ['a','b','c']
'for loop' the array, generate a canvas and measure each characters width
add all char widths one by one until the value is greater than e.pageX
set the 'for loop' iterate as the setSelectionRange(i, i)
Any help or suggestions on making this better would be appreciated. Thanks!

How to drag and drop into text

Before trying to build something, I would like to determine if it is possible.
Start with a text area which can be pre-populated with text and which the user can add/delete text. Now, There are some small elements to the side. They can either be images or HTML elements such as a button or anchor links, whatever is easier. The user can drag an elements into the text area, and it will be inserted at the mouse cursor location and take up text space by pushing the existing text around it (the nearby element will also remain so the user can drop a second). The elements will remain as an element which can later be dragged elsewhere in the document or outside of the view port in which it will be removed. When the elements are positioned as desired, the location of the elements can be identified through some means (regex, dom, etc) so that they can be replaced with different content.
Line breaks will be needed. Ideally, it will work with jQuery and IE7+, however, the IE7 desire might need to be changed.
I’ve come across the following which are close but not quite.
http://skfox.com/jqExamples/insertAtCaret.html
http://jsbin.com/egefi (reference jQuery Drag & Drop into a Text Area)
http://jqueryui.com/droppable/#method-option
If you think it could be built and you have suggestions where I should start, please advise.
I did something very similar yesterday so why not sharing :)
My goal was to drop elements of text onto my textarea, in the middle of two lines of text while showing an indicator to where it would be dropped. I believe it will be useful to you ! ;)
$(document).ready(function() {
var lineHeight = parseInt($('#textarea').css('line-height').replace('px',''));
var previousLineNo = 0;
var content = $('#textarea').val();
var linesArray = content.length > 0 ? content.split('\n') : [];
var lineNo = 0;
var emptyLineAdded = false;
$('.draggable').draggable({
revert: function(is_valid_drop) {
if (!is_valid_drop) {
$('#textarea').val(content);
return true;
}
},
drag: function(event, ui) {
lineNo = getLineNo(ui, lineHeight);
if (linesArray.length > 0 && previousLineNo != lineNo) {
insertWhiteLine(lineNo, linesArray);
}
previousLineNo = lineNo;
}
});
$("#textarea").droppable({
accept: ".draggable",
drop: function( event, ui ) {
appendAtLine(lineNo, linesArray, ui.draggable.text());
$(ui.draggable).remove();
content = $('#textarea').val();
linesArray = content.split('\n');
if (linesArray[linesArray.length - 1] == '')
linesArray.pop(); //remove empty line
}
});
$('#textarea').on('input', function() {
if (!emptyLineAdded) {
console.log('input !');
console.log($('#textarea').val());
content = $('#textarea').val();
linesArray = content.split('\n');
if (linesArray[linesArray.length - 1] == '')
linesArray.pop(); //remove empty line
}
});
});
//Returns the top position of a draggable element,
//relative to the textarea. (0 means at the very top of the textarea)
function getYPosition(element, lineHeight) {
var participantIndex = $(element.helper.context).index();
var initPos = participantIndex * lineHeight;
var actualPos = initPos + element.position.top;
return actualPos;
}
//Returns the line number corresponding to where the element
//would be dropped
function getLineNo(element, lineHeight) {
return Math.round(getYPosition(element, lineHeight) / lineHeight);
}
//Inserts a white line at the given line number,
//to show where the element would be dropped in the textarea
function insertWhiteLine(lineNo, linesArray) {
$('#textarea').val('');
$(linesArray).each(function(index, value) {
if (index < lineNo)
$('#textarea').val($('#textarea').val() + value + '\n');
});
emptyLineAdded = true;
$('#textarea').val($('#textarea').val() + '_____________\n'); //white line
emptyLineAdded = false;
$(linesArray).each(function(index, value) {
if (index >= lineNo)
$('#textarea').val($('#textarea').val() + value + '\n');
});
}
//Inserts content of draggable at the given line number
function appendAtLine(lineNo, linesArray, content) {
$('#textarea').val('');
$(linesArray).each(function(index, value) {
if (index < lineNo)
$('#textarea').val($('#textarea').val() + value + '\n');
});
$('#textarea').val($('#textarea').val() + content + '\n'); //content to be added
$(linesArray).each(function(index, value) {
if (index >= lineNo)
$('#textarea').val($('#textarea').val() + value + '\n');
});
}

slide text (book page) from top to bottom

i am working on a small application (phonegap) that scrolls a page of a book when the users pushed the audio-button to listen to the text at the same time. The general idea :-)
I have looked into the Marquee version, what works so far but it has some strange behaviour:
<marquee behavior="scroll" height="100%" vspace="0%" direction="up" id="mymarquee" scrollamount="3" scolldelay="1000" loop="1"> TEXT HERE </marquee>
with the "id="mymarquee" connected to the audio play button. This works but not recommanded as they say. Better to use a javascript version. So i found a cool version so far on the web, but it goes from the right to the left. Now i am not the best programmer in the world so i was wondering if someone could help adjust the script below so we can add a direction to it. This way the script would be multi-functional (for others as well) since i only need a scroll from top to bottom.
Here is the HTML part:
<script src="js/slideandfade.js" type="text/javascript"></script>
<DIV ID="fader" STYLE="text-align:right;"></DIV>
<SCRIPT TYPE="text/javascript">
fadeandscroll('TEXT HERE', '#676F77', '#DFF5FF', 40, 70, 250, 10);
</SCRIPT>
And this is the slideandfade.js
//Text fade
var bgcolor;
var fcolor;
var heading;
//Number of steps to fade
var steps;
var colors;
var color = 0;
var step = 1;
var interval1;
var interval2;
//fade: fader function
// Fade from backcolor to forecolor in specified number of steps
function fade(headingtext,backcolor,forecolor,numsteps) {
if (color == 0) {
steps = numsteps;
heading = "<font color='{COLOR}'>"+headingtext+"</strong></font>";
bgcolor = backcolor;
fcolor = forecolor;
colors = new Array(steps);
getFadeColors(bgcolor,fcolor,colors);
}
// insert fader color into message
var text_out = heading.replace("{COLOR}", colors[color]);
// write the message to the document
document.getElementById("fader").innerHTML = text_out;
// select next fader color
color += step;
if (color >= steps) clearInterval(interval1);
}
//getFadeColors: fills colors, using predefined Array, with color hex strings fading from ColorA to ColorB
//Note: Colors.length equals the number of steps to fade
function getFadeColors(ColorA, ColorB, Colors) {
len = Colors.length;
//Strip '#' from colors if present
if (ColorA.charAt(0)=='#') ColorA = ColorA.substring(1);
if (ColorB.charAt(0)=='#') ColorB = ColorB.substring(1);
//Substract red green and blue components from hex string
var r = HexToInt(ColorA.substring(0,2));
var g = HexToInt(ColorA.substring(2,4));
var b = HexToInt(ColorA.substring(4,6));
var r2 = HexToInt(ColorB.substring(0,2));
var g2 = HexToInt(ColorB.substring(2,4));
var b2 = HexToInt(ColorB.substring(4,6));
// calculate size of step for each color component
var rStep = Math.round((r2 - r) / len);
var gStep = Math.round((g2 - g) / len);
var bStep = Math.round((b2 - b) / len);
// fill Colors array with fader colors
for (i = 0; i < len-1; i++) {
Colors[i] = "#" + IntToHex(r) + IntToHex(g) + IntToHex(b);
r += rStep;
g += gStep;
b += bStep;
}
Colors[len-1] = ColorB; // make sure we finish exactly at ColorB
}
//IntToHex: converts integers between 0 - 255 into a two digit hex string.
function IntToHex(n) {
var result = n.toString(16);
if (result.length==1) result = "0"+result;
return result;
}
//HexToInt: converts two digit hex strings into integer.
function HexToInt(hex) {
return parseInt(hex, 16);
}
var startwidth = 0;
//scroll: Make the text scroll using the marginLeft element of the div container
function scroll(startw) {
if (startwidth == 0) {
startwidth=startw;
}
document.getElementById("fader").style.marginLeft = startwidth + "px";
if (startwidth > 1) {
startwidth -= 1;
} else {
clearInterval(interval2);
}
}
function fadeandscroll(txt,color1,color2,numsteps,fademilli,containerwidth,scrollmilli) {
interval1 = setInterval("fade('"+txt+"','"+color1+"','"+color2+"',"+numsteps+")",fademilli);
interval2 = setInterval("scroll("+containerwidth+")",scrollmilli);
}

Finding number of lines in an html textarea

I'm writing a mobile web application where scrollbars are not displayed on the device's browser. Due to this, I'm trying to dynamically modify the height of the textarea to make it bigger, however I don't know of any way to actually get the line count on an html textarea. Any help would be greatly appreciated!
EDIT
So I realize now that it's not newlines per se, but actual line wrapping. So when one line finishes it wraps the text to the next line. It appears as if it is a new line. Any way to count the number of these? Thanks!
The number of lines in the textarea would be
textarea.value.match(/\n/g).length + 1
I have created a plugin to handle line counting and wrap detection in a <textarea>.
I hope someone can use it.
Code on BitBucket
Sample Usage
var result = $.countLines("#textarea");
result.actual // The number of lines in the textarea.
result.wraps // The number of lines in the textarea that wrap at least once.
result.wrapped // The total number of times all lines wrap.
result.blank // The number of blank lines.
result.visual // The approximate number of lines that the user actually sees in the textarea
Working Demonstration
/*! Textarea Line Count - v1.4.1 - 2012-12-06
* https://bitbucket.org/MostThingsWeb/textarea-line-count
* Copyright (c) 2012 MostThingsWeb (Chris Laplante); Licensed MIT */
(function($) {
$.countLines = function(ta, options) {
var defaults = {
recalculateCharWidth: true,
charsMode: "random",
fontAttrs: ["font-family", "font-size", "text-decoration", "font-style", "font-weight"]
};
options = $.extend({}, defaults, options);
var masterCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var counter;
if (!ta.jquery) {
ta = $(ta);
}
var value = ta.val();
switch (options.charsMode) {
case "random":
// Build a random collection of characters
options.chars = "";
masterCharacters += ".,?!-+;:'\"";
for (counter = 1; counter <= 12; counter++) {
options.chars += masterCharacters[(Math.floor(Math.random() * masterCharacters.length))];
}
break;
case "alpha":
options.chars = masterCharacters;
break;
case "alpha_extended":
options.chars = masterCharacters + ".,?!-+;:'\"";
break;
case "from_ta":
// Build a random collection of characters from the textarea
if (value.length < 15) {
options.chars = masterCharacters;
} else {
for (counter = 1; counter <= 15; counter++) {
options.chars += value[(Math.floor(Math.random() * value.length))];
}
}
break;
case "custom":
// Already defined in options.chars
break;
}
// Decode chars
if (!$.isArray(options.chars)) {
options.chars = options.chars.split("");
}
// Generate a span after the textarea with a random ID
var id = "";
for (counter = 1; counter <= 10; counter++) {
id += (Math.floor(Math.random() * 10) + 1);
}
ta.after("<span id='s" + id + "'></span>");
var span = $("#s" + id);
// Hide the span
span.hide();
// Apply the font properties of the textarea to the span class
$.each(options.fontAttrs, function(i, v) {
span.css(v, ta.css(v));
});
// Get the number of lines
var lines = value.split("\n");
var linesLen = lines.length;
var averageWidth;
// Check if the textarea has a cached version of the average character width
if (options.recalculateCharWidth || ta.data("average_char") == null) {
// Get a pretty good estimation of the width of a character in the textarea. To get a better average, add more characters and symbols to this list
var chars = options.chars;
var charLen = chars.length;
var totalWidth = 0;
$.each(chars, function(i, v) {
span.text(v);
totalWidth += span.width();
});
// Store average width on textarea
ta.data("average_char", Math.ceil(totalWidth / charLen));
}
averageWidth = ta.data("average_char");
// We are done with the span, so kill it
span.remove();
// Determine missing width (from padding, margins, borders, etc); this is what we will add to each line width
var missingWidth = (ta.outerWidth() - ta.width()) * 2;
// Calculate the number of lines that occupy more than one line
var lineWidth;
var wrappingLines = 0;
var wrappingCount = 0;
var blankLines = 0;
$.each(lines, function(i, v) {
// Calculate width of line
lineWidth = ((v.length + 1) * averageWidth) + missingWidth;
// Check if the line is wrapped
if (lineWidth >= ta.outerWidth()) {
// Calculate number of times the line wraps
var wrapCount = Math.floor(lineWidth / ta.outerWidth());
wrappingCount += wrapCount;
wrappingLines++;
}
if ($.trim(v) === "") {
blankLines++;
}
});
var ret = {};
ret["actual"] = linesLen;
ret["wrapped"] = wrappingLines;
ret["wraps"] = wrappingCount;
ret["visual"] = linesLen + wrappingCount;
ret["blank"] = blankLines;
return ret;
};
}(jQuery));
result = jQuery.countLines("#textarea");
jQuery('#display').html(
'<span>Actual: ' + result.actual + '</span>' +
'<span>Blank: ' + result.blank + '</span>' +
'<span>Visual: ' + result.visual + '</span>' +
'<span>Wrapped: ' + result.wrapped + '</span>' +
'<span>Wraps: ' + result.wraps + '</span>'
);
#textarea {
width: 150px;
height: 80px;
}
#display span {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="textarea">text
here
this is a longer line so that it will wrap in the box longer longer longer</textarea>
<div id="display"></div>
This is an efficient and accurate method to count the number of lines in a text area, including wrapped lines.
/** #type {HTMLTextAreaElement} */
var _buffer;
/**
* Returns the number of lines in a textarea, including wrapped lines.
*
* __NOTE__:
* [textarea] should have an integer line height to avoid rounding errors.
*/
function countLines(textarea) {
if (_buffer == null) {
_buffer = document.createElement('textarea');
_buffer.style.border = 'none';
_buffer.style.height = '0';
_buffer.style.overflow = 'hidden';
_buffer.style.padding = '0';
_buffer.style.position = 'absolute';
_buffer.style.left = '0';
_buffer.style.top = '0';
_buffer.style.zIndex = '-1';
document.body.appendChild(_buffer);
}
var cs = window.getComputedStyle(textarea);
var pl = parseInt(cs.paddingLeft);
var pr = parseInt(cs.paddingRight);
var lh = parseInt(cs.lineHeight);
// [cs.lineHeight] may return 'normal', which means line height = font size.
if (isNaN(lh)) lh = parseInt(cs.fontSize);
// Copy content width.
_buffer.style.width = (textarea.clientWidth - pl - pr) + 'px';
// Copy text properties.
_buffer.style.font = cs.font;
_buffer.style.letterSpacing = cs.letterSpacing;
_buffer.style.whiteSpace = cs.whiteSpace;
_buffer.style.wordBreak = cs.wordBreak;
_buffer.style.wordSpacing = cs.wordSpacing;
_buffer.style.wordWrap = cs.wordWrap;
// Copy value.
_buffer.value = textarea.value;
var result = Math.floor(_buffer.scrollHeight / lh);
if (result == 0) result = 1;
return result;
}
Demo here
I haven't tried using the function discussed in this blog, but you may find it useful.
http://kirblog.idetalk.com/2010/03/calculating-cursor-position-in-textarea.html
Basically, if you create a div and then copy the text into that div, with the same width and font characteristics, you can then get the information you need, such as the number of lines. The number of lines in this example would be easy, in that if you know how many pixels high a single line would be, then just find the width of the test div and you can get a pretty accurate idea as to how many lines are in your textarea.
Get scrollHeight, subtract top+bottom padding, divide by lineHeight.
I'm pretty sure there is no reasonable way to count the number of lines as displayed in the browser especially considering some browsers (Safari) allow the user to resize textareas.
It'd be hacky, but your best bet might be to just estimate based on the total characters divided by average number of characters per line. :-/
Maybe there is a way to get the "raw" number of "visual" lines. You should read the scrollHeight property of the textarea and divide it by the height of a line. Let's try.
Start with this HTML:
<textarea id="ta" cols="50" rows="10"></textarea>
Then:
var line_height = Math.floor($("#ta").height() / parseInt($("#ta").attr("rows")));
var dirty_number_of_lines = Math.ceil($("#ta")[0].scrollHeight / line_height);
I am not sure if that really works, just a mad theory.
You can calculate is as so:
var length = $('#textarea').val().split("\n").length;
The number of characters allowed per line is dictated by the "cols" attribute of the textarea.
<textarea rows="10" cols="80"></textarea>
Assuming 80 characters per line, a good estimate may be:
var approxNumLines = textareaElement.value.length / textareaElement.cols ;
Doesn't account for word-break and word-wrap.

Select a specific character in a string and offset it (visually) with Jquery

I'm trying to use Jquery/Javascript to mimic a broken typewriter font (since I could not find one). But I want to make it random which letter gets broken. I was able to split the string of the id that I wanted and use a bit of code I found to get a random number between 0 and the total length of the string. What I'm having problem with now is doing something with that specific character. I want to push it down or up a few pixels. I was trying to give it a class so I could add some margin or padding, but it doesn't work. So I'm stuck where I am now.
here's the page, I'm trying to do it to the word "ABOUT":
http://www.franciscog.com/bs/about.php
here's the script:
<script type="text/javascript">
function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
var str = $('#typehead').text();
var strcnt = str.length;
var exploded = str.split('');
var rdmltr =randomXToY(0,strcnt);
var theLetter = exploded[rdmltr];
theLetter.addClass("newClass");
var toffset = $('.newClass').offset();
alert(toffset.left + "," + toffset.top);
</script>
EDIT: Updated to ensure that the matched character is not a space character, and added a little style suggested by #abelito.
How about this: http://jsfiddle.net/cgXa3/4/
function randomXToY(minVal,maxVal,floatVal){
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
var exploded = $('#typehead').text().split('');
var rdmltr = randomXToY(0,exploded.length);
// Make sure we don't get a space character
while(exploded[rdmltr] == ' ') {
rdmltr = randomXToY(0,exploded.length);
}
// Wrap the letter with a span that has the newClass
// and update it in the array
exploded[rdmltr] = '<span class="newClass">' + exploded[rdmltr] + '</span>';
// Update the content
$('#typehead').html(exploded.join(''));
var toffset = $('.newClass').offset();
alert(toffset.left + "," + toffset.top);​
Update: If you want to apply it to several: http://jsfiddle.net/cgXa3/5/
I like Patrick's answer, but as an alternative I would alter the same letter throughout the text. And maybe rotate it a tiny bit as well (although this won't work in IE). I made a demo which I forked off of Patrick's.
CSS
.newClass {
left: 0px;
top: -1px;
color: red;
position:relative;
-webkit-transform: rotate(-5deg);
-moz-transform: rotate(-5deg);
}
Code
function randomLetter(cased){
// case = true for uppercase, false for lowercase
var base = (cased) ? 65 : 97;
// convert HTML escape code into a letter
var rand = $('<span>&#' + parseInt(base+(Math.random()*25),10) + ';</span>');
return rand.text();
};
$(document).ready(function(){
var ltr = randomLetter(false);
var reg = new RegExp( ltr, 'g');
$('#typehead').html(function(i,html){
return html.replace(reg, '<span class="newClass">' + ltr + '</span>');
});
});
Update: This is the code needed to apply to multiple h1 tags (updated demo):
function randomXToY(minVal,maxVal,floatVal){
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
$('.typehead').each(function() {
//access the text and characters within the tag with the id typehead
var exploded = $(this).text().split('');
var rdmltr = randomXToY(0,exploded.length);
// Make sure we don't get a space character or undefined
while(exploded[rdmltr] == ' ' || exploded[rdmltr] == undefined) {
rdmltr = randomXToY(0,exploded.length);
}
// Wrap the letter with a span that has the new class brokenType
// and update it in the array
exploded[rdmltr] = '<span class="brokenType">' + exploded[rdmltr] + '</span>';
// Update the content
$(this).html(exploded.join(''));
});

Categories

Resources