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

مقدمة

أصبحت المواقع الإلكترونية التي تستخدم تأثير التمويه من أكثر المواقع رواجًا مؤخرًا، وننصحك بإلقاء نظرة على ما يلي:

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

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

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

من المنطقي تعميم الموقع الإلكتروني الذي يستخدم تأثير المجسم المتداخل على النحو التالي:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


/**
 * 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 API، ولكن سيترتب على ذلك فقدان إمكانية الوصول (لقد حوّلت النص إلى صورة نقطية) وسيكون عليك الآن التعامل مع لف السطور ومجموعة كبيرة من المشاكل الأخرى. ننصحك بتجنُّب ذلك قدر الإمكان، ومن المرجّح أن تستفيد أكثر من استخدام نهج عمليات التحويل أعلاه.

بما أنّنا نسعى إلى تحسين هذه الميزة إلى أقصى حدّ ممكن، ليس هناك سبب لافتراض أنّه يجب تنفيذ تأثير التمويه البصري داخل عنصر لوحة. يمكننا استخدام 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، من المفترض أن تتمكّن من التبديل بين مُنشئِي الصور بسهولة كبيرة استنادًا إلى الدعم الذي تحتاجه.

الخاتمة

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

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