كيف يعمل محتوى BBC التفاعلي عبر AMP والتطبيقات والويب
نشرت: 2022-03-10في فريق الصحافة المرئية في هيئة الإذاعة البريطانية (BBC) ، ننتج محتوى مرئيًا مثيرًا وجذابًا وتفاعليًا ، بدءًا من الآلات الحاسبة إلى تنسيقات سرد القصص المرئية الجديدة.
يمثل كل تطبيق تحديًا فريدًا لإنتاجه في حد ذاته ، ولكن أكثر من ذلك عندما تفكر في أنه يتعين علينا نشر معظم المشاريع في العديد من اللغات المختلفة. يجب أن يعمل المحتوى الخاص بنا ليس فقط على مواقع BBC News and Sports ولكن على التطبيقات المماثلة لها على iOS و Android ، وكذلك على مواقع الجهات الخارجية التي تستهلك محتوى BBC.
ضع في اعتبارك الآن أن هناك مجموعة متزايدة من الأنظمة الأساسية الجديدة مثل AMP و Facebook Instant Articles و Apple News. كل منصة لها قيودها الخاصة وآلية نشر الملكية. يعد إنشاء محتوى تفاعلي يعمل عبر جميع هذه البيئات تحديًا حقيقيًا. سأصف كيف تعاملنا مع المشكلة في بي بي سي.
مثال: Canonical مقابل AMP
هذا كله نظري بعض الشيء حتى تراه قيد التنفيذ ، لذلك دعونا نتعمق في مثال.
هنا مقال بي بي سي يحتوي على محتوى الصحافة المرئية:
هذه هي النسخة الأساسية للمقالة ، أي النسخة الافتراضية ، والتي ستحصل عليها إذا انتقلت إلى المقالة من الصفحة الرئيسية.
لنلقِ الآن نظرة على نسخة AMP من المقالة:
بينما يبدو الإصداران الأساسي و AMP متماثلين ، إلا أنهما في الواقع نقطتا نهاية مختلفتان لهما سلوك مختلف:
- ينتقل بك الإصدار الأساسي إلى البلد الذي اخترته عند إرسال النموذج.
- لا يقوم إصدار AMP بالتمرير إليك ، حيث لا يمكنك تمرير الصفحة الرئيسية من داخل إطار AMP iframe.
- يُظهر إصدار AMP إطار iframe مقصوصًا به زر "إظهار المزيد" ، اعتمادًا على حجم منفذ العرض وموضع التمرير. هذه سمة من سمات AMP.
بالإضافة إلى الإصدارات الأساسية و AMP من هذه المقالة ، تم شحن هذا المشروع أيضًا إلى تطبيق الأخبار ، وهو نظام أساسي آخر له تعقيداته وقيوده الخاصة. إذن كيف ندعم كل هذه المنصات؟
الأدوات هي المفتاح
نحن لا نبني المحتوى الخاص بنا من الصفر. لدينا سقالة تستند إلى Yeoman والتي تستخدم Node لإنشاء مشروع معياري بأمر واحد.
تأتي المشاريع الجديدة مع Webpack و SASS والنشر وبنية المكونات خارج الصندوق. يتم أيضًا استخدام التدويل في مشاريعنا ، باستخدام نظام قوالب المقاود. كتب توم ماسلن عن هذا بالتفصيل في رسالته ، 13 نصيحة لجعل تصميم الويب سريع الاستجابة متعدد اللغات.
خارج الصندوق ، يعمل هذا جيدًا للترجمة لمنصة واحدة ولكننا نحتاج إلى دعم منصات متعددة . دعنا نتعمق في بعض التعليمات البرمجية.
تضمين مقابل مستقل
في الصحافة المرئية ، نخرج أحيانًا المحتوى الخاص بنا داخل إطار iframe بحيث يمكن أن يكون "مضمّنًا" بذاته في مقالة ، ولا يتأثر بالبرمجة والتصميم العالميين. مثال على ذلك هو تفاعل دونالد ترامب المضمن في المثال الأساسي في وقت سابق من هذه المقالة.
من ناحية أخرى ، في بعض الأحيان نخرج المحتوى الخاص بنا على هيئة HTML خام. نحن نفعل ذلك فقط عندما يكون لدينا سيطرة على الصفحة بأكملها أو إذا كنا نحتاج إلى تفاعل تمرير سريع الاستجابة. دعنا نسمي هذه المخرجات "التضمين" و "المستقلة" على التوالي.
لنتخيل كيف يمكننا بناء "هل سيأخذ الروبوت وظيفتك؟" تفاعلي في كل من التنسيقين "التضمين" و "المستقل".
سيشترك كلا الإصدارين من المحتوى في الغالبية العظمى من التعليمات البرمجية الخاصة بهما ، ولكن قد تكون هناك بعض الاختلافات الجوهرية في تنفيذ JavaScript بين الإصدارين.
على سبيل المثال ، انظر إلى الزر "اكتشف مخاطر التشغيل التلقائي الخاص بي". عندما يضغط المستخدم على زر الإرسال ، يجب أن يتم تمريره تلقائيًا إلى نتائجه.
قد يبدو الإصدار "المستقل" من الشفرة كما يلي:
button.on('click', (e) => { window.scrollTo(0, resultsContainer.offsetTop); });
ولكن إذا كنت تقوم ببناء هذا كإخراج "تضمين" ، فأنت تعلم أن المحتوى الخاص بك موجود داخل إطار iframe ، لذلك ستحتاج إلى ترميزه بشكل مختلف:
// inside the iframe button.on('click', () => { window.parent.postMessage({ name: 'scroll', offset: resultsContainer.offsetTop }, '*'); }); // inside the host page window.addEventListener('message', (event) => { if (event.data.name === 'scroll') { window.scrollTo(0, iframe.offsetTop + event.data.offset); } });
أيضًا ، ماذا لو احتاج تطبيقنا إلى ملء الشاشة؟ هذا سهل بما يكفي إذا كنت في صفحة "قائمة بذاتها":
document.body.className += ' fullscreen';
.fullscreen { position: fixed; top: 0; left: 0; right: 0; bottom: 0; }
إذا حاولنا القيام بذلك من داخل "التضمين" ، فسيكون لهذا الكود نفسه حجم المحتوى لعرض iframe وارتفاعه ، بدلاً من إطار العرض:
... لذلك بالإضافة إلى تطبيق نمط ملء الشاشة داخل إطار iframe ، يتعين علينا إرسال رسالة إلى الصفحة المضيفة لتطبيق النمط على إطار iframe نفسه:
// iframe window.parent.postMessage({ name: 'window:toggleFullScreen' }, '*'); // host page window.addEventListener('message', function () { if (event.data.name === 'window:toggleFullScreen') { document.getElementById(iframeUid).className += ' fullscreen'; } });
يمكن أن يترجم هذا إلى الكثير من أكواد السباغيتي عندما تبدأ في دعم منصات متعددة:
button.on('click', (e) => { if (inStandalonePage()) { window.scrollTo(0, resultsContainer.offsetTop); } else { window.parent.postMessage({ name: 'scroll', offset: resultsContainer.offsetTop }, '*'); } });
تخيل القيام بما يعادل ذلك لكل تفاعل DOM ذي معنى في مشروعك. بمجرد الانتهاء من الارتجاف ، اصنع لنفسك كوبًا من الشاي للاسترخاء ، واستمر في القراءة.
التجريد هو المفتاح
بدلاً من إجبار مطورينا على التعامل مع هذه الشروط داخل التعليمات البرمجية الخاصة بهم ، قمنا ببناء طبقة تجريد بين المحتوى الخاص بهم والبيئة. نسمي هذه الطبقة "الغلاف".
بدلاً من الاستعلام عن DOM أو أحداث المستعرض الأصلي مباشرةً ، يمكننا الآن تفويض طلبنا من خلال وحدة wrapper
.
import wrapper from 'wrapper'; button.on('click', () => { wrapper.scrollTo(resultsContainer.offsetTop); });
كل نظام أساسي له تطبيق الغلاف الخاص به والذي يتوافق مع واجهة مشتركة لطرق الغلاف. يلتف الغلاف حول المحتوى الخاص بنا ويتعامل مع التعقيد من أجلنا.
يعد تنفيذ الغلاف المستقل لوظيفة scrollTo
بسيطًا للغاية ، حيث يتم تمرير وسيطتنا مباشرة إلى window.scrollTo
أسفل الغطاء.
لنلقِ نظرة الآن على غلاف منفصل ينفذ نفس الوظيفة لإطار iframe:
يأخذ غلاف "التضمين" نفس الوسيطة كما في المثال "المستقل" ولكنه يعالج القيمة بحيث يتم أخذ إزاحة iframe في الاعتبار. بدون هذه الإضافة ، كنا سنقوم بالتمرير على مستخدمنا في مكان ما غير مقصود تمامًا.
نمط الغلاف
يؤدي استخدام الأغلفة إلى رمز أنظف وأكثر قابلية للقراءة ومتسقًا بين المشاريع. كما يسمح أيضًا بإجراء تحسينات دقيقة بمرور الوقت ، حيث نجري تحسينات تدريجية على الأغلفة لجعل أساليبها أكثر أداءً ويمكن الوصول إليها. لذلك يمكن أن يستفيد مشروعك من خبرة العديد من المطورين.
إذن ، كيف يبدو الغلاف؟
هيكل غلاف
يتكون كل غلاف بشكل أساسي من ثلاثة أشياء: قالب المقاود وملف الغلاف JS وملف SASS يشير إلى التصميم الخاص بالغلاف. بالإضافة إلى ذلك ، هناك مهام بناء مرتبطة بالأحداث التي كشفتها السقالات الأساسية بحيث يكون كل غلاف مسؤولاً عن التجميع والتنظيف المسبق الخاص به.
هذه طريقة عرض مبسطة للغلاف المضمن:
embed-wrapper/ templates/ wrapper.hbs js/ wrapper.js scss/ wrapper.scss
تعرض السقالات الأساسية الخاصة بنا قالب مشروعك الرئيسي على أنه جزء من المقاود ، والذي يستهلكه الغلاف. على سبيل المثال ، قد تحتوي templates/wrapper.hbs
على:
<div class="bbc-news-vj-wrapper--embed"> {{>your-application}} </div>
يحتوي scss/wrapper.scss
على نمط خاص بالغلاف لا يحتاج كود التطبيق الخاص بك إلى تعريفه بنفسه. غلاف التضمين ، على سبيل المثال ، يكرر الكثير من تصميم BBC News داخل iframe.
أخيرًا ، يحتوي js/wrapper.js
على تنفيذ iframed لواجهة برمجة تطبيقات المجمّع ، بالتفصيل أدناه. يتم شحنها بشكل منفصل إلى المشروع ، بدلاً من تجميعها مع رمز التطبيق - نحن نضع علامة على wrapper
على أنه عالمي في عملية بناء Webpack الخاصة بنا. هذا يعني أنه على الرغم من أننا نقدم تطبيقنا إلى منصات متعددة ، فإننا نقوم بتجميع الكود مرة واحدة فقط.
غلاف API
تلخص واجهة API الخاصة بالغلاف عددًا من تفاعلات المستعرض الرئيسية. فيما يلي أهمها:
scrollTo(int)
يقوم بالتمرير إلى الموضع المحدد في النافذة النشطة. سيعمل الغلاف على تسوية العدد الصحيح المقدم قبل تشغيل التمرير بحيث يتم تمرير صفحة المضيف إلى الموضع الصحيح.
getScrollPosition: int
تُرجع موضع التمرير الحالي (المعدل) للمستخدم. في حالة iframe ، يعني هذا أن موضع التمرير الذي تم تمريره إلى تطبيقك يكون سالبًا حتى يصبح iframe أعلى إطار العرض. هذا مفيد للغاية ويتيح لنا القيام بأشياء مثل تحريك أحد المكونات فقط عندما يتم عرضه.
onScroll(callback)
يوفر رابطًا في حدث التمرير. في الغلاف المستقل ، يتم ربط هذا بشكل أساسي بحدث التمرير الأصلي. في غلاف التضمين ، سيكون هناك تأخير طفيف في تلقي حدث التمرير نظرًا لأنه يتم تمريره عبر postMessage.
viewport: {height: int, width: int}
طريقة لاسترداد ارتفاع وعرض إطار العرض (حيث يتم تنفيذ ذلك بشكل مختلف تمامًا عند الاستعلام من داخل إطار iframe).
toggleFullScreen
في الوضع المستقل ، نخفي قائمة BBC والتذييل من العرض ونحدد position: fixed
على المحتوى الخاص بنا. في تطبيق الأخبار ، لا نفعل شيئًا على الإطلاق - المحتوى في وضع ملء الشاشة بالفعل. الأكثر تعقيدًا هو iframe ، والذي يعتمد على تطبيق الأنماط داخل وخارج إطار iframe ، بالتنسيق عبر postMessage.
markPageAsLoaded
أخبر الغلاف أنه تم تحميل المحتوى الخاص بك. يعد هذا أمرًا بالغ الأهمية لكي يعمل المحتوى الخاص بنا في تطبيق الأخبار ، والذي لن يحاول عرض المحتوى الخاص بنا للمستخدم حتى نخبر التطبيق صراحة أن المحتوى الخاص بنا جاهز. كما أنه يزيل القرص الدوار للتحميل على إصدارات الويب للمحتوى الخاص بنا.
قائمة الأغلفة
في المستقبل ، نتصور إنشاء أغلفة إضافية لمنصات كبيرة مثل Facebook Instant Articles و Apple News. لقد أنشأنا ستة أغلفة حتى الآن:
غلاف مستقل
إصدار المحتوى الخاص بنا الذي يجب أن يتم عرضه في صفحات مستقلة. يأتي مرفقًا بعلامة بي بي سي التجارية.
تضمين الغلاف
النسخة ذات الإطارات المضمنة من المحتوى الخاص بنا ، والتي يمكن وضعها داخل المقالات بأمان أو المشاركة في مواقع غير تابعة لـ BBC ، نظرًا لأننا نحتفظ بالسيطرة على المحتوى.
غلاف AMP
هذه هي نقطة النهاية التي يتم سحبها باعتبارها amp-iframe
إلى صفحات AMP.
غلاف تطبيق الأخبار
يجب أن يقوم المحتوى الخاص بنا بإجراء مكالمات إلى bbcvisualjournalism://
protocol.
غلاف الأساسية
يحتوي فقط على HTML - لا يحتوي على CSS أو JavaScript في مشروعنا.
غلاف JSON
تمثيل JSON للمحتوى الخاص بنا ، للمشاركة عبر منتجات BBC.
مغلفة الأسلاك حتى المنصات
لكي يظهر المحتوى الخاص بنا على موقع بي بي سي ، فإننا نوفر للصحفيين مسارًا بمسافة الأسماء:
/include/[department]/[unique ID], eg
/include/visual-journalism/123-quiz
يضع الصحفي هذا "مسار التضمين" في نظام إدارة المحتوى ، والذي يحفظ بنية المقالة في قاعدة البيانات. يتم وضع جميع المنتجات والخدمات في نهاية آلية النشر هذه. كل منصة مسؤولة عن اختيار نكهة المحتوى الذي تريده وطلب ذلك المحتوى من خادم وكيل.
لنأخذ تفاعل دونالد ترامب هذا من قبل. هنا ، مسار التضمين في CMS هو:
/include/newsspec/15996-trump-tracker/english/index
تعرف صفحة المقالة الأساسية أنها تريد إصدار "التضمين" من المحتوى ، لذا فهي تلحق /embed
تضمّن مسار التضمين:
/include/newsspec/15996-trump-tracker/english/index
/embed
... قبل طلبه من الخادم الوكيل:
https://news.files.bbci.co.uk/include/newsspec/15996-trump-tracker/english/index/embed
من ناحية أخرى ، ترى صفحة AMP مسار التضمين والملحقات /amp
:
/include/newsspec/15996-trump-tracker/english/index
/amp
يقدم عارض AMP بعض السحر في عرض بعض AMP HTML الذي يشير إلى المحتوى الخاص بنا ، ويسحب إصدار /amp
كإطار iframe:
<amp-iframe src="https://news.files.bbci.co.uk/include/newsspec/15996-trump-tracker/english/index/amp" width="640" height="360"> <!-- some other AMP elements here --> </amp-iframe>
كل منصة مدعومة لها نسختها الخاصة من المحتوى:
/include/newsspec/15996-trump-tracker/english/index
/amp
/include/newsspec/15996-trump-tracker/english/index
/core
/include/newsspec/15996-trump-tracker/english/index
/envelope
...وما إلى ذلك وهلم جرا
يمكن توسيع هذا الحل لدمج المزيد من أنواع الأنظمة الأساسية عند ظهورها.
التجريد صعب
إن بناء هندسة "اكتب مرة واحدة ، ونشر في أي مكان" تبدو مثالية تمامًا ، وهي كذلك. لكي تعمل بنية الغلاف ، يجب أن نكون صارمين للغاية في العمل ضمن التجريد. هذا يعني أنه يتعين علينا محاربة إغراء "القيام بهذا الشيء المخترق لجعله يعمل في [أدخل اسم النظام الأساسي هنا]." نريد أن يكون المحتوى الخاص بنا غير مدرك تمامًا للبيئة التي يتم شحنه فيها - ولكن قول هذا أسهل من فعله.
يصعب تكوين ميزات النظام الأساسي بشكل تجريدي
قبل نهج التجريد الخاص بنا ، كان لدينا سيطرة كاملة على كل جانب من جوانب مخرجاتنا ، بما في ذلك ، على سبيل المثال ، ترميز إطار iframe الخاص بنا. إذا احتجنا إلى تعديل أي شيء على أساس كل مشروع ، مثل إضافة سمة title
إلى إطار iframe لأسباب تتعلق بإمكانية الوصول ، فيمكننا فقط تحرير الترميز.
الآن بعد أن وجد ترميز الغلاف بمعزل عن المشروع ، فإن الطريقة الوحيدة لتكوينه هي كشف خطاف في السقالة نفسها. يمكننا القيام بذلك بسهولة نسبيًا بالنسبة لميزات الأنظمة الأساسية ، لكن الكشف عن الخطافات لمنصات معينة يكسر التجريد. لا نريد حقًا الكشف عن خيار تكوين "عنوان iframe" الذي يستخدمه الغلاف الواحد فقط.
يمكننا تسمية الخاصية بشكل أكثر عمومية ، على سبيل المثال title
، ثم استخدام هذه القيمة كسمة title
iframe. ومع ذلك ، فقد أصبح من الصعب تتبع ما يتم استخدامه وأين يتم استخدامه ، ونحن نجازف بتجريد التكوين الخاص بنا إلى درجة عدم فهمه بعد الآن. على العموم ، نحاول أن نحافظ على تكويناتنا بسيطة قدر الإمكان ، فقط نعيِّن الخصائص التي لها استخدام عالمي.
يمكن أن يكون سلوك المكونات معقدًا
على الويب ، تقوم وحدة أدوات المشاركة الخاصة بنا بإخراج أزرار مشاركة الشبكة الاجتماعية التي يمكن النقر عليها بشكل فردي وتفتح رسالة مشاركة مملوءة مسبقًا في نافذة جديدة.
في تطبيق الأخبار ، لا نريد المشاركة عبر ويب الجوال. إذا كان المستخدم لديه التطبيق ذي الصلة مثبتًا (مثل Twitter) ، فنحن نريد المشاركة في التطبيق نفسه. من الناحية المثالية ، نريد أن نقدم للمستخدم قائمة مشاركة iOS / Android الأصلية ، ثم نسمح له باختيار خيار المشاركة قبل أن نفتح التطبيق لهم برسالة مشاركة مملوءة مسبقًا. يمكننا تشغيل قائمة المشاركة الأصلية من التطبيق عن طريق الاتصال بالملكية bbcvisualjournalism://
protocol.
ومع ذلك ، سيتم تشغيل هذه الشاشة سواء نقرت على "Twitter" أو "Facebook" في قسم "مشاركة نتائجك" ، بحيث يضطر المستخدم إلى الاختيار مرتين ؛ لأول مرة داخل المحتوى الخاص بنا ، ومرة ثانية في النافذة المنبثقة الأصلية.
هذه رحلة مستخدم غريبة ، لذلك نريد إزالة رموز المشاركة الفردية من تطبيق الأخبار وإظهار زر مشاركة عام بدلاً من ذلك. يمكننا القيام بذلك عن طريق التحقق صراحة من أي غلاف قيد الاستخدام قبل عرض المكون.
يعمل بناء طبقة تجريد الغلاف جيدًا للمشروعات ككل ، ولكن عندما يؤثر اختيارك للغلاف على التغييرات على مستوى المكون ، يكون من الصعب جدًا الاحتفاظ بتجريد نظيف. في هذه الحالة ، فقدنا القليل من التجريد ، ولدينا بعض منطق التفرع الفوضوي في الكود الخاص بنا. لحسن الحظ ، هذه الحالات قليلة ومتباعدة.
كيف نتعامل مع الميزات المفقودة؟
الحفاظ على التجريد هو كل شيء جيد وجيد. يخبر الكود الخاص بنا الغلاف بما يريد أن يفعله النظام الأساسي ، على سبيل المثال "الانتقال إلى وضع ملء الشاشة". ولكن ماذا لو لم تتمكن المنصة التي نقوم بالشحن إليها من العمل في وضع ملء الشاشة؟
سيبذل الغلاف قصارى جهده حتى لا ينكسر تمامًا ، لكنك في النهاية تحتاج إلى تصميم يعود بأمان إلى حل عملي سواء نجحت الطريقة أم لا. علينا أن نصمم بشكل دفاعي.
لنفترض أن لدينا قسم نتائج يحتوي على بعض المخططات الشريطية. غالبًا ما نرغب في الاحتفاظ بقيم المخطط الشريطي عند الصفر حتى يتم تمرير المخططات للعرض ، وعند هذه النقطة نقوم بتشغيل الأشرطة المتحركة إلى عرضها الصحيح.
ولكن إذا لم يكن لدينا آلية للربط في موضع التمرير - كما هو الحال في غلاف AMP الخاص بنا - فستظل الأشرطة عند الصفر إلى الأبد ، وهي تجربة مضللة تمامًا.
نحن نحاول بشكل متزايد اعتماد نهج التحسين التدريجي في تصميماتنا. على سبيل المثال ، يمكننا توفير زر سيكون مرئيًا لجميع الأنظمة الأساسية افتراضيًا ، ولكن يتم إخفاؤه إذا كان الغلاف يدعم التمرير. بهذه الطريقة ، إذا فشل التمرير في تشغيل الرسوم المتحركة ، فلا يزال بإمكان المستخدم تشغيل الرسوم المتحركة يدويًا.
خطط للمستقبل
نأمل في تطوير أغلفة جديدة لمنصات مثل Apple News و Facebook Instant Articles ، بالإضافة إلى تقديم إصدار "أساسي" من المحتوى الخاص بنا لجميع الأنظمة الأساسية الجديدة خارج الصندوق.
نأمل أيضًا أن نتحسن في التحسين التدريجي ؛ النجاح في هذا المجال يعني التطوير الدفاعي. لا يمكنك أبدًا افتراض أن جميع الأنظمة الأساسية الآن وفي المستقبل ستدعم تفاعلًا معينًا ، ولكن يجب أن يكون المشروع المصمم جيدًا قادرًا على إيصال رسالته الأساسية دون الوقوع في العقبة التقنية الأولى.
يعد العمل ضمن حدود الغلاف بمثابة نقلة نوعية إلى حد ما ، ويشعر وكأنه منزل في منتصف الطريق من حيث الحل طويل الأجل . ولكن حتى تنضج الصناعة إلى معيار عبر الأنظمة الأساسية ، سيضطر الناشرون إلى طرح حلولهم الخاصة ، أو استخدام أدوات مثل Distro للتحويل من نظام أساسي إلى نظام أساسي ، أو تجاهل أقسام كاملة من جمهورهم تمامًا.
إنها الأيام الأولى بالنسبة لنا ، ولكن حتى الآن حققنا نجاحًا كبيرًا في استخدام نمط الغلاف لبناء المحتوى الخاص بنا مرة واحدة وتقديمه إلى عدد لا يحصى من الأنظمة الأساسية التي يستخدمها جمهورنا الآن.