Bagaimana cara bekerja dengan gambar pada klien, sambil mempertahankan UI yang lancar? Pengembang antarmuka Pavel Smirnov berbicara tentang ini berdasarkan pengalaman mengembangkan pencarian foto di Pasar. Dari laporan tersebut, Anda dapat mempelajari cara menggunakan Pekerja Web dan OffscreenCanvas dengan benar.

- Selama setengah jam ini kita akan berbicara tentang petualangan. Saya akan bercerita tentang petualangan saya dan sangat berharap bahwa laporan saya akan menginspirasi Anda dan Anda akan mengambil dan melakukan hal yang sama di rumah.
Pada awalnya saya ingin berbicara tentang beberapa teknologi baru atau tidak sangat baru yang diberikan browser kami dan yang memungkinkan kami melakukan hal-hal keren. Tapi menurut saya itu tidak terlalu menyenangkan, karena semua orang bisa pergi ke MDN dan membaca sesuatu. Karenanya, saya akan menceritakan kisah satu fitur yang saya lakukan dengan tim Market.
Mari kenalkan diri saya lagi dulu. Nama saya Pasha, saya adalah pengembang antarmuka di tim Market.

Saya terutama berurusan dengan antarmuka seluler - pencarian peta, kartu penawaran. Saya juga menulis ulang kode dari tumpukan lama ke yang baru, dan kemudian dari yang baru ke tumpukan yang lebih baru. Dan saya mencoba membuat antarmuka saya bagus. Di sini perlu dikatakan apa antarmuka yang baik.
Antarmuka yang baik memiliki karakteristik yang berbeda. Pertama, nyaman, kedua indah, ketiga terjangkau. Tetapi salah satu karakteristik yang ingin saya bicarakan hari ini adalah kecepatan. Dan kecepatan sering memanifestasikan dirinya dalam kelancaran karyanya. Bahkan jalur kecil dapat sangat mengubah pengalaman pengguna antarmuka kami.

Mari beralih ke rencana untuk percakapan saya hari ini. Pertama-tama kita akan berbicara tentang tugas yang saya lakukan: menemukan gambar di Pasar. Selanjutnya, saya akan memberi tahu Anda masalah apa yang harus saya selesaikan untuk menerapkan fungsi ini. Di sini kita ingat sedikit bagaimana skrip Anda bekerja di browser, dan melihat teknologi yang membantu saya. Spoiler kecil: ini adalah Web Workers dan OffscreenCanvas.
Mari kita kembali ke tugas. Beberapa bulan yang lalu, Luba, manajer produk kami, mendekati saya. Lyuba berurusan dengan masalah memilih produk di Pasar. Sekarang kami memiliki beberapa opsi untuk menemukan barang. Salah satunya adalah memasukkan sesuatu ke bilah pencarian.

Misalnya, "beli iPhone X merah di Samara." Dan kita akan menemukan sesuatu. Atau kita dapat menggunakan pohon katalog. Dalam katalog ini kami memiliki kategori dan subkategori.
Tetapi bagaimana jika saya ingin menemukan sesuatu di Pasar, tidak tahu apa namanya, tetapi apakah saya memiliki gambar hal ini, atau saya melihatnya di pesta seseorang?

