如何渲染数以万计的元素?

How can I render tens of thousands of elements?

在你说我疯了之前,请相信我,我知道。我不会选择一个呈现速度快、加载速度快或灯塔得分高的网站。我只是想让它工作。

我有一些 javascript 可以拾取图像的所有像素颜色。使用此函数,我创建了一个 1px x 1px 的 div 元素,并将背景颜色设置为相同坐标的像素颜色。然后坐标用于设置顶部和左侧的值。我的代码按照它所说的去做。

这是我的问题,我的图像是 700 像素 x 387 像素。如果您进行数学计算,结果为 270,900 html 个元素。 Chrome,根本不是为了这种疯狂而建造的。我想看这个作品,我想 "manually" 创建一个包含 div 元素的图像,不知何故。当我尝试这样做时,我的 cpu 达到最大值,我确信我最终会 运行 超出 ram。

如果我只尝试几百或几千像素,一切正常,但再多一点,chrome 就死了。我不确定它是否在浏览器中计算可能是我的问题,或者 chrome 是否无法显示这么多元素,或两者兼而有之。我想我可以在我的服务器上使用 python 进行相同的计算,并将其附加到 html,但是 chrome 可能无法显示它。

显然,这不是特别重要,只是有趣。我认为社区也会享受挑战。

此处计算 100 个像素:

onload = e => {

  function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }

  function rgbToHex(r, g, b) {
    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
  }

  function img(x, y) {
    var img = document.getElementById('my-image');
    var canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);

    var pixelData = canvas.getContext('2d').getImageData(x, y, 1, 1).data;
    return rgbToHex(pixelData[0], pixelData[1], pixelData[2]);
  }
  //x = 700 y = 387
  for (var x = 0; x < 10; x++) {
    for (var y = 0; y < 10; y++) {
      document.body.insertAdjacentHTML("beforeend", "<div style='top:" + y + "px; left:" + x + "px;background:" + img(x, y) + ";' />");
    }
  }
};
div {
  position: absolute;
  width: 1px;
  height: 1px;
}
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/New_born_Frisian_red_white_calf.jpg/640px-New_born_Frisian_red_white_calf.jpg" id="my-image" crossorigin="anonymous">

这里正在计算 2500px(仍然有效,需要一段时间)

onload = e => {
  function componentToHex(c) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
  }

  function rgbToHex(r, g, b) {
    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
  }

  function img(x, y) {
    var img = document.getElementById('my-image');
    var canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);

    var pixelData = canvas.getContext('2d').getImageData(x, y, 1, 1).data;
    return rgbToHex(pixelData[0], pixelData[1], pixelData[2]);
  }
  //x = 700 y = 387
  for (var x = 0; x < 50; x++) {
    for (var y = 0; y < 50; y++) {
      document.body.insertAdjacentHTML("beforeend", "<div style='top:" + y + "px; left:" + x + "px;background:" + img(x, y) + ";' />");
    }
  }
};
div {
  position: absolute;
  width: 1px;
  height: 1px;
}
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/New_born_Frisian_red_white_calf.jpg/640px-New_born_Frisian_red_white_calf.jpg" id="my-image" crossorigin="anonymous">

干杯,艾萨克。

目前,您正在为每个像素执行以下操作

  • 创建canvas
  • 获取上下文并绘制到图像
  • 获取上下文并获取一个像素的像素数据
  • 创建 DIV
  • 将其添加到 DOM

现在,让我们简化一下

以下可以一次性完成

  • 创建 canvas
  • 获取上下文
  • 使用上下文绘制图像
  • 使用上下文获取图像数据
  • 创建一个空字符串

现在,对于每个像素

  • 获取像素数据
  • 将 div 的 html 添加到字符串

最后,只有一次

  • 将包含所有 div 的字符串添加到 DOM

类似于:

const componentToHex = c => c.toString(16).padStart(2, '0');
const rgbToHex = (r, g, b) => `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
const img = document.getElementById('my-image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const w = canvas.width = img.width;
const h = canvas.height = img.height;
context.drawImage(img, 0, 0, img.width, img.height);
const imageData = context.getImageData(0, 0, w, h).data;
const pixel = (x, y) => {
    const index = (w * y + x) * 4;
    return rgbToHex(imageData[index], imageData[index + 1], imageData[index + 2]);
}
const df = document.createDocumentFragment();
for (let y = 0; y < img.height; y++) {
    for (let x = 0; x < img.width; x++) {
        const div = df.appendChild(document.createElement('div'));
        div.style.top = y + "px";
        div.style.left = x + "px";
        div.style.backgroundColor = pixel(x, y);
    }
}
document.body.appendChild(df);

注意:现在可能不是这样,但这样的循环在函数内部可能运行得更快——通常在全局上下文中的循环更慢

因此,您可以将上面的整个代码包装在

(() => {
    // the code from above
})();

再次看到显着改善 - 不确定,过去几年曾经是这种情况

changed to use document fragment for a further 25% speed improvement
Now takes 1.4 seconds in firefox for a 640x480 image, 2.3 seconds in chrome - which didn't really see a big difference between using insertAdjacentHTML vs a document fragment

another thing to note. In Firefox the page becomes sluggish, in chrome, for 640x480, no such issue