Pertanyaan Mengoptimalkan pengujian klik asli elemen DOM (Chrome)


Saya punya berat aplikasi JavaScript yang dioptimalkan, editor grafik yang sangat interaktif. Saya sekarang mulai mem-profil-nya (menggunakan alat-dev Chrome) dengan sejumlah besar data (ribuan bentuk dalam grafik), dan saya menghadapi hambatan kinerja yang sebelumnya tidak biasa, Hit Test.

| Self Time       | Total Time      | Activity            |
|-----------------|-----------------|---------------------|
| 3579 ms (67.5%) | 3579 ms (67.5%) | Rendering           |
| 3455 ms (65.2%) | 3455 ms (65.2%) |   Hit Test          | <- this one
|   78 ms  (1.5%) |   78 ms  (1.5%) |   Update Layer Tree |
|   40 ms  (0.8%) |   40 ms  (0.8%) |   Recalculate Style |
| 1343 ms (25.3%) | 1343 ms (25.3%) | Scripting           |
|  378 ms  (7.1%) |  378 ms  (7.1%) | Painting            |

Ini membutuhkan 65% dari segalanya (!), tersisa hambatan monster di basis kode saya. Saya tahu ini adalah proses pelacakan objek di bawah penunjuk, dan saya memiliki ide yang tidak berguna tentang bagaimana hal ini dapat dioptimalkan (gunakan lebih sedikit elemen, gunakan lebih sedikit peristiwa mouse, dll.).

Konteks: Profil kinerja di atas menunjukkan fitur "panning layar" di aplikasi saya, tempat konten layar dapat dipindahkan dengan menyeret area kosong. Ini menghasilkan banyak objek yang dipindahkan, dioptimalkan dengan memindahkan wadah mereka, bukan setiap objek secara individual. Saya membuat demo.


Sebelum melompat ke ini, saya ingin mencari prinsip-prinsip umum mengoptimalkan pengujian klik (yang bagus ol ' "Tidak sh * t, Sherlock" artikel blog), serta jika ada trik untuk meningkatkan kinerja pada akhir ini (seperti menggunakan translate3d untuk mengaktifkan pemrosesan GPU).

Saya mencoba pertanyaan seperti js mengoptimalkan uji klik, tetapi hasilnya penuh dengan artikel pemrograman grafis dan contoh implementasi manual - seolah-olah komunitas JS belum dengar dari hal ini sebelumnya! Bahkan kromnya panduan devtools tidak memiliki area ini.

Jadi di sinilah saya, dengan bangga melakukan riset saya, bertanya: bagaimana cara mengoptimalkan pengujian klik asli di JavaScript?


Saya menyiapkan demo yang menunjukkan bottleneck kinerja, meskipun tidak persis sama dengan aplikasi saya yang sebenarnya, dan nomor juga akan berbeda-beda menurut perangkat juga. Untuk melihat bottleneck:

  1. Buka tab Timeline di Chrome (atau yang setara dengan browser Anda)
  2. Mulai merekam, lalu putar-putar di demo seperti orang gila
  3. Berhenti merekam dan periksa hasilnya

Rangkuman semua pengoptimalan signifikan yang telah saya lakukan di area ini:

  • memindahkan satu kontainer di layar alih-alih memindahkan ribuan elemen secara individual
  • menggunakan transform: translate3d untuk memindahkan kontainer
  • v-sinkronisasi gerakan mouse ke layar refresh rate
  • menghapus semua elemen "pembungkus" dan "fixer" yang tidak perlu
  • menggunakan pointer-events: none pada bentuk - tidak berpengaruh

Catatan tambahan:

  • bottleneck ada keduanya dengan dan tanpa Akselerasi GPU
  • pengujian hanya dilakukan di Chrome, terbaru
  • DOM diberikan menggunakan ReactJS, tetapi masalah yang sama dapat diamati tanpa itu, seperti yang terlihat pada demo terkait

32
2018-01-24 14:19


asal


Jawaban:


Menarik sekali, itu pointer-events: none tidak berpengaruh. Tetapi jika Anda memikirkannya, itu masuk akal, karena elemen-elemen dengan bendera yang ditetapkan masih mengaburkan peristiwa penunjuk elemen lain, jadi yang terkuat harus terjadi.

Apa yang dapat Anda lakukan adalah meletakkan overlay di atas konten penting dan menanggapi acara mouse di overlay itu, biarkan kode Anda memutuskan apa yang harus dilakukan dengan itu.

Ini bekerja karena sekali algoritma paling hittest telah menemukan hit, dan saya berasumsi itu melakukan itu ke bawah z-index, ia berhenti.


Dengan overlay

// ================================================
// Increase or decrease this value for testing:
var NUMBER_OF_OBJECTS = 40000;
// Wether to use the overlay or the container directly
var USE_OVERLAY = true;
// ================================================

var overlay = document.getElementById("overlay");
var container = document.getElementById("container");
var contents = document.getElementById("contents");

for (var i = 0; i < NUMBER_OF_OBJECTS; i++) {
    var node = document.createElement("div");
    node.innerHtml = i;
    node.className = "node";
    node.style.top = Math.abs(Math.random() * 2000) + "px";
    node.style.left = Math.abs(Math.random() * 2000) + "px";
    contents.appendChild(node);
}

var posX = 100;
var posY = 100;
var previousX = null;
var previousY = null;

var mousedownHandler = function (e) {
    window.onmousemove = globalMousemoveHandler;
    window.onmouseup = globalMouseupHandler;
    previousX = e.clientX;
    previousY = e.clientY;
}

var globalMousemoveHandler = function (e) {
    posX += e.clientX - previousX;
    posY += e.clientY - previousY;
    previousX = e.clientX;
    previousY = e.clientY;
    contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
}

var globalMouseupHandler = function (e) {
    window.onmousemove = null;
    window.onmouseup = null;
    previousX = null;
    previousY = null;
}

if(USE_OVERLAY){
	overlay.onmousedown = mousedownHandler;
}else{
	overlay.style.display = 'none';
	container.onmousedown = mousedownHandler;
}


contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
#overlay{
  position: absolute;
  top: 0;
  left: 0;
  height: 400px;
  width: 800px;
  opacity: 0;
  z-index: 100;
  cursor: -webkit-grab;
  cursor: -moz-grab;
  cursor: grab;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-user-select: none;
  user-select: none;
}