Saya akan menceritakan kasus nyata. Saya pernah pergi bersama teman-teman ke kafe. Kami memesan limun di sana, Anda tahu, dalam kendi, dan kendi ini memiliki hal yang aneh. Saya bahkan menyimpan foto. Itu dimaksudkan agar ketika Anda menuangkan limun ke dalam gelas, es tidak masuk ke dalamnya. Kami pikir itu hal yang keren, tetapi kami memiliki pendapat berbeda tentang apa nama benda ini dan, secara umum, untuk apa benda itu dimaksudkan. Oleh karena itu, kami menemukannya di Yandex. Gambar.
Tapi saya pikir - itu akan keren jika saya tidak hanya dapat mencari hal ini, tetapi juga membelinya segera, atau setidaknya mencari tahu harga, membaca ulasan, fitur, dll. Pada titik ini, impian kami bertepatan dengan Any, dan kami memutuskan membuat fungsi seperti itu di Pasar.
Seperti apa fungsi ini? Ini memungkinkan pengguna untuk mengunggah foto atau gambar, Anda bahkan dapat langsung mengambil gambar dan mengirimkannya ke Pasar. Kami menganalisis foto ini menggunakan teknologi pencarian Yandex, menemukan produk di atasnya dan menunjukkan hasilnya kepada pengguna dengan produk ini. Tampaknya kedengarannya sederhana, tetapi jika sesederhana itu, saya tidak akan membuat laporan. Untuk memastikan fitur apa ini, izinkan saya menunjukkannya.
Tonton demo pertamaSaya akan tunjukkan pada produksi. Pertama mari kita unggah hal yang kita cari, dan lihat apa yang terjadi.
Kami menemukan beberapa barang dan khususnya barang ini. Benda ini disebut saringan. Untuk menemukan sesuatu yang lain, saya memotret satu buku di meja seorang rekan kemarin, mari kita cari. Inilah buku semacam itu, mungkin seseorang membacanya. Ini disebut "Kode Sempurna." Dia juga menemukannya entah bagaimana, dan untuk beberapa alasan dengan batas 18+. Ini mungkin agak aneh.
Mari kita kembali ke laporan kita. Masalah apa yang saya temui? Masalah pertama adalah bahwa pengguna mulai mengunduh apa pun, termasuk gambar besar. Misalnya, ponsel saya mengambil gambar berukuran tiga hingga empat megabita, yang cukup banyak. Mengirim foto seperti itu ke backend tidak efisien. Butuh waktu lama, perlu waktu lama untuk menganalisisnya, jadi Anda perlu melakukan sesuatu. Tapi di sini semuanya sederhana - kami akan memotong, memampatkan, mengubah ukuran foto ini pada klien.

Bagaimana kita akan melakukan ini? Kami punya file. Dan entah bagaimana kita akan membaca file ini. Kita akan membaca menggunakan FileReader API. Secara singkat saya akan memberi tahu Anda apa itu.

Ini adalah API peramban yang memungkinkan kami membaca file yang diunduh dan melakukan sesuatu dengannya. Anda dapat membaca dengan cara yang berbeda, kami akan melihatnya sekarang. Berikut adalah fitur-fiturnya, dan kami memiliki beberapa jenis objek yang dikembalikan kepada kami dari masukan oleh acara perubahan. Mari kita coba membacanya.

Kode akan terlihat seperti ini. Belum ada yang rumit di sini. Kami memiliki objek Pustaka yang dibuat dari konstruktor FileReader, tempat kami menggantung pengembang acara pemuatan. Selanjutnya kita akan membaca file ini sebagai DataURL. DataURL - string yang mewakili konten file yang disandikan melalui Base64. Seperti yang kita baca, kita perlu memotongnya entah bagaimana. Pertama, mari kita muat semuanya ke dalam gambar. Kami memiliki tag atau elemen img, dan kami memuatnya di sana.

Kode akan terlihat seperti ini. Kami membuat elemen img, dengan acara Reader load, kami memuat baris kami ke atribut src dan kami akan melakukan segalanya lebih jauh ketika baris kami selesai memuat ke img.
Kami akan melakukan apa yang kami inginkan - memotong gambar. Kami akan memampatkannya, dan di sini Canvas akan membantu kami, alat yang sangat kuat. Ini memungkinkan Anda melakukan banyak hal. Tapi di sini kita hanya menggambar gambar kita di kanvas ini, dan jika ukuran gambar melebihi batas maksimum yang diijinkan, kita akan memasangnya sedikit. Juga, kita dapat mengambil gambar ini dengan Canvas dari rasio kompresi yang diinginkan.

