Как я могу визуализировать десятки тысяч элементов? - PullRequest
1 голос
/ 08 мая 2020

Перед тем, как вы go скажете, что я сумасшедший, поверьте, я знаю. Я не собираюсь создавать веб-сайт, который быстро отображается, быстро загружается или получает высокий балл Lighthouse. Я просто хочу, чтобы это работало.

У меня есть javascript, который улавливает все цвета пикселей изображения. С помощью этой функции я создаю элемент div размером 1px на 1px и устанавливаю цвет фона на цвет пикселя с теми же координатами. Затем координаты используются для установки верхнего и левого значений. Мой код делает то, что ему говорят.

Вот моя проблема, мое изображение составляет 700 пикселей на 387 пикселей. Если вы сделаете математику, получится 270 900 html элементов. Chrome, просто не для этого безумия. Я хочу увидеть эту работу, я хочу каким-то образом «вручную» создать изображение с элементами div. Мой процессор выходит из строя, когда я пытаюсь это сделать, и я уверен, что в конечном итоге у меня закончится оперативная память.

Все работает нормально, если я пробую только сотни или несколько тысяч пикселей, но еще больше, и 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">

здесь вычисление 2500 пикселей (все еще работает, требует времени)

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">

Ура, Исаа c.

1 Ответ

3 голосов
/ 08 мая 2020

На данный момент вы делаете следующее для каждого пикселя

  • Создание холста
  • получение контекста и рисование изображения
  • получение контекста и получить данные пикселей для одного пикселя
  • создать DIV
  • добавить его в DOM

Теперь давайте оптимизируем это

Следующее может выполняется ОДИН РАЗ

  • создать холст
  • получить контекст
  • использовать контекст для рисования изображения
  • использовать контекст для получения изображения data
  • создать пустую строку

Теперь для каждого пикселя

  • получить данные пикселей
  • добавить html для div в строку

И, наконец, только ОДИН РАЗ

  • Добавьте строку со всеми 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);

Примечание: сейчас это может быть не так, но такие al oop могут работать быстрее внутри функции - часто циклы в глобальном контексте медленнее

Итак, вы могли оберните весь приведенный выше код в

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

и снова увидите значительное улучшение - не уверен, раньше было так в прошедшие годы

изменено на document fragment для дальнейшее повышение скорости на 25% Теперь требуется 1,4 секунды в firefox для изображения 640x480, 2,3 секунды в chrome - что действительно не видит большой разницы между использованием insertAdjacentHTML и фрагмента документа

, еще одна вещь, на которую следует обратить внимание. В Firefox страница становится sluggi sh, в chrome, для 640x480 такой проблемы нет

...