How to ... emulate a terminal-like caret with plain css and javascript(optionally unobtrusive)?
NOTES: This is just a guide which shows you steps on creating a terminal-like caret, therefore I do not have any plug-and-play library for creating this. This is nothing than just a proof-of-concept that this effect can be acquired without any advanced techniques(which includes disabling keyboard actions to global{browser based} actions, et cetera)
WARNING: I do not recommend using this technique in any real-world-applications as this could be really annoying and confusing to the user, therefore this might only be used where it is absolutely needed like demonstrations, et cetera.
Enough warnings and notes, let's begin.
What we are making?
I hope you've seen those fancy web-apps that have a [customized] caret(the thin thing that enables you to type, or "trace" where you are)? I first came across such a thing at http://masswerk.at/jsuix/index.html I was fascinated when I saw that and I wanted to know how it was made. After trying for ages I finally gave up. After some months I came across another application which had the same effect at: http://tryruby.hobix.com/ this time I decided to find out how it was made but still failed
when some days ago I had this brilliant idea which showed a beam of light on how it could be done, so this morning I sat in-front of my computer and began my journey and it seems I succeeded. So here I am to share the technique I used.
BTW if you haven't seen any of those already, check this out: http://shachi.prophp.org/demo.html
Click on the red caret to activate and start typing.
That's what we are going to make today.
How is it going to work?
Sshhh ... this is a secret.
We will have a plain textarea somewhere in the screen out of the view of the viewer and when the user clicks on our "fake terminal" we will focus into the textarea and when the user starts typing we will simply append the data typed into the textarea to our "terminal" and that's that.
The code
Let me take this part step-by-step, the full source will be available at the end.
The HTML
Lets start with the HTML part, here's the basic skeleton:
Code:
<html>
<head>
<script type="text/javascript">
// js(will be described later)
</script>
<style type="text/css">
// css(will be described later)
</style>
</head>
<body>
<div id="terminal" onclick="$('setter').focus();">
<textarea type="text" id="setter" onkeydown="writeit(this, event);moveIt(this.value.length, event)" onkeyup="writeit(this, event)" onkeypress="writeit(this, event);"></textarea>
<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>
</div>
</body>
</html>
That's seems pretty self explanatory. Let me just go through the main things you need to know here:
Code:
<div id="terminal" ...>...</div>
This is the main container where all your elements go.
Code:
<textarea type="text" id="setter" ...></textarea>
This is the "out-of-sight" textarea.
Code:
<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>
This is the place where the "fake caret" and the content goes.
The Css
The css is also very self explanatory and I don't think I need to describe it as it's your own choice to customise it.
Code:
body {
margin: 0px;
padding: 0px;
height: 99%;
}
textarea#setter { /* explorer doesn't support [att=val] selector :( */
left: -1000px;
position: absolute;
}
.cursor {
font-size: 12px;
background-color: red;
color: red;
position: relative;
opacity: 0.5;
}
#terminal {
margin: 8px;
cursor: text;
height: 500px;
}
#writer {
font-family: cursor, courier;
font-weight: bold;
}
#getter {
margin: 5px;
}
The javascript
The next thing is the javascript:
Code:
function $(elid){ /* shortcut for d.gEBI */
return document.getElementById(elid);
}
var cursor; /* global variable */
window.onload = init;
function init(){
cursor = $("cursor"); /* defining the global var */
cursor.style.left = "0px"; /* setting it's position for future use */
}
function nl2br(txt){ /* helper, textarea return \n not <br /> */
return txt.replace(/\n/g, "<br />");
}
function writeit(from, e){ /* the magic starts here, this function requires the element from which the value is extracted and an event object */
e = e || window.event; /* window.event fix for browser compatibility */
var w = $("writer"); /* get the place to write */
var tw = from.value; /* get the value of the textarea */
w.innerHTML = nl2br(tw); /* convert newlines to breaks and append the returned value to the content area */
}
function moveIt(count, e){ /* function to move the "fake caret" according to the keypress movement */
e = e || window.event; /* window.event fix again */
var keycode = e.keyCode || e.which; /* keycode fix */
// alert(count); /* for debugging purposes */
if(keycode == 37 && parseInt(cursor.style.left) >= (0-((count-1)*10))){ // if the key pressed by the user is left and the position of the cursor is greater than or equal to 0 - the number of words in the textarea - 1 * 10 then ...
cursor.style.left = parseInt(cursor.style.left) - 10 + "px"; // move the cursor to the left
} else if(keycode == 39 && (parseInt(cursor.style.left) + 10) <= 0){ // otherwise, if the key pressed by the user if right then check if the position of the cursor + 10 is smaller than or equal to zero if it is then ...
cursor.style.left = parseInt(cursor.style.left) + 10 + "px"; // move the "fake caret" to the right
}
}
function alert(txt){ // for debugging
console.log(txt); // works only with firebug
}
And now the ....
Final Code(comments ripped):
Code:
<html>
<head>
<script type="text/javascript">
function $(elid){
return document.getElementById(elid);
}
var cursor;
window.onload = init;
function init(){
cursor = $("cursor");
cursor.style.left = "0px";
}
function nl2br(txt){
return txt.replace(/\n/g, "<br />");
}
function writeit(from, e){
e = e || window.event;
var w = $("writer");
var tw = from.value;
w.innerHTML = nl2br(tw);
}
function moveIt(count, e){
e = e || window.event;
var keycode = e.keyCode || e.which;
// alert(count);
if(keycode == 37 && parseInt(cursor.style.left) >= (0-((count-1)*10))){
cursor.style.left = parseInt(cursor.style.left) - 10 + "px";
} else if(keycode == 39 && (parseInt(cursor.style.left) + 10) <= 0){
cursor.style.left = parseInt(cursor.style.left) + 10 + "px";
}
}
function alert(txt){
console.log(txt);
}
</script>
<style type="text/css">
body {
margin: 0px;
padding: 0px;
height: 99%;
}
textarea#setter {
left: -1000px;
position: absolute;
}
.cursor {
font-size: 12px;
background-color: red;
color: red;
position: relative;
opacity: 0.5;
}
#terminal {
margin: 8px;
cursor: text;
height: 500px;
overflow: auto;
}
#writer {
font-family: cursor, courier;
font-weight: bold;
}
#getter {
margin: 5px;
}
</style>
</head>
<body>
<div id="terminal" onclick="$('setter').focus();">
<textarea type="text" id="setter" onkeydown="writeit(this, event);moveIt(this.value.length, event)" onkeyup="writeit(this, event)" onkeypress="writeit(this, event);"></textarea>
<div id="getter">
<span id="writer"></span><b class="cursor" id="cursor">B</b>
</div>
</div>
</body>
</html>
Browser Compatibility:
As far as I know it works in FF 2.0.0.1 and IE4Lin 6.0. Haven't tested on any other browser yet.
What about the Optionally Unobtrusive part?
To make it unobtrusive, things you can do:
i) Create the main div(id="terminal"), and the getter div(id="getter" and all it's contents) dynamically through javascript
ii) move the textarea to the far left(-1000px) with javascript again.
PS.
I have no idea how http://masswerk.at/jsuix/index.html or http://tryruby.hobix.com/ created their carets so this is not the exact same technique they used, as far as I know, the way they did it, it cancels (almost)all global browser shortcuts.
Hope you like it, bug reports or problems are welcome. 
EDIT:BUG FOUND:When you type a few things and hit enter and then try to go back to the first line, the cursor simultaneously goes to the left and not to the first line. I am searching for a fix for this but cannot find anything(yet).
Bookmarks