0

I am building a small web application, which includes a webpage that is updated regularly via a websocket. Each message is a string containing many HTML tags, including LaTeX code. The content of this string is put in a div container. Then I use renderMathInElement from KaTeX to interpret the LaTeX parts.

The Problem: This works fine for small messages, but for large strings, the site starts flickering, when inserting the new content. The flickering only occurs in the deployed version, which irritates me, because my Firefox should run the javascript code always with the same speed. The fact, that the loading times are slower for the deployed version, should not change the efficiency of the javascript code.

Code

// connect the websocket
if (location.protocol == 'https:') {
    var socket = new WebSocket("wss://" + location.host + "/lectureserver", "lecture");
} else {
    var socket = new WebSocket("ws://" + location.host + "/lectureserver", "lecture");
}

var last = ""; // last message
var content = document.getElementById("lecture"); // div displaying content
var content_hidden = document.getElementById("lecture_hidden"); // hidden element to prepare content

socket.onerror = function (error) {
    console.log("unable to connect");
    content.innerHTML = "<p>Error. Can't connect!</p>";
};

socket.onmessage = function (event) {
    // check if data didn't change or is empty
    if (event.data == last || event.data == "") {
        return;
    }
    // save current vertical scroll position
    var scrollY = window.scrollY;
    // and old max height (to determine if client scrolled to the very bottom)
    var oldMax = document.body.scrollHeight - document.body.clientHeight;
    // save the received data
    var newContent = event.data;
    // fill the hidden element with the content
    content_hidden.innerHTML = newContent;
    // evaluate the math (KaTeX)
    renderMathInElement(content_hidden, {
        delimiters: [
            {left: "\\[", right: "\\]", display: true},
            {left: "\\(", right: "\\)", display: false},
            {left: "\\begin{align\*}", right: "\\end{align\*}", display: true},
            {left: "$", right: "$", display: false}
        ],
        macros: {
            "\\Q": "\\mathbb{Q}",
            "\\C": "\\mathbb{C}"
        },
        strict: false
    });
    // replace old content with now rendered and prepared new content
    content.innerHTML = content_hidden.innerHTML;
    // reset last content
    last = event.data;

    // keep window in place
    if (scrollY >= oldMax) {
        window.scrollTo(scrollX, 100000);
    } else {
        window.scrollTo(scrollX, scrollY);
    }
};

As you can see, I added a hidden div, that prepares the content first before replacing the real content.innerHTML with the new content. But this change, doesn't solve the flickering issue.

If you wonder, even removing the renderMathInElement part doesn't resolve the issue. The flickering is faster, but still there.

Is the javascript websocket interface in any way lazy, that might cause the difference between the deployed and the local instance? If it's strict, shouldn't make the traffic speed any difference, as the message is already completely transferred when the processing starts?

Please let me know, if you need clarification.

Erich
  • 1,838
  • 16
  • 20
  • 1
    Don't live-update your document with every single thing. Create a detached `div`, do your work in that, and only when you're done, swap that div into your document. Which includes not using `innerHTML`. Build a new element, fill it, and then put that element in your DOM, so that you end up with a single content reflow operation. – Mike 'Pomax' Kamermans Jan 09 '20 at 23:49
  • What Mike said. Make sure you facilitate the swap by using the dom methods `appendChild` and `removeChild`, and *not* by using innerHTML. – Travis J Jan 09 '20 at 23:50
  • @Mike'Pomax'Kamermans How do I create a detached `div`? – Erich Jan 09 '20 at 23:51
  • 1
    by just making a div? `const newDiv = document.createElement('div')`, and then you work with that. Also, remember that `innerHTML` is _incredibly dangerous_ if you're dealing with user generated content. Unless you rigidly control that content, someone could trivially include code like `` and put literally any JS they want in there. Including reading cookies, checking for webextensions, etc. etc. and sending that to their own server using the fetch API, generating a link element, etc. etc. – Mike 'Pomax' Kamermans Jan 09 '20 at 23:52
  • I placed the `const newDiv = document.createElement('div')` into my `socket.onmessage` function, filled the `newDiv.innerHTML` with my content, then removed the old div from `content` with `removeChild` and added the `newDiv` with `appendChild` but the flickering is still there. – Erich Jan 10 '20 at 00:02
  • since you remove and add, that would cause two reflows – Jaromanda X Jan 10 '20 at 00:03
  • But don't I have to remove my old content `div`, before appending the `newDiv`? – Erich Jan 10 '20 at 00:05
  • try ... `replaceWith` or `replaceChild` methods -also, how often is the message fired? – Jaromanda X Jan 10 '20 at 00:07
  • `replaceWith` made no difference. The messages are only transferred once per update, but interestingly the message in `event.data` is received in chunks, the final one being the complete one, so sometimes the `replaceWith` get's called thrice in a row. Is this a problem with my `event.data` usage? Does this not get the whole content, once it's available? This would explain the difference between my local instance and the deployed one. – Erich Jan 10 '20 at 00:31

0 Answers0