8

I am working on a terminal emulator for fun and have the basics of the backend up and running. However I keep running into performance problems on the frontend.

As you all probably know is that each character in a terminal window can have a different style. (color, backdrop, bold, underline etc). So my idea is was to use a <span> for each character in the view window and apply an inline style if necesary so I have the degree of control I need.

The problem is that the performance is horrendous on a refresh. Chrome can handle it on my pc with about 120 ops per second and firefox with 80. But internet explorer barely gets 6. So after my stint with html I tried to use canvas but the text on a canvas is ultra slow. Online I read caching helps so I implement a cache for each character and could apply colors to the then bitmapped font with some composite operation. However this is way way slower than DOM.

Then I went back to the dom and tried using document.createDocumentFragment but it performs a little bit worse then just using the standard.

I have no idea on where to begin optimization now. I could keep track on what character changes when but then I will still run into this slowness when the terminal gets a lot of input.

I am new to the DOM so I might do something completely wrong...

any help is appreciated!

Here is a jsperf with a few testcases:

http://jsperf.com/canvas-bitma32p-cache-test/6

  • You should use a CSS file instead of inline styles. See http://stackoverflow.com/a/8284398/5111146. – James Brierley Oct 14 '15 at 21:22
  • Also http://jsperf.com/style-element-vs-attr – James Brierley Oct 14 '15 at 21:23
  • Try to use one span for each instance of a style. For example, if the style never changes, one `span` tag should do. If the style changes once and then changes back to the original style, three `span` tags should do. – Tyler Crompton Oct 14 '15 at 21:24
  • @TylerCrompton yes true but I wanted to test the worse case which is obviously each character being different. –  Oct 14 '15 at 21:28
  • Is that justifiable? Have you analyzed any use cases that would cause that to happen? Sometimes "good enough" is the best option that you have. – Tyler Crompton Oct 14 '15 at 21:40
  • @TylerCrompton Well it probably would almost never happen but I would say that it is a little ridiculous that I cannot replicate a technology that has been available since the 90 natively in a browser because of performance problems. If this cannot be solved like this I willl probably write a webgl render. –  Oct 14 '15 at 21:47
  • A browser is a far more general rendering engine than a text-only terminal. A specialized device like a terminal can usually perform better within its limited domain than a general application can. – Barmar Oct 14 '15 at 21:52
  • I did something like this in a Java applet, and then in Silverlight using XAML, which was a really bad fit with what XAML is good at. Both performed just fine... but I confined myself to having one color per line, not per character. I also did that in HTML with no trouble. I would not expect Canvas to be that much worse than those plugin technologies, but I guess it's not astonishing. [continued ->] – Paul Kienitz Oct 14 '15 at 23:19
  • If you want to go back to do it in HTML, I strongly suggest that instead of separating out every character, you make a single span out of consecutive runs of characters that use the same style. Minimize the number of tags rendered to be as small as necessary to separate the different styles. At least per line -- I wouldn't want to try handling word-wrapping in one of those style spans. – Paul Kienitz Oct 14 '15 at 23:19
  • Do I get it right that you're *redrawing* on every cycle? If so, why not try to only redraw the changed elements? If you don't want to implement that yourself, have a look at [React](https://facebook.github.io/react/). – Yoshi Oct 15 '15 at 08:39
  • I added an case to your jsperf using classes instead. http://jsperf.com/canvas-bitma32p-cache-test/7 – James Brierley Oct 16 '15 at 15:03
  • Is your canvas being updated on an off-screen buffer and merely copied to the canvas in-view? Are you only redrawing the dirty regions of the canvas instead of redrawing the entire canvas every animationFrame? Have you tried limiting the style manipulation to only the nodes within the bounds of the viewport? – jeffjenx Oct 29 '15 at 18:32
  • What about using the html5 canvas element or an iframe with contentEditable and the document.execCommand function ? – Nimmi Nov 09 '15 at 17:09

1 Answers1

1

Direct insertion of HTML as string text is surprisingly efficient when you use insertAdjacentHTML to append the HTML to an element.

var div = document.getElementById("output");
var charCount = 50;
var line, i, j;

for (i = 0; i < charCount; i++) {
  line = "";
  for (j = 0; j < charCount; j++) {
    line += "<span style=\"background-color:rgb(0,0,255);color:rgb(255,127,0)\">o</span>";
  }
  div.insertAdjacentHTML("beforeend","<div>"+line+"</div>");
}
#output{font-family:courier; font-size:6pt;}
<div id="output"></div>

The downside of this approach is obvious: you never get the chance to treat each appended element as an object in JavaScript (they're just plain strings), so you can't, for example, directly attach an event listener to each of them. (You could do so after the fact by querying the resultant HTML for matching elements using document.querySelectorAll(".css selector).)

If you're truly just formatting output being printed to the screen, insertAdjacentHTML is perfect.

Thriggle
  • 7,009
  • 2
  • 26
  • 37