Sesuatu seperti itu. Penafian kecil lainnya: kode di sini sangat disederhanakan, saya tidak menentukan semuanya. Kami memiliki penanganan kesalahan dan hal-hal lain, tetapi agar semuanya sesuai pada slide dan jelas dalam laporan, saya menghilangkan beberapa detail.
Kami memiliki ukuran gambar, kami hanya melihatnya. Ada beberapa konstanta yang diizinkan untuk kita. Jika ukuran gambar melebihi konstanta kami, kami hanya memotongnya di bawahnya dan mengatur Kanvas kami ke ukuran yang sama.
Selanjutnya kita akan menggambar gambar kita di kanvas ini.

Ambil konteks 2d, kita perlu gambar 2d, dan coba menggambar menggunakan metode drawImage. DrawImage adalah metode menarik yang menerima, jika saya tidak salah, sembilan parameter. Tetapi mereka tidak semua wajib, kami hanya akan menggunakan lima. Kami mengambil Gambar dan dua nol itu, ini adalah offset atau lekukan dari gambar. Kami membutuhkan titik kiri atas. Gambarlah dengan dimensi yang kita butuhkan.
Lebih jauh, dari Kanvas ini, kita akan mengambil string Base64 yang disandikan DataURL dengan cara yang persis sama dan mengubahnya menjadi gumpalan - objek khusus yang nyaman untuk kita kirim ke server. Sepertinya semuanya. Semuanya berfungsi. Gambar dipotong, gambar dikirim, gambar dikenali.
Tetapi kemudian saya mulai memperhatikan sesuatu. Ketika saya menguji solusi ini, ketika saya mengunggah gambar, terutama pada perangkat yang lemah, antarmuka saya sedikit melambat. Entah tombol itu tidak ditekan, maka elemen tidak gulir begitu. Apakah Anda merasa bahwa kode Anda berfungsi di 99% kasus dan berfungsi dengan baik, tetapi kadang-kadang itu tidak berhasil? Dan Anda dapat memberikannya untuk pengujian, dan mungkin tidak ada yang akan memperhatikan. Dan pengguna, mungkin, tidak akan memperhatikan, terutama pada perangkat yang lemah.
Ini tidak pernah terjadi pada saya, dan saya memutuskan untuk memperbaikinya. Ini ternyata menjadi masalah. Jika gambar besar, maka selama manipulasi dengan cropping, kompresi, kami butuh beberapa waktu, dan dalam waktu kecil ini, antarmuka kami tidak responsif.
Pada awalnya saya mencari tahu mengapa ini terjadi. Ini ada baiknya sedikit mengingat bagaimana JavaScript bekerja di browser. Saya tidak akan merinci, ini adalah topik untuk laporan besar. Ingatlah beberapa poin.

