اختلاف المنظر

مقدمة

كانت مواقع اختلاف المنظر غاضبة جدًا مؤخرًا، ما عليك سوى إلقاء نظرة على ما يلي:

إذا لم تكن معتادًا عليها، فهي المواقع التي تتغير فيها البنية المرئية للصفحة أثناء التمرير. عادةً ما تكون العناصر داخل مقياس الصفحة، يتم تدويرها أو نقلها بشكل متناسب مع موضع التمرير في الصفحة.

صفحة نموذجية تجريبية
صفحة العرض التوضيحي الخاصة بنا مكتملة بتأثير التباين

وما إذا كنت تحب مواقع الاستعراض المظلي أم لا، ولكن ما يمكنك أن تقوله بثقة تامة هو أنها بمثابة ثقب أسود في الأداء. ويرجع السبب في ذلك إلى أن المتصفحات تميل إلى التحسين للتوافق مع الحالة التي يظهر فيها محتوى جديد أعلى أو أسفل الشاشة عند التمرير (بناءً على اتجاه التمرير)، وبصفة عامة، تعمل المتصفحات بشكل أفضل عند ظهور تغييرات بسيطة جدًا أثناء التمرير. بالنسبة إلى موقع اختلاف المنظر، نادرًا ما تكون هذه الحالة موجودة؛ حيث تتغير العناصر المرئية الكبيرة الحجم عدة مرات في جميع أنحاء الصفحة، مما يؤدي إلى إعادة عرض الصفحة بأكملها بواسطة المتصفح.

من المعقول تعميم موقع اختلاف على النحو التالي:

  • عناصر الخلفية التي تغيّر موضعها وتدويرها وتغيير حجمها أثناء التمرير للأعلى وللأسفل
  • محتوى الصفحة، مثل النص أو الصور الأصغر، والذي يتم تمريره بالطريقة المعتادة من أعلى إلى أسفل.

في السابق، تحدّثنا عن أداء التنقّل والطرق التي يمكنك من خلالها تحسين سرعة استجابة تطبيقك، وتعتمد هذه المقالة على هذا الأساس، لذا قد يكون من المفيد قراءة هذه المقالة إذا لم يسبق لك الاطّلاع عليها.

إذًا، هناك سؤالٌ مُلح هو، إذا كنت بصدد إنشاء موقع إلكتروني التمرير المتناوب، هل المطلوب إعادة صياغة الإعلانات باهظة الثمن أو هل هناك أساليب بديلة يمكنك اتّباعها لتحسين الأداء إلى أقصى حد؟ لنلقِ نظرة على الخيارات المتاحة لدينا.

الخيار 1: استخدام عناصر DOM والمواقع المطلقة

يبدو أن هذا هو النهج الافتراضي الذي يتبعه معظم الأشخاص. هناك مجموعة من العناصر داخل الصفحة، وكلما يتم تنشيط حدث تمرير، يتم تنفيذ مجموعة من التحديثات المرئية لتحويلها.

إذا بدأت "المخطّط الزمني في أدوات مطوّري البرامج" في وضع الإطار وتنقَّلت في أنحاء الشاشة، ستلاحظ أنّ هناك عمليات مكلفة للعرض بملء الشاشة، وإذا انتقلت كثيرًا، قد ترى عدّة أحداث تمرير داخل إطار واحد، وسيؤدّي كل منها إلى بدء عمليات التنسيق.

"أدوات مطوري البرامج في Chrome" بدون أحداث التمرير الدوّارة
تعرِض "أدوات مطوّري البرامج" عروض رسم كبيرة وتنسيقات متعددة تشغّلها أحداث في إطار واحد.

