javascript - 单个页面上有 1000 个 DOM 元素

标签 javascript html dom html5-canvas

大“文字 map ”项目BigPicture ,我需要有超过 1000 个文本输入。 点击+拖动时,可以“平移”显示区域。

但性能非常差(在 Firefox 和 Chrome 上):渲染 1000 多个 DOM 元素一点也不快。

当然,另一个性能更好的解决方案是:在 <canvas> 上工作,将文本渲染为位图,每次我们想要编辑文本时,让我们显示一个独特的 DOM <textarea> ,编辑完成后消失,文本再次呈现为位图......有效(我目前正在朝这个方向努力)但它需要更多代码才能提供编辑 Canvas 。

问题:是否可以提高在 HTML 页面上呈现 1000 多个 DOM 元素的性能,以便我不需要使用 <canvas>完全没有?

或者当一个包含 1000 多个 DOM 元素的页面时,是否无法获得良好的性能?

enter image description here


注释:

1) 在这里的演示中我使用 <span contendteditable="true">因为我想要多行输入 + 自动调整大小,但渲染性能与标准 <textarea> 相同.*

2) 作为引用,这就是我创建 1000 个文本元素的方式。

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);
}

最佳答案

对于这样的事情,您可以使用文档片段,这些是不属于实际 DOM 树的 DOM 节点(可以在此处找到更多信息 https://developer.mozilla.org/en-US/docs/Web/API/document.createDocumentFragment ),因此您可以在片段上进行所有设置然后附加只会导致一次重新流而不是 1000 次的片段。

所以这是一个例子 - http://jsfiddle.net/leighking2/awzoz7bj/ - 快速检查运行时间大约需要 60-70 毫秒才能运行

var currentzoom = 1;
var docFragment = document.createDocumentFragment();
var start = new Date();
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";
  docFragment.appendChild(tb);
}

document.body.appendChild(docFragment);

var end = new Date();
console.log(end-start)

与运行大约 645 毫秒的原始版本相比 http://jsfiddle.net/leighking2/896pusex/

更新 因此,为了再次提高拖动速度,请将单个编辑保持在 DOM 之外,以避免每次鼠标拖动时回流 1000 次的成本