Kami menjalankan JavaScript dalam satu utas, sebut saja utama. Dan kami memiliki hal semacam itu di browser sebagai loop acara. Di sini kita langsung mengatakan bahwa ini adalah model. Di beberapa browser, event loop diatur secara berbeda, tetapi seperti namanya, secara umum itu adalah loop. Itu memproses tugas-tugas tertentu dalam antrian agar.
Momen yang tidak menyenangkan: sampai dia memproses satu tugas, dia tidak akan melanjutkan ke yang berikutnya. Saya akan menunjukkan demo yang saya lihat, dia menunjukkannya. Dia seorang klasik.
Tonton demo keduaSaya memiliki gambar GIF dan animasi CSS yang dilakukan dengan cara yang berbeda: satu menggunakan translatex, lainnya menggunakan posisi: relatif kiri, ketiga menggunakan JavaScript, yaitu requestAnimationFrame. Di sinilah landak berputar. Apa yang akan saya lakukan?
Saya akan memblokir utas utama selama lima detik. Anda tahu, biasanya orang-orang yang tangguh menghitung angka Fibonacci n, tapi saya menulis loop tanpa akhir dengan istirahat dalam lima detik.
Apa yang akan terjadi Anda segera memperhatikan bahwa landak berhenti berputar, dan kucing yang lebih rendah, yang dianimasikan menggunakan translatex, juga berhenti mengendarai. Tapi mari kita lihat demo yang sama di browser lain, misalnya Safari. Kucing GIF berhenti berlari.
Mengapa saya menunjukkan semua ini? Pertama, browser berbeda, Anda harus mempertimbangkan ini. Kedua, ketika aliran kita terhalang oleh sesuatu, beberapa hal akan berhenti bekerja. Misalnya - Animasi JavaScript. Atau mari kita tunjukkan bahwa teks tidak lagi menonjol bagi kita, tombol-tombol tidak akan lagi ditekan.
Ini adalah contoh yang sangat abstrak. Mari kita tidak memblokir aliran selama lima detik, tetapi ambil tugas kami, unggah foto, potong, peras dan tarik di sini. Kami tidak akan mengirimnya ke mana pun, itu tidak akan sangat terbuka.
Tonton demo ketigaSaya memiliki MacBook yang kuat di sini, dan untuk membuat semuanya terlihat lebih meyakinkan, kami akan memperlambat prosesor sebanyak enam kali. Ini memungkinkan Anda untuk melakukan DevTools. Unggah foto kami. Kode Sempurna akan membantu kita lagi. Seperti yang kita lihat, hal yang sama terjadi ketika memblokir utas.
Mari kita kembali ke tugas kita dan memikirkan bagaimana kita akan menghadapi ini.

Omong-omong, jika Anda melihat profiler, kita akan melihat ini. Dalam bingkai merah adalah mikrotask kami, yang memblokir utas. Kami melihat bahwa ia memblokirnya selama hampir lima detik. Ini pada komputer yang agak kuat, dan pada perangkat yang lebih lemah itu akan lebih terlihat.
Mari kita beralih ke solusinya. Saya akan segera mengatakan apa yang saya gunakan dan apa yang saya lakukan, dan kemudian kita akan menganalisis semua hal ini. Pertama, saya menggunakan Pekerja Web. Mereka memungkinkan kita untuk menempatkan beberapa tugas ke utas terpisah. Dan kedua, dalam konteks Pekerja Web, DOM tidak tersedia untuk kami. Untuk menghadapi situasi ini, kami akan menggunakan alat lain. Gambar tidak akan tersedia bagi kami, Kanvas klasik tersedia, dan oleh karena itu kami menggunakan kanvas dan beberapa trik lainnya.

Mari kita cepat mengingat apa itu Pekerja, untuk apa mereka. Mereka memungkinkan Anda untuk menjalankan JavaScript di utas terpisah, tidak terutama. Dan aliran Pekerja tidak mengganggu aliran rendering antarmuka utama. Oleh karena itu, kita dapat melakukan beberapa tugas komputasi yang kompleks tanpa memperlambat antarmuka kita.
Kami memiliki alat yang memungkinkan Anda untuk mentransfer sesuatu ke Pekerja dan mengembalikan sesuatu dari Pekerja. Mari kita lihat sebuah contoh.

Jadi kami membuat Pekerja kami menggunakan konstruktor. Di sana Anda perlu mentransfer jalur ke file. Kita bahkan bisa melewati gumpalan. Dan kami memiliki penangan event Message. Dalam hal ini, itu hanya akan menampilkan sesuatu di layar. Kemudian kami dapat mengirim beberapa data ke Pekerja kami.

Apa dukungannya? Semuanya baik di sini. Pekerja adalah alat yang terkenal, bukan yang baru, tetapi banyak teman saya berpikir bahwa mereka tidak selalu didukung. Ini tidak benar.

Sekarang mari kita lihat OffscreenCanvas. Seperti yang telah kita lihat, Kanvas adalah alat yang sangat kuat, tetapi, sayangnya, itu tidak tersedia bagi kami dalam konteks Pekerja Web, jadi kami akan menggunakan alternatif. Ini adalah hal yang cukup baru yang disebut OffscreenCanvas. Ini memungkinkan Anda untuk melakukan hal-hal yang sama seperti Canvas, hanya di luar layar, yaitu, dalam konteks Pekerja Web. Tentu saja, kita dapat melakukan ini juga dalam konteks window, tetapi sekarang kita tidak akan melakukannya.