من المهم أن تضع في اعتبارك أنه للوصول إلى 60 لقطة في الثانية (وهو معدّل تحديث الشاشة المعتاد الذي يبلغ 60 هرتز) سيكون لدينا ما يزيد قليلاً عن 16 ملي ثانية لإنجاز جميع المهام. في هذا الإصدار الأول، نُجري تحديثات مرئية في كل مرة نحصل فيها على حدث تمرير، ولكن كما ناقشنا في المقالات السابقة حول الصور المتحركة الأكثر تصغيرًا وسرعة أكبر باستخدام requestAnimationFrame وأداء التنقّل، لا يتزامن هذا مع الجدول الزمني لتحديث المتصفّح، وبالتالي إما أن يفوتنا اللقطات أو نجري الكثير من العمل داخل كل منها. قد يؤدي ذلك بسهولة إلى إصدار مظهر رديء وغير طبيعي على موقعك، مما يؤدي إلى خيبة أمل المستخدمين وعدم رضا القطط.

لننقل رمز التعديل من حدث الانتقال للأعلى أو للأسفل في حدث التمرير إلى معاودة الاتصال بـ requestAnimationFrame ونلتقط ببساطة قيمة التمرير في معاودة الاتصال لحدث التمرير.

إذا كررت اختبار التمرير، من المحتمل أن تلاحظ تحسنًا طفيفًا، ولكن ليس كثيرًا. السبب هو أن عملية التخطيط التي نجريها من خلال التمرير ليست مكلفة للغاية، ولكن في حالات الاستخدام الأخرى يمكن أن تكون كذلك بالفعل. نحن الآن نجري الآن على الأقل عملية تنسيق واحدة فقط في كل إطار.

"أدوات مطوري البرامج في Chrome" تتضمن أحداث التمرير السريع المرتدة
تعرِض "أدوات مطوّري البرامج" عروض رسم كبيرة وتنسيقات متعددة تشغّلها أحداث في إطار واحد.

أصبح بإمكاننا الآن معالجة حدث واحد أو مائة حدث تمرير في كل إطار، ولكن من المهم للغاية تخزين أحدث قيمة لاستخدامها عندما يتم تشغيل معاودة الاتصال في requestAnimationFrame وإجراء تحديثات مرئية. وهذه النقطة هي انتقالك من محاولة فرض تحديثات مرئية في كل مرة تتلقى فيها حدث تمرير إلى طلب أن يمنحك المتصفح نافذة مناسبة للقيام بها من خلالها. ألستَ لطيفًا؟

المشكلة الرئيسية في هذا المنهج، requestAnimationFrame أم لا، هي أنّ لدينا طبقة واحدة في الأساس للصفحة بأكملها، ومن خلال نقل هذه العناصر المرئية إلى مكانها، نحتاج إلى عمليات إعادة طلاء كبيرة (ومكلفة). وعادة ما تكون عملية الرسم هي عملية حظر (على الرغم من أن ذلك تغير)، وهذا يعني أن المتصفح لا يمكنه تنفيذ أي عمل آخر وغالبًا ما نتجاوز ميزانية الإطار التي تبلغ 16 ملي ثانية، وتظل الأمور رديئة.

الخيار 2: استخدام عناصر DOM والإحالات الناجحة الثلاثية الأبعاد

وبدلاً من استخدام المواضع المطلقة، يمكننا اتباع نهج آخر وهو تطبيق التحويلات ثلاثية الأبعاد على العناصر. في هذه الحالة، نرى أن العناصر التي تم تطبيق التحويلات ثلاثية الأبعاد لها طبقة جديدة لكل عنصر، وفي متصفحات WebKit، غالبًا ما يؤدي ذلك أيضًا إلى التبديل إلى مكون الأجهزة. أما في الخيار الأول، على النقيض من ذلك، كانت لدينا طبقة واحدة كبيرة للصفحة تحتاج إلى إعادة طلاء عند حدوث أي تغيير، ومعالجة جميع عمليات الرسم والإنشاء بواسطة وحدة المعالجة المركزية (CPU).

وهذا يعني أنه باستخدام هذا الخيار، تكون الأمور مختلفة: من المحتمل أن يكون لدينا طبقة واحدة لأي عنصر نطبق عليه تحويلًا ثلاثي الأبعاد. إذا كان كل ما نقوم به من هذه النقطة هو المزيد من التحويلات على العناصر، فلن نحتاج إلى إعادة طلاء الطبقة، ويمكن لوحدة معالجة الرسومات التعامل مع نقل العناصر وتكوين الصفحة النهائية معًا.

