
في هذا البرنامج التعليمي ،
سأوضح لك كيفية استخدام
OffscreenCanvas
للحصول على جميع التعليمات البرمجية للعمل مع WebGL و
Three.js في
سلسلة رسائل منفصلة على الويب. هذا تسارع عمل الموقع وعلى الأجهزة الضعيفة اختفت أفاريز أثناء تحميل الصفحة.
تستند المقالة إلى التجربة الشخصية ، عندما
أضفت تدوير الأرض ثلاثية الأبعاد إلى
موقعي واستغرق الأمر 5 نقاط من الإنتاجية في
Google Lighthouse - أكثر من اللازم لسهولة المواجهة.
المشكلة
تخفي Three.js الكثير من مشكلات WebGL المعقدة ، لكن لها سعرًا كبيرًا - تضيف المكتبة
563 كيلوبايت إلى بنية JS الخاصة بك للمتصفحات (ولا تسمح بنية المكتبة بالتريشينج بكفاءة).
قد يقول البعض أن الصور غالبًا ما تزن 500 كيلو بايت - وستكون خاطئة جدًا. كل كيلوبايت من البرنامج النصي أقوى بكثير في الأداء من كيلوبايت في الصورة. لكي يكون الموقع سريعًا ، يجب عليك التفكير ليس فقط في عرض القناة ووقت التأخير - أنت بحاجة أيضًا إلى التفكير في وقت تشغيل وحدة المعالجة المركزية بالكمبيوتر لمعالجة الملفات. على الهواتف وأجهزة الكمبيوتر المحمولة الضعيفة ، قد تستغرق المعالجة وقتًا أطول من التحميل.
تستغرق معالجة JS 170K 3.5 ثانية مقابل 0.1 ثانية لصور 170K - Eddie Osmaniبينما سيقوم المستعرض بتنفيذ 500 كيلوبايت Three.js ، سيتم حظر تدفق الصفحة الرئيسية وسيشاهد المستخدم إفريز الواجهة.
عمال الويب و Offscreen Canvas
لقد كان لدينا منذ فترة طويلة حل بعدم إزالة الإفريز خلال المدى الطويل لـ JS - العاملون على الويب الذين يقومون بتشغيل الشفرة في خيط منفصل.
بحيث لا يتحول العمل مع العاملين على شبكة الإنترنت إلى جحيم من البرمجة ذات مؤشرات الترابط المتعددة ، ولا يستطيع عامل الويب الوصول إلى DOM. يعمل الخيط الرئيسي فقط مع صفحة HTML. ولكن كيف تبدأ Three.js دون الوصول إلى DOM ، الأمر الذي يتطلب الوصول المباشر إلى
<canvas>
؟
للقيام بذلك ، هناك
OffscreenCanvas - يسمح لك بتمرير
<canvas>
إلى عامل ويب. من أجل عدم فتح أبواب الجحيم متعدد مؤشرات الترابط ، بعد النقل ، يفقد مؤشر الترابط الرئيسي الوصول إلى هذا
<canvas>
- سوف يعمل معه مؤشر ترابط واحد فقط.
يبدو أننا قريبون من الهدف ، لكن اتضح أن Chrome هو الوحيد الذي يدعم
OffscreenCanvas
.
دعم OffscreenCanvas لشهر أبريل 2019 وفقًا لـ Can I Useولكن حتى هنا ، في مواجهة العدو الرئيسي لمطور الويب ، ودعم المتصفح ، يجب ألا نستسلم. نلتقي ونجد العنصر الأخير من اللغز - هذه هي الحالة المثالية "للتحسين التدريجي". في Chrome والمتصفحات المستقبلية ، سنقوم بإزالة الإفريز ، وستعمل المتصفحات الأخرى كما كان من قبل.
نتيجة لذلك ، سوف نحتاج إلى كتابة ملف واحد يمكن أن يعمل في بيئتين مختلفتين في آن واحد - في عامل ويب وفي دفق JS رئيسي منتظم.
قرار
لإخفاء الاختراقات تحت طبقة من السكر ، قمت بإنشاء مكتبة JS صغيرة
خارج الشاشة التي تحتوي على 400 بايت (!). في الأمثلة ، سوف تستخدمها الشفرة ، لكنني سأخبرك كيف تعمل "تحت الغطاء".
لنبدأ بتثبيت المكتبة:
npm install offscreen-canvas
نحتاج إلى ملف JS منفصل لموظف الويب - قم بإنشاء ملف تجميع منفصل في Webpack أو Parcel:
entry: { 'app': './src/app.js', + 'webgl-worker': './src/webgl-worker.js' }
ستعمل المجمعات على تغيير اسم الملف باستمرار أثناء النشر بسبب مخبئ ذاكرة التخزين المؤقت - سنحتاج إلى كتابة الاسم في HTML باستخدام
علامة التحميل المسبق . هنا المثال سيكون مجردة ، لأن الكود الفعلي سيعتمد إلى حد كبير على ميزات التجميع الخاص بك.
<link type="preload" as="script" href="./webgl-worker.js"> </head>
نحتاج الآن إلى الحصول على عقدة DOM لـ
<canvas>
ومحتويات علامة التحميل المسبق في ملف JS الرئيسي.
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
إذا كان هناك
canvas.transferControlToOffscreen
بتحميل ملف JS إلى عامل الويب. وفي غياب هذه الطريقة - مثل
<script>
منتظم.
أنشئ هذا
webgl-worker.js
للعامل:
import insideWorker from 'offscreen-canvas/inside-worker' const worker = insideWorker(e => { if (e.data.canvas) {
يتحقق
insideWorker
لمعرفة ما إذا كان قد تم تحميله داخل أحد عمال الويب. اعتمادًا على البيئة ، ستطلق أنظمة اتصال مختلفة باستخدام الخيط الرئيسي.
ستقوم المكتبة بتشغيل وظيفة تم تمريرها إلى
insideWorker
لكل رسالة جديدة من سلسلة الرسائل الرئيسية. مباشرة بعد التحميل ،
createWorker
أول رسالة
{ canvas, width, height }
لرسم الإطار الأول على
<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() } })
عند نقل الكود القديم الخاص بـ Three.js إلى أحد العاملين على الويب ، قد تشاهد أخطاء ، نظرًا لأن عامل الويب ليس لديه DOM API. على سبيل المثال ، لا يوجد
document.createElement
لتحميل مواد SVG. لذلك ، في بعض الأحيان سنحتاج إلى برامج تحميل مختلفة في عامل الويب وداخل برنامج نصي عادي. للتحقق من نوع البيئة ، لدينا
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()
ووجهنا الإطار الأول. لكن يجب أن تستجيب معظم مشاهد WebGL لإجراءات المستخدم. على سبيل المثال ، قم بتدوير الكاميرا عندما يتحرك المؤشر أو ارسم إطارًا عند تغيير حجم النافذة. لسوء الحظ ، لا يمكن لعامل الويب الاستماع إلى أحداث DOM. نحتاج إلى الاستماع إليهم في ساحة المشاركات الرئيسية وإرسال رسائل إلى عامل الويب.
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
يؤدي
مع
OffscreenCanvas
هزمت أفاريز على موقعي وحصلت على 100٪ من نقاط Google Lighthouse. ويعمل WebGL في جميع المتصفحات ، حتى بدون دعم
OffscreenCanvas
.
يمكنك إلقاء نظرة على
الموقع المباشر والرمز المصدر للخيط الرئيسي أو
العامل .
باستخدام OffscreenCanvas ، ارتفعت نظارات Google Lighthouse من 95 إلى 100