#container {
  height: 400px;
  width: 800px;
  background-color: #ccc;
  overflow: hidden;
}

#container:active {
  cursor: move;
  cursor: -webkit-grabbing;
  cursor: -moz-grabbing;
  cursor: grabbing;
}

.node {
  position: absolute;
  height: 20px;
  width: 20px;
  background-color: red;
  border-radius: 10px;
  pointer-events: none;
}
<div id="overlay"></div>
<div id="container">
    <div id="contents"></div>
</div>

Tanpa overlay

// ================================================
// Increase or decrease this value for testing:
var NUMBER_OF_OBJECTS = 40000;
// Wether to use the overlay or the container directly
var USE_OVERLAY = false;
// ================================================

var overlay = document.getElementById("overlay");
var container = document.getElementById("container");
var contents = document.getElementById("contents");

for (var i = 0; i < NUMBER_OF_OBJECTS; i++) {
    var node = document.createElement("div");
    node.innerHtml = i;
    node.className = "node";
    node.style.top = Math.abs(Math.random() * 2000) + "px";
    node.style.left = Math.abs(Math.random() * 2000) + "px";
    contents.appendChild(node);
}

var posX = 100;
var posY = 100;
var previousX = null;
var previousY = null;

var mousedownHandler = function (e) {
    window.onmousemove = globalMousemoveHandler;
    window.onmouseup = globalMouseupHandler;
    previousX = e.clientX;
    previousY = e.clientY;
}

var globalMousemoveHandler = function (e) {
    posX += e.clientX - previousX;
    posY += e.clientY - previousY;
    previousX = e.clientX;
    previousY = e.clientY;
    contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
}

var globalMouseupHandler = function (e) {
    window.onmousemove = null;
    window.onmouseup = null;
    previousX = null;
    previousY = null;
}

if(USE_OVERLAY){
	overlay.onmousedown = mousedownHandler;
}else{
	overlay.style.display = 'none';
	container.onmousedown = mousedownHandler;
}


contents.style.transform = "translate3d(" + posX + "px, " + posY + "px, 0)";
#overlay{
  position: absolute;
  top: 0;
  left: 0;
  height: 400px;
  width: 800px;
  opacity: 0;
  z-index: 100;
  cursor: -webkit-grab;
  cursor: -moz-grab;
  cursor: grab;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-user-select: none;
  user-select: none;
}

#container {
  height: 400px;
  width: 800px;
  background-color: #ccc;
  overflow: hidden;
}

#container:active {
  cursor: move;
  cursor: -webkit-grabbing;
  cursor: -moz-grabbing;
  cursor: grabbing;
}

.node {
  position: absolute;
  height: 20px;
  width: 20px;
  background-color: red;
  border-radius: 10px;
  pointer-events: none;
}
<div id="overlay"></div>
<div id="container">
    <div id="contents"></div>
</div>


6
2017-07-26 08:44



Salah satu masalah adalah bahwa Anda memindahkan SETIAP elemen tunggal di dalam wadah Anda, tidak masalah jika Anda memiliki akselerasi GPU atau tidak, leher botol menghitung ulang posisi baru mereka, yaitu bidang prosesor.

Saran saya di sini adalah menyegmentasikan kontainer, sehingga Anda dapat memindahkan berbagai panel secara terpisah, mengurangi beban, ini disebut perhitungan fase luas, yaitu hanya memindahkan apa yang perlu dipindahkan. Jika Anda mengeluarkan sesuatu dari layar, mengapa Anda harus memindahkannya?

Mulailah dengan membuat, bukan satu, 16 kontainer, Anda harus melakukan beberapa matematika di sini untuk mengetahui mana dari panel ini yang ditampilkan. Kemudian, ketika peristiwa mouse terjadi, pindahkan hanya panel dan biarkan yang tidak ditampilkan di mana mereka. Ini akan sangat mengurangi waktu yang digunakan untuk memindahkannya.

+------+------+------+------+
|    SS|SS    |      |      |
|    SS|SS    |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+

Pada contoh ini, kita memiliki 16 panel, dimana, 2 sedang ditampilkan (ditandai oleh S untuk Layar). Ketika pengguna melakukan panning, periksa kotak pembatas pada "layar", cari tahu panel mana yang berhubungan dengan "layar", pindahkan hanya panel-panel itu. Ini secara teoritis jauh terukur.

Sayangnya saya tidak punya waktu untuk menulis kode yang menunjukkan pemikiran itu, tetapi saya harap ini membantu Anda.

Tepuk tangan!


5
2018-02-03 12:51