في كثير من الأحيان، يستخدم المستخدمون حيلة -webkit-transform: translateZ(0); ويشاهدون تحسينات سحرية في الأداء، وفي حين أن ذلك يحقق نجاحًا اليوم، هناك مشاكل:

  1. ولا يتوافق مع عدّة متصفّحات.
  2. تفرض هذه السياسة يد المتصفح من خلال إنشاء طبقة جديدة لكل عنصر تم تحويله. يمكن أن تؤدي العديد من الطبقات إلى التأثير سلبًا في الأداء، لذلك استخدمها باعتدال.
  3. وقد تم إيقافه في بعض منافذ WebKit (النقطة الرابعة من أسفل الشاشة).

في حال الانتقال إلى مسار الترجمة الثلاثية الأبعاد، هناك خطوات حذرة، فهي حل مؤقت لمشكلتك. من الناحية المثالية، سنرى خصائص عرض متشابهة من التحويلات ثنائية الأبعاد كما هو الحال مع التحويل الثلاثي الأبعاد. تتقدم المتصفحات بوتيرة مذهلة، لذا نأمل أن يكون هذا ما نراه قبل ذلك.

أخيرًا، يجب أن تهدف إلى تجنب الطلاء حيثما أمكنك ونقل العناصر الموجودة ببساطة حول الصفحة. على سبيل المثال، عادةً ما يستخدم في مواقع الويب المتباينة علامات div ذات الارتفاع الثابت وتغيير موضع الخلفية لتحقيق التأثير. وهذا يعني أنّه يجب إعادة طلاء العنصر في كل بطاقة، وقد يكلفك ذلك من حيث الأداء. بدلاً من ذلك، يمكنك إنشاء العنصر (يمكنك لفّه داخل div باستخدام overflow: hidden إذا لزم الأمر) وترجمته ببساطة بدلاً من ذلك.

الخيار 3: استخدام لوحة ذات موضع ثابت أو WebGL

الخيار الأخير الذي سنضعه في الاعتبار هو استخدام لوحة ذات موضع ثابت في الجزء الخلفي من الصفحة سنرسم إليها الصور المحوّلة. للوهلة الأولى، قد لا يبدو هذا الحل الأكثر أداءً، ولكن هناك في الواقع بعض الفوائد لهذا النهج:

  • لم نعد نطلب الكثير من الجهد الناتج عن وجود عنصر واحد فقط، وهو لوحة الرسم.
  • نتعامل بشكل فعّال مع صورة نقطية واحدة تم تسريعها بالأجهزة.
  • تتناسب واجهة برمجة تطبيقات Canvas2D بشكل كبير مع نوع التحويل الذي نتطلع إلى إجرائه، مما يعني إمكانية إدارة عمليتي التطوير والصيانة بشكل أكبر.

إنّ استخدام عنصر لوحة رسم يمنحنا طبقة جديدة، لكنه طبقة واحدة فقط، بينما في الخيار الثاني تم توفير طبقة جديدة لكل عنصر مع تطبيق تحويل ثلاثي الأبعاد، لذلك لدينا أعباء عمل متزايدة تجمع كل تلك الطبقات معًا. ويُعد هذا أيضًا الحل الأكثر توافقًا في الوقت الحالي في ضوء التطبيقات المختلفة للتحويلات عبر المتصفحات.


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

تعمل هذه الطريقة فعلاً عند التعامل مع الصور الكبيرة (أو العناصر الأخرى التي يمكن كتابتها بسهولة على لوحة رسم)، وبالتأكيد قد يكون التعامل مع كتل نصية كبيرة أكثر صعوبة، ولكن استنادًا إلى موقعك الإلكتروني، قد يكون هذا هو الحل الأنسب. إذا اضطررت إلى التعامل مع النص في لوحة الرسم، عليك استخدام طريقة واجهة برمجة التطبيقات fillText، إلا أنّ ذلك سيكون على حساب تسهيل الاستخدام (ما عليك سوى تحويل النص إلى صورة نقطية)، وسيتعين عليك الآن التعامل مع التفاف الأسطر والكثير من المشاكل الأخرى. إذا كان بإمكانك تجنّبها، ننصحك باستخدامها على الأرجح، ومن المحتمل أن يتم تقديم الخدمة لك باستخدام منهج عمليات التحويل أعلاه.