Apa yang ada dengan dukungan? Seperti yang Anda lihat, ada banyak warna merah. OffscreenCanvas biasanya hanya didukung di Chrome. Ada juga opsi dengan Firefox, tetapi sejauh ini ada bendera, dan Canvas hanya berfungsi dengan konteks WebGL. Di sini Anda dapat bertanya - mengapa saya berbicara tentang hal yang keren seperti OffscreenCanvas, yang tidak berfungsi di mana saja?

Penyimpangan kecil. Kami memiliki beberapa tingkat dukungan browser di Pasar. Dan kami memiliki dua kuantitas. Satu nilai menjadi ciri browser, yang tidak kami dukung sama sekali. Ini sekitar setengah dari persentase popularitas peramban.
Dan ada kuantitas kedua. Ini termasuk browser yang kami dukung, tetapi hanya fungsionalitas penting. Di sini, tanpa Pekerja, semua fungsi pencarian berfungsi, tetapi dengan jalur kecil. Saya pikir tidak apa-apa, dan tim kami percaya itu tidak apa-apa. Mari kita lihat bagaimana kita akan mengimplementasikannya.

Berikut adalah diagram dari apa yang akan kita lakukan. Kami bahkan memiliki file yang akan kami baca melalui FileReader. Tetapi dalam arus utama kami akan mengirimkannya ke Pekerja Web, di mana ia akan dipotong, dikompresi dan akan kembali kepada kami, dan kami sudah akan mengirimkannya ke server.

Mari kita lihat kode untuk Pekerja kita. Pertama, kita membuat instance OffscreenCanvas dengan lebar dan tinggi yang kita butuhkan.
Lebih jauh, seperti yang saya katakan, elemen Gambar tidak tersedia untuk kita dalam konteks Pekerja, jadi di sini kita menggunakan metode createImageBitmap, yang akan membuat kita struktur data yang menjadi ciri gambar kita.
Dari yang menarik: kita lihat di sini sendiri. Mereka yang tidak terbiasa dengan Pekerja Web, hal ini menunjuk ke konteks eksekusi. Tidak masalah bagi kami di sini, jendela atau ini, kami menggunakan diri sendiri. Metode ini asinkron, saya telah menggunakan menunggu di sini untuk kekompakan dan kenyamanan, mengapa tidak?
Selanjutnya, kita mendapatkan gambar yang sama dan melakukan hal yang sama yang kita lakukan sebelumnya. Gambarlah di kanvas dan kembali.
Dari yang sederhana. Kami biasa mengambil DataURL dan mengonversikan semuanya menjadi gumpalan. Namun di sini metode convertToBlob segera tersedia bagi kami. Mengapa saya belum pernah menggunakannya sebelumnya? Karena dukungannya lebih buruk. Tetapi karena kami sudah jauh-jauh ke sini dan menggunakan OffscreenCanvas, apa yang mencegah kami menggunakan convertToBlob?

Kami akan mengembalikan gumpalan ini pada dasarnya sebuah aliran, dari mana kami akan mengirimkannya ke server. Atau, seperti dalam demo, gambarkan.
Jadi kami membuat Worker di utas utama, mendengarkan beberapa pesan darinya dan kami akan menggambar atau mengirim ke server. Tidak ada yang penting di sini. Pekerja akan menerima file kami.
Mari kita kembali ke demo kita.
Tonton demo keempatSemua demo yang sama, semua tiga kucing yang sama, dan landak. Saya akan mengaktifkan pembatasan lagi, memperlambat prosesor enam kali. Saya akan mengunggah foto yang sama. Seperti yang kita lihat, pada saat gambar diambil, animasi tidak berhenti, landak terus berputar, antarmuka tetap, dan kami mencapai apa yang kami inginkan.
Tetapi bisakah keputusan ini diperbaiki?

Di sini, omong-omong, profiler. Di sini kita tidak melihat Microtasks besar selama lima detik yang kita lihat sebelumnya.
Perbaikan dimungkinkan. Menggunakan benda yang bisa dipindahtangankan. Di sini perlu kembali lagi. Ketika kami melewati DataURL atau gumpalan kami melalui mekanisme postMessage, kami menyalin data ini. Ini mungkin tidak terlalu efektif. Akan keren untuk menghindarinya. Oleh karena itu, kami memiliki mekanisme yang memungkinkan Anda untuk mentransfer data ke Pekerja Web seolah-olah dalam sebuah paket.
Mengapa saya mengatakan "suka"? Ketika kami mentransfer data ini ke Pekerja, kami kehilangan kendali atas mereka di arus utama - kami tidak dapat berinteraksi dengan mereka dengan cara apa pun. Ada batasan kedua di sini. Kami tidak dapat mentransfer semua tipe data ke Pekerja Web. Kita tidak dapat melakukan ini dengan string, kita akan melakukannya secara berbeda.

Mari kita lihat kodenya. Pertama, kami mentransmisikan data sedikit berbeda. Ini postmessage kami. Anda lihat, ada array dengan loadEvent.target.result. Antarmuka seperti ini memungkinkan kita untuk mentransfer data kita sebagai objek yang dapat dipindahtangankan, kehilangan kendali atasnya
Ngomong-ngomong, siapa pun yang menulis di Rust mungkin akan mendengar sesuatu yang akrab. Dan kita akan membaca file kita bukan sebagai string, tetapi sebagai ArrayBuffer. Ini adalah aliran data biner lidar yang tidak memiliki akses langsung. Karena itu, kita harus melakukan sesuatu yang lain dengan mereka.

Kembali ke ImageWorkers kami. Di sini menjadi jauh lebih menarik. Pertama, kami mengambil buffer kami dan melakukan hal yang mengerikan seperti Uint8ClampedArray. Ini adalah array yang diketik. Seperti namanya, data di dalamnya adalah angka tanda, yaitu angka dari nol hingga 255 yang akan mewakili piksel gambar kita.
Argumen ketiga, kita melewati hal yang aneh, seperti lebar, dikalikan dengan tinggi, dikalikan empat. Mengapa tepatnya empat? Tepatnya, RGBA. Ada tiga nilai per warna dan satu per saluran alpha.
Selanjutnya, kita akan membuat ImageData dari larik ini, tipe data khusus yang dapat dengan mudah digambar di kanvas. Tidak ada yang menarik di sini. Kami hanya mengambil array dan meneruskannya ke konstruktor. Lebih lanjut, dengan cara yang sama kita menggambar gambar kita di atas kanvas, tetapi menggunakan metode yang berbeda, di bawah ImageData. Lebih lanjut, semuanya sama seperti sebelumnya.
Mari kita beralih ke kesimpulan. Hari ini saya memberi tahu Anda tentang satu tugas yang belum lama saya lakukan. Apa yang saya perhatikan di dalamnya?

Kelancaran antarmuka sangat penting. Ketika pengguna sedikit terlambat, sedikit membeku, tombol tidak ditekan, ini dapat menyebabkan kemunduran UX yang kuat. Browser bekerja secara berbeda. Kami melihat contoh bola dengan Safari dan Yandex.Browser. Kami melihat bahwa jika Anda memeriksa antarmuka Anda untuk kelancaran di satu browser, Anda harus melihat yang lain.
Anda perlu melakukan sesuatu dengan memblokir skrip jika skrip itu berlangsung lama. Dalam kasus saya, saya menaruhnya di Web Pekerja. Tetapi mungkin ada pendekatan lain, Anda dapat membaginya menjadi lebih kecil, di sini Anda harus berpikir. , Web Workers, .
? . . . , 200 , .
Web Workers . , , .
:
.