Basj Basj - 3 months ago 15
HTML Question

1000 DOM elements on a single page

For a project of big "text map" (Live demo), I need to have more than 1000 text inputs.
When you click + drag, you can "pan" the displayed area.

But the performance is very poor (both on Firefox and Chrome) : rendering 1000+ DOM elements is not fast at all.

Of course, another solution with better performance would be : work on a

<canvas>
, render text as bitmap on it, and each time we want to edit text, let's show a unique DOM
<textarea>
, that disappears what editing is finished, and text is rendered as bitmap again... It works (I'm currently working in this direction) but it needs much more code in order to provide editing on a canvas.


Question : Is it possible to improve performance for rendering of 1000+ DOM elements on a HTML page (Live demo), so that I don't need to use
<canvas>
at all ?


Or will it be impossible to have good performance when panning a page with 1000+ DOM elements ?

enter image description here




Notes :

1) In the demo here I use
<span contendteditable="true">
because I want multiline input + autoresize, but the rendering performance is the same with standard
<textarea>
.*

2) For reference, this is how I create the 1000 text elements.

for (i=0; i < 1000; i++)
{
var blax = (Math.random()-0.5)*3000;
var blay = (Math.random()-0.5)*3000;
var tb = document.createElement('span');
$(tb).data("x", blax / $(window).width());
$(tb).data("y", blay / $(window).height());
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.position = 'absolute';
tb.style.top = blay + 'px';
tb.style.left = blax + 'px';
tb.innerHTML="newtext";
document.body.appendChild(tb);
}

Answer

A solution was given by @Shmiddty which is much faster to all previous attempts : all elements should be wrapped, and only the wrapper has to be moved (instead of moving each element) :

http://jsfiddle.net/qhskacsw/

It runs smooth and fast even with 1000+ DOM elements.


var container = document.createElement("div"),
    wrapper = document.createElement("div"),
    dragging = false,
    offset = {x:0, y:0},
    previous = {x: 0, y:0};

container.style.position = "absolute";
wrapper.style.position = "relative";
container.appendChild(wrapper);
document.body.appendChild(container);

for (var i = 1000, span; i--;){
    span = document.createElement("span");
    span.textContent = "banana";
    span.style.position = "absolute";
    span.style.top = (Math.random() * 3000 - 1000 | 0) + 'px';
    span.style.left = (Math.random() * 3000 - 1000 | 0) + 'px';

    wrapper.appendChild(span);
}


// Don't attach events like this. 
// I'm only doing it for this proof of concept.

window.ondragstart = function(e){
    e.preventDefault();
}

window.onmousedown = function(e){
    dragging = true;
    previous = {x: e.pageX, y: e.pageY};
}

window.onmousemove = function(e){
    if (dragging){
        offset.x += e.pageX - previous.x;
        offset.y += e.pageY - previous.y;
        previous = {x: e.pageX, y: e.pageY};

        container.style.top = offset.y + 'px';
        container.style.left = offset.x + 'px';
    }
}

window.onmouseup = function(){
    dragging = false;
}