بالنظر إلى أننا نأخذ هذا إلى أقصى حد ممكن، فليس هناك سبب لافتراض أن عمل المنظر يجب أن يتم داخل عنصر لوحة رسم. وإذا كان المتصفّح يتيح استخدام هذه الصفحة، يمكننا استخدام WebGL. أهم ما في الأمر هو أن WebGL لديها المسار المباشر بين جميع واجهات برمجة التطبيقات وبطاقة الرسومات، ومن ثم فهي مرشحة على الأرجح للوصول إلى 60 لقطة في الثانية، خاصةً إذا كانت تأثيرات الموقع معقّدة.

وقد يكون رد فعلك الفوري هو أن WebGL تفرط في التفكير، أو أنه ليس منتشرًا في كل مكان من حيث الدعم، ولكن إذا كنت تستخدم شيئًا مثل Three.js، يمكنك دائمًا الرجوع إلى استخدام عنصر لوحة رسم وسيتم استخراج الرمز الخاص بك بطريقة متسقة ومحبوبة. ما عليك سوى استخدام Modernizr للتحقق من الدعم المناسب لواجهة برمجة التطبيقات:

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

بشكل أخير في هذا النهج، إذا كنت لا تحبّ إضافة المزيد من العناصر إلى الصفحة، يمكنك دائمًا استخدام لوحة رسم كعنصر خلفية في كل من Firefox وWebKit المستند إلى. من الواضح أن هذا ليس شائعًا في كل مكان، لذا عليك كالعادة التعامل معه بحذر.

القرار بيدك

إنّ السبب الرئيسي وراء الوضع التلقائي للمطوّرين هو توفُّر كل العناصر المتوفّرة في كل قسم بدلاً من استخدام أي خيار من الخيارات الأخرى. وهذا أمر مضلل إلى حد ما، لأن المتصفحات القديمة التي يتم استهدافها من المحتمل أن تقدم تجربة عرض رديئة للغاية. حتى في المتصفحات الحديثة اليوم، لا يؤدي استخدام عناصر ذات موضع مطلق إلى تحقيق أداء جيد بالضرورة!

توفر لك التحولات، بالتأكيد من نوعها، القدرة على العمل مباشرةً مع عناصر DOM وتحقيق عدد لقطات ثابت في الثانية. مفتاح النجاح هنا هو تجنب الطلاء حيثما أمكنك ومحاولة نقل العناصر في كل مكان. ويجب أن تأخذ في الاعتبار أن الطريقة التي تنشئ بها متصفحات WebKit الطبقات لا ترتبط بالضرورة بمحركات المتصفح الأخرى، لذا احرص على اختبارها قبل الالتزام بهذا الحل.

إذا كان هدفك هو الوصول إلى أعلى مستوى من المتصفحات فقط، وتمكّنك من عرض الموقع باستخدام اللوحات، فقد يكون هذا هو الخيار الأفضل لك. وبالتأكيد إذا كنت ستستخدم Three.js، من المفترض أن تتمكن من التبديل والتغيير بين العارضات بسهولة كبيرة حسب الدعم الذي تحتاج إليه.

الخلاصة

لقد قيّمنا بعض المناهج للتعامل مع مواقع اختلاف المنظر، من عناصر ذات موضع مطلق إلى استخدام لوحة ذات موضع ثابت. بالطبع، يعتمد التنفيذ الذي ستتخذه على ما تحاول تحقيقه والتصميم المحدد الذي تعمل به، ولكن من الجيد دائمًا معرفة أن لديك خيارات.

وكالعادة، استخدِم أسلوبك لا تخمّنه، واختبِره.