Page 1 of 2 12 LastLast
Results 1 to 10 of 19

Thread: Emulating a terminal-like caret with javascript and css.

  1. #1
    Join Date
    Aug 2005
    Posts
    971
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Lightbulb Emulating a terminal-like caret with javascript and css.

    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&#37;;
    			}
    			
    			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).
    Last edited by shachi; 02-18-2007 at 07:17 AM.

  2. #2
    Join Date
    Aug 2005
    Posts
    971
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Default

    53 views and no comments? Must be something wrong.

  3. #3
    Join Date
    Jun 2006
    Location
    Acton Ontario Canada.
    Posts
    677
    Thanks
    0
    Thanked 1 Time in 1 Post

    Default

    seems like a good idea, but what practical uses could it employ?

    it would have to be secured with a password for sending SQL requests, php scripts, etc. if you could define a set of commands, send via ajax on enter, this would make an excellent environment for trying out new code.
    - Ryan "Boxxertrumps" Trumpa
    Come back once it validates: HTML, CSS, JS.

  4. #4
    Join Date
    Aug 2005
    Posts
    971
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Default

    boxxertrumps: sure you can send ajax calls when hit enter. It's really easy, just to detect if the enter key is pressed and if it is, do the ajax call and return false.

  5. #5
    Join Date
    Jun 2006
    Location
    Acton Ontario Canada.
    Posts
    677
    Thanks
    0
    Thanked 1 Time in 1 Post

    Default

    JS noob... Remember? i was kindof asking you to create a class/function that returns the last line in an easy to transfer manner...

    works well, good job. But instead of the solid block, having it animated would look better. i have the perfect pic. once I get home, ill play with the styles to see if i can give it more of a DOS feel...
    - Ryan "Boxxertrumps" Trumpa
    Come back once it validates: HTML, CSS, JS.

  6. #6
    Join Date
    Aug 2005
    Posts
    971
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Default

    I guess you didn't read the notice:

    Code:
    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
    The blinking can be easily done.

    Here's the function(to make it blinkable):

    Code:
    function blink(){ 
    var div = document.getElementById("id_of_the_cursor"); 
    if(div.style.display == "none"){ 
    div.style.display = "block"; 
    } else {
    div.style.display = "none";
    }
    }
    
    setInterval("blink()", 500);
    May be I can help you with what you are trying to achieve?
    Last edited by shachi; 02-15-2007 at 04:33 PM.

  7. #7
    Join Date
    Jun 2006
    Location
    Acton Ontario Canada.
    Posts
    677
    Thanks
    0
    Thanked 1 Time in 1 Post

    Default

    Block elements are displayed on a new line, so i changed this:
    div.style.display = "inline";
    only minor changes, but i have them HERE.
    but to the best of my knowlege, seperating lines could be done in php with the explode function, explode("\n\r",$full) then delete the previosly used lines.
    ill write something up...
    Would you be able to write a function that gets all of the content written by the carret?
    - Ryan "Boxxertrumps" Trumpa
    Come back once it validates: HTML, CSS, JS.

  8. #8
    Join Date
    Aug 2005
    Posts
    971
    Thanks
    0
    Thanked 0 Times in 0 Posts

    Default

    Would you be able to write a function that gets all of the content written by the carret?
    Sure I can, but you need to provide some more information. Do you want the caret-written data to just remain there or disappear? Where(and how) will you send the data?

  9. #9
    Join Date
    Jun 2006
    Location
    Acton Ontario Canada.
    Posts
    677
    Thanks
    0
    Thanked 1 Time in 1 Post

    Default

    i want to send the data on the last line via ajax POST then load the contents of the ajaxed document into a side/bottom div. it doesn't really matter if the previous commands stay or not. i just want to turn this into a fun testing enviroment.

    im going to have php files commands tested as something like this..
    $encname = $testdir . md5($final) .".php";
    $handle = fopen($encname,"a");
    $data = strstr("<?php",$final);
    fwrite($handle,$data);
    include "$encname";
    unlink($encname);
    fclose($handle);
    - Ryan "Boxxertrumps" Trumpa
    Come back once it validates: HTML, CSS, JS.

  10. #10
    Join Date
    Jun 2005
    Location
    英国
    Posts
    11,876
    Thanks
    1
    Thanked 180 Times in 172 Posts
    Blog Entries
    2

    Default

    Code:
    function $(elid){
            return document.getElementById(elid);
    }
    $ is a terrible name for a function. Prototype was evidently written by a deranged Perl scripter.
    w.innerHTML = nl2br(tw);
    It would be nice to avoid innerHTML, especially since writing directly to innerHTML in this case would break the script in KHTML (deleting any spaces as soon as they're written).

    On a purely stylistic note, eight spaces per block is far too much indentation for practical use.

    Of course, the easy way to do this is:
    Code:
      <style type="text/css">
        input.stdin, span.cursor {
          font-family: monospace;
        }
    
        input.stdin {
          width: 0;
        }
      </style>
    </head>
    <body>
      <p>
        <input type="text" id="stdin">
        <span id="cursor" style="display: none;">_</span>
        <script type="text/javascript">
          var e = document.getElementById("cursor");
          e.className = "cursor";
          e.onfocus = e.onclick = function() {
            setTimeout(
              function() {
                document.getElementById("stdin").focus();
              },
              0
            );
          };
          e = document.getElementById("stdin");
          e.onkeyup = function() {
            this.style.width = this.value.length + ".1em";
          };
          e.className = "stdin";
          e = null;
        </script>
      </p>
    There are some improvements that could be made, but that's the gist of it.
    Twey | I understand English | 日本語が分かります | mi jimpe fi le jbobau | mi esperanton komprenas | je comprends français | entiendo español | tôi ít hiểu tiếng Việt | ich verstehe ein bisschen Deutsch | beware XHTML | common coding mistakes | tutorials | various stuff | argh PHP!

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •