
Dalam tutorial ini, saya akan
OffscreenCanvas
caranya, menggunakan
OffscreenCanvas
saya berhasil memasukkan semua kode untuk bekerja dengan WebGL dan
Three.js ke utas pekerja web yang terpisah. Ini mempercepat kerja situs dan pada jalur perangkat yang lemah menghilang selama pemuatan halaman.
Artikel ini didasarkan pada pengalaman pribadi, ketika saya
menambahkan bumi berputar 3D ke
situs saya dan butuh 5 poin produktivitas di
Google Lighthouse - terlalu banyak untuk pamer mudah.
Masalah
Three.js menyembunyikan banyak masalah WebGL yang kompleks, tetapi memiliki harga yang serius - perpustakaan menambahkan
563 KB ke JS build untuk browser Anda (dan arsitektur perpustakaan tidak memungkinkan trichashing berfungsi secara efisien).
Beberapa orang mungkin mengatakan bahwa gambar seringkali memiliki berat 500 KB yang sama - dan akan sangat salah. Setiap KB script jauh lebih kuat dalam kinerjanya daripada KB gambar. Agar suatu situs menjadi cepat, Anda tidak hanya perlu memikirkan lebar saluran dan waktu tunda - Anda juga perlu memikirkan waktu pengoperasian CPU komputer untuk memproses file. Pada ponsel dan laptop yang lemah, pemrosesan bisa memakan waktu lebih lama daripada memuat.
Pemrosesan 170K JS membutuhkan 3,5 detik dibandingkan 0,1 detik untuk gambar 170K - Eddie OsmaniSementara browser akan mengeksekusi 500 KB Three.js, aliran halaman utama akan diblokir dan pengguna akan melihat dekorasi antarmuka.
Pekerja Web dan Kanvas Offscreen
Kami telah lama memiliki solusi untuk tidak menghapus dekorasi selama JS - pekerja web menjalankan kode dalam utas terpisah.
Agar bekerja dengan pekerja web tidak berubah menjadi neraka pemrograman multithreaded, pekerja web tidak memiliki akses ke DOM. Hanya utas utama yang berfungsi dengan laman HTML. Tetapi bagaimana cara memulai Three.js tanpa akses ke DOM, yang membutuhkan akses langsung ke
<canvas>
?
Untuk melakukan ini, ada
OffscreenCanvas - memungkinkan Anda untuk mengirimkan
<canvas>
ke pekerja web. Agar tidak membuka gerbang neraka multithreaded, setelah transfer, utas utama kehilangan akses ke
<canvas>
- hanya satu utas yang akan bekerja dengannya.
Sepertinya kami dekat dengan tujuan, tetapi ternyata hanya Chrome yang mendukung
OffscreenCanvas
.
Dukungan OffscreenCanvas untuk April 2019 sesuai dengan Can I UseTetapi bahkan di sini, dalam menghadapi musuh utama pengembang web, dukungan browser, kita tidak boleh menyerah. Kami berkumpul dan menemukan elemen terakhir dari teka-teki - ini adalah kasus yang ideal untuk "peningkatan progresif." Di Chrome dan browser berikutnya, kami akan menghapus dekorasi, dan browser lain akan berfungsi seperti sebelumnya.
Akibatnya, kita perlu menulis satu file yang dapat bekerja di dua lingkungan yang berbeda sekaligus - dalam pekerja web dan dalam aliran JS utama reguler.
Solusi
Untuk menyembunyikan retasan di bawah lapisan gula, saya membuat pustaka JS kecil dengan
layar 400 byte (!). Dalam contoh, kode akan menggunakannya, tetapi saya akan memberitahu Anda cara kerjanya "di bawah tenda."
Mari kita mulai dengan menginstal perpustakaan:
npm install offscreen-canvas
Kami memerlukan file JS terpisah untuk pekerja web - buat file rakitan terpisah di Webpack atau Paket:
entry: { 'app': './src/app.js', + 'webgl-worker': './src/webgl-worker.js' }
Para kolektor akan terus-menerus mengubah nama file selama penyebaran karena penghilang cache - kita perlu menulis nama dalam HTML menggunakan
tag preload . Di sini contohnya akan abstrak, karena kode aktual akan sangat tergantung pada fitur-fitur perakitan Anda.
<link type="preload" as="script" href="./webgl-worker.js"> </head>
Sekarang kita perlu mendapatkan simpul DOM untuk
<canvas>
dan konten tag preload di file JS utama.
import createWorker from 'offscreen-canvas/create-worker' const workerUrl = document.querySelector('[rel=preload][as=script]').href const canvas = document.querySelector('canvas') const worker = createWorker(canvas, workerUrl)
createWorker
jika ada
canvas.transferControlToOffscreen
memuat file JS ke dalam pekerja web. Dan dengan tidak adanya metode ini - seperti
<script>
.
Buat
webgl-worker.js
untuk pekerja:
import insideWorker from 'offscreen-canvas/inside-worker' const worker = insideWorker(e => { if (e.data.canvas) {
insideWorker
memeriksa untuk melihat apakah telah dimuat di dalam pekerja web. Tergantung pada lingkungannya, ia akan meluncurkan sistem komunikasi yang berbeda dengan utas utama.
Perpustakaan akan menjalankan fungsi yang diteruskan ke
insideWorker
untuk setiap pesan baru dari utas utama. Segera setelah memuat,
createWorker
akan mengirim pesan pertama
{ canvas, width, height }
untuk menggambar bingkai pertama di
<canvas>
.
+ import { + WebGLRenderer, Scene, PerspectiveCamera, AmbientLight, + Mesh, SphereGeometry, MeshPhongMaterial + } from 'three' import insideWorker from 'offscreen-canvas/inside-worker' + const scene = new Scene() + const camera = new PerspectiveCamera(45, 1, 0.01, 1000) + scene.add(new AmbientLight(0x909090)) + + let sphere = new Mesh( + new SphereGeometry(0.5, 64, 64), + new MeshPhongMaterial() + ) + scene.add(sphere) + + let renderer + function render () { + renderer.render(scene, camera) + } const worker = insideWorker(e => { if (e.data.canvas) { + // canvas - — , Three.js + if (!canvas.style) canvas.style = { width, height } + renderer = new WebGLRenderer({ canvas, antialias: true }) + renderer.setPixelRatio(pixelRatio) + renderer.setSize(width, height) + + render() } })
Saat porting kode lama Anda untuk Three.js ke pekerja web, Anda mungkin melihat kesalahan, karena pekerja web tidak memiliki DOM API. Misalnya, tidak ada
document.createElement
untuk memuat tekstur SVG. Jadi, kadang-kadang kita akan membutuhkan loader yang berbeda dalam pekerja web dan di dalam skrip biasa. Untuk memeriksa jenis lingkungan, kami memiliki
worker.isWorker
:
renderer.setPixelRatio(pixelRatio) renderer.setSize(width, height) + const loader = worker.isWorker ? new ImageBitmapLoader() : new ImageLoader() + loader.load('/texture.png', mapImage => { + sphere.material.map = new CanvasTexture(mapImage) + render() + }) render()
Kami menggambar bingkai pertama. Tetapi sebagian besar adegan WebGL harus menanggapi tindakan pengguna. Misalnya, putar kamera ketika kursor bergerak atau menggambar bingkai ketika jendela diubah ukurannya. Sayangnya, pekerja web tidak dapat mendengarkan acara DOM. Kita perlu mendengarkan mereka di arus utama dan mengirim pesan ke pekerja web.
import createWorker from 'offscreen-canvas/create-worker' const workerUrl = document.querySelector('[rel=preload][as=script]').href const canvas = document.querySelector('canvas') const worker = createWorker(canvas, workerUrl) + window.addEventListener('resize', () => { + worker.post({ + type: 'resize', width: canvas.clientWidth, height: canvas.clientHeight + }) + })
const worker = insideWorker(e => { if (e.data.canvas) { if (!canvas.style) canvas.style = { width, height } renderer = new WebGLRenderer({ canvas, antialias: true }) renderer.setPixelRatio(pixelRatio) renderer.setSize(width, height) const loader = worker.isWorker ? new ImageBitmapLoader() : new ImageLoader() loader.load('/texture.png', mapImage => { sphere.material.map = new CanvasTexture(mapImage) render() }) render() - } + } else if (e.data.type
Hasil
Dengan
OffscreenCanvas
saya mengalahkan
OffscreenCanvas
di situs saya dan mendapat 100% poin di Google Lighthouse. Dan WebGL bekerja di semua browser, bahkan tanpa dukungan
OffscreenCanvas
.
Anda dapat melihat
situs langsung dan
kode sumber utas atau
pekerja utama .
Dengan OffscreenCanvas, kacamata Google Lighthouse naik dari 95 menjadi 100