Monday, October 15, 2012

On-Zoom and the ACE Editor

‹prev | My Chain | next›

Last night, I fixed a relatively minor ACE code editor bug in my fork of Mr Doob's code editor. When the window or viewport resized (e.g. when the JavaScript console was shown), the contents of the ACE editor would seemingly disappear.

The fix was small so I contented myself that ACE was still likely a more robust solution than CodeMirrror and its odd Chrome paint issue in the code editor. Today, however, I found another display issue.

When the browser's text is zoomed / unzoomed, ACE's display gets all out of whack. The most visually obvious problem is when ACE no longer fills up the viewport:


Worse is when ACE will not scroll the bottom of the editor contents into view.

There are two ways that I can try to handle this. The first is to intercept Ctrl++ or Ctrl+- key events (as well as Ctrl + Mouse Scroll events) to prevent the normal means of increasing and decreasing font size. The problem with this approach is that, if a user zooms the font through other means (e.g. from a menu), then the problem still exists.

I know how to resolve the problem. The same solution from last night will work again tonight. I simply need to invoke the not-a-callback onResize() method. I might do so every 2 seconds:
setInterval(function() {
  ace.renderer.onResize(true);
}, 2000);
The problem with this approach, besides needing to compensate endlessly for a bug, is that there is a noticeable flicker in the ACE contents from time-to-time.

What I would really like is an on-zoom event listener. I even try adding a "zoom" listener to both the document and the window in the vain hopes that it might exist. It does not—at least not with that name. There are demonstration hacks that describe how to determine new zoom levels. I do not need that amount of information—I only need to know if the text has re-zoomed, not the new zoom level.

So I borrow from that hack to absolutely position an element to the left by a percentage. When the zoom changes, that percentage will change. I then only need to check the offsetLeft occasionally to see if it has changed. Only if is had changed will I invoke the onResize() method:
var zoomEl = document.createElement('div');
zoomEl.style.left = '-50%';
zoomEl.style.width = '1px';
zoomEl.style.height = '1px';
zoomEl.style.position = 'absolute';
document.body.appendChild(zoomEl);

var lastZoom = zoomEl.offsetLeft;
setInterval(function() {
  if (zoomEl.offsetLeft != lastZoom) {
    lastZoom = zoomEl.offsetLeft;
    ace.renderer.onResize(true);
  }
}, 2000);
It is not pretty and it can take upwards of 2 seconds for a bad display to correct. But in the end it works.

This solution is very reminiscent of the CodeMirror repaint solution. Since that repaint issue was one of the deciding factors in switching to ACE, I may have to revisit that decision. That said, I can (and will) add the key and mouse zoom handlers to prevent the usual zooms. If the programmer goes out of her way to zoom after all of that, she can likely abide a 2 second wait for ACE to resize.


Day #540

2 comments:

  1. Hi.

    Zoom problem happens because your resize handler is called after the one from ace the best fix for this is to set top/bottom to 0 like this and let browser resize the element automatically

    if it doesn't fix the problem please create an issue on https://github.com/ajaxorg/ace/issues

    ReplyDelete
    Replies
    1. Nice. That is rock solid. Nary a flicker when I resize things. Thanks!

      Delete