所以这是使用 jquery 的 detach() 方法的一种方法(示例 http://jsfiddle.net/sf72ubdt/ )。这将从 DOM 中删除元素,但将它们及其所有属性提供给您,以便您可以操作它们并稍后重新插入它们

redraw = function(resize) {
    //detach spans
    var spans = $("span").detach();
    //now loop other them, because they are no longer attached to the DOM any changes are
    //not going to cause a reflow of the page
    $(spans).each(function(index) {
        var newx = Math.floor(($(this).data("x") - currentx) / currentzoom * $(window).width());
        var newy = Math.floor(($(this).data("y") - currenty) / currentzoom * $(window).height());

        if (resize) {
            displaysize = Math.floor($(this).data("size") / currentzoom);
            if (displaysize) {
                $(this).css({
                    fontSize: displaysize
                });
                $(this).show();
            } else
                $(this).hide();
        }
        //changed this from offset as I was getting a weird dispersing effect around the mouse
        // also can no longer test for visible but i assume you want to move them all anyway.
        $(this).css({
            top: newy + 'px',
            left: newx + 'px'
        });
    });
    //reattach to the body
    $("body").append(spans);

};

更新 2 -

因此,为了从中获得更多性能,您可以缓存窗口宽度和高度,使用 vanilla for 循环,使用 vanilla js 更改跨度的 css。现在每次重绘(在 chrome 上)大约需要 30-45 毫秒(http://jsfiddle.net/leighking2/orpupsge/),而我上面的更新大约需要 80-100 毫秒(http://jsfiddle.net/leighking2/b68r2xeu/)

所以这是更新后的重绘

redraw = function (resize) {
    var spans = $("span").detach();
    var width = $(window).width();
    var height = $(window).height();

    for (var i = spans.length; i--;) {
        var span = $(spans[i]);
        var newx = Math.floor((span.data("x") - currentx) / currentzoom * width);
        var newy = Math.floor((span.data("y") - currenty) / currentzoom * height);
        if (resize) {
            displaysize = Math.floor(span.data("size") / currentzoom);
            if (displaysize) {
                span.css({
                    fontSize: displaysize
                });
                span.show();
            } else span.hide();
        }


        spans[i].style.top = newy + 'px',
        spans[i].style.left = newx + 'px'
    }

    $("body").append(spans);
};

片段示例 -

var currentzoom = 1;
var docFragment = document.createDocumentFragment();
var start = new Date();
var positions = []
var end = new Date();
console.log(end - start);

var currentx = 0.0,
currenty = 0.0,
currentzoom = 1.0,
xold = 0,
yold = 0,
button = false;

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";
docFragment.appendChild(tb);
}
document.body.appendChild(docFragment);

document.body.onclick = function (e) {
if (e.target.nodeName == 'SPAN') {
    return;
}
var tb = document.createElement('span');

$(tb).data("x", currentx + e.clientX / $(window).width() * currentzoom);
$(tb).data("y", currenty + e.clientY / $(window).height() * currentzoom);
$(tb).data("size", 20 * currentzoom);
tb.contentEditable = true;
tb.style.fontFamily = 'arial';
tb.style.fontSize = '20px';
tb.style.backgroundColor = 'transparent';
tb.style.position = 'absolute';
tb.style.top = e.clientY + 'px';
tb.style.left = e.clientX + 'px';
document.body.appendChild(tb);
tb.focus();
};

document.body.onmousedown = function (e) {
button = true;
xold = e.clientX;
yold = e.clientY;
};

document.body.onmouseup = function (e) {
button = false;
};

redraw = function (resize) {
var start = new Date();
var spans = $("span").detach();
var width = $(window).width();
var height = $(window).height();

for (var i = spans.length; i--;) {
    var span = $(spans[i]);
    var newx = Math.floor((span.data("x") - currentx) / currentzoom * width);
    var newy = Math.floor((span.data("y") - currenty) / currentzoom * height);
    if (resize) {
        displaysize = Math.floor(span.data("size") / currentzoom);
        if (displaysize) {
            span.css({
                fontSize: displaysize
            });
            span.show();
        } else span.hide();
    }


    spans[i].style.top = newy + 'px',
    spans[i].style.left = newx + 'px'
}

$("body").append(spans);
var end = new Date();
console.log(end - start);
};

document.body.onmousemove = function (e) {
if (button) {
    currentx += (xold - e.clientX) / $(window).width() * currentzoom;
    currenty += (yold - e.clientY) / $(window).height() * currentzoom;

    xold = e.clientX;
    yold = e.clientY;

    redraw(false);
}
};

$(function () {
$('body').on('mousedown', 'span', function (event) {
    if (event.which == 3) {
        $(this).remove()
    }
})
});

zoomcoef = function (coef) {
middlex = currentx + currentzoom / 2
middley = currenty + currentzoom / 2
currentzoom *= coef
currentx = middlex - currentzoom / 2
currenty = middley - currentzoom / 2
redraw(true)
}

window.onkeydown = function (event) {
if (event.ctrlKey && event.keyCode == 61) {
    zoomcoef(1 / 1.732);
    event.preventDefault();
}
if (event.ctrlKey && event.keyCode == 169) {
    zoomcoef(1.732);
    event.preventDefault();
}
if (event.ctrlKey && event.keyCode == 48) {
    zoomonwidget(1 / 1.732);
    event.preventDefault();
}
};
  html, body {
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

关于javascript - 单个页面上有 1000 个 DOM 元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25910500/

相关文章:

php - 将 PHP 变量传递给 JavaScript getElementById

javascript - 我如何使用简单变量在javascript中找到事件的预计到达时间

html - 让 UL 边框只环绕文本

javascript - 检查图像文件的可见性

javascript - 如何连接两个 getElementsBy

JavaScript 重定向不起作用

javascript - 每当我将光标放在子类别上时,它会关闭下拉菜单和子类别链接

python - Django 静态文件

javascript - 使用 document.addEventListener 破坏 document.getElementById

php - 获取具有特定文本 PHP/XPath 的 DOMElement