BBC 互動內容如何跨 AMP、應用程序和 Web 工作

已發表: 2022-03-10
快速總結 ↬在沒有大量額外開發開銷的情況下將內容髮佈到如此多的媒體可能很困難。 Chris Ashton 解釋了他們是如何在 BBC 的視覺新聞部門解決這個問題的。

在 BBC 的視覺新聞團隊中,我們製作令人興奮的視覺、引人入勝和互動的內容,從計算器到可視化新的故事講述格式。

每個應用程序本身都是一個獨特的挑戰,但當您考慮到我們必須以多種不同的語言部署大多數項目時更是如此。 我們的內容不僅可以在 BBC 新聞和體育網站上運行,而且還可以在 iOS 和 Android 上的等效應用程序以及使用 BBC 內容的第三方網站上運行。

現在考慮有越來越多的新平台,例如 AMP、Facebook Instant Articles 和 Apple News。 每個平台都有自己的局限性和專有的發布機制。 創建適用於所有這些環境的交互式內容是一項真正的挑戰。 我將描述我們在 BBC 是如何解決這個問題的。

示例:Canonical 與 AMP

在您實際看到它之前,這都是理論性的,所以讓我們直接研究一個示例。

這是包含視覺新聞內容的 BBC 文章:

包含視覺新聞內容的 BBC 新聞頁面截圖
我們的視覺新聞內容以唐納德特朗普的插圖開始,並位於 iframe 中

這是文章的規範版本,即默認版本,如果您從主頁導航到文章,您將獲得該版本。

跳躍後更多! 繼續往下看↓

現在讓我們看一下文章的AMP版本:

BBC 新聞 AMP 頁面的屏幕截圖,其中包含與以前相同的內容,但內容已被剪輯並具有“顯示更多”按鈕
這看起來與普通文章的內容相同,但引入了專門為 AMP 設計的不同 iframe

雖然規範版本和 AMP 版本看起來相同,但它們實際上是具有不同行為的兩個不同端點

  • 當您提交表格時,規範版本會將您滾動到您選擇的國家/地區。
  • AMP 版本不會滾動您,因為您無法從 AMP iframe 中滾動父頁面。
  • AMP 版本顯示帶有“顯示更多”按鈕的裁剪 iframe,具體取決於視口大小和滾動位置。 這是 AMP 的一個功能。

除了本文的規範版本和 AMP 版本外,該項目還發佈到 News App,這是另一個具有自身複雜性和局限性的平台。 那麼我們如何支持所有這些平台呢?

工具是關鍵

我們不會從頭開始構建我們的內容。 我們有一個基於 Yeoman 的腳手架,它使用 Node 通過單個命令生成樣板項目。

新項目帶有開箱即用的 Webpack、SASS、部署和組件化結構。 國際化也融入了我們的項目,使用 Handlebars 模板系統。 Tom Maslen 在他的帖子中詳細描述了這一點,13 條使響應式網頁設計成為多語言的技巧。

開箱即用,這對於為一個平台進行編譯非常有效,但我們需要支持多個平台。 讓我們深入研究一些代碼。

嵌入與獨立

在視覺新聞中,我們有時會在 iframe 中輸出我們的內容,以便它可以獨立“嵌入”文章中,不受全局腳本和样式的影響。 這方面的一個例子是嵌入在本文前面的規範示例中的 Donald Trump 交互。

另一方面,有時我們將內容輸出為原始 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 本身:

 // 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 交互都做同樣的事情。 一旦你完成了顫抖,給自己泡一杯放鬆的茶,然後繼續閱讀。

抽像是關鍵

我們沒有強迫我們的開發人員在他們的代碼中處理這些條件,而是在他們的內容和環境之間建立了一個抽象層。 我們將此層稱為“包裝器”。

我們現在可以通過wrapper模塊代理我們的請求,而不是直接查詢 DOM 或本機瀏覽器事件。

 import wrapper from 'wrapper'; button.on('click', () => { wrapper.scrollTo(resultsContainer.offsetTop); });

每個平台都有自己的包裝器實現,符合包裝器方法的公共接口。 包裝器將自己包裹在我們的內容周圍並為我們處理複雜性。

UML 圖顯示當我們的應用程序調用獨立包裝器滾動方法時,包裝器調用宿主頁面中的本機滾動方法。
獨立包裝器的簡單“scrollTo”實現

獨立包裝器對scrollTo函數的實現非常簡單,在底層直接將我們的參數傳遞給window.scrollTo

現在讓我們看一個為 iframe 實現相同功能的單獨包裝器:

UML 圖顯示當我們的應用程序調用嵌入包裝器滾動方法時,嵌入包裝器在觸發宿主頁面中的本機滾動方法之前將請求的滾動位置與 iframe 的偏移量結合起來。
嵌入包裝器的高級“scrollTo”實現

“嵌入”包裝器採用與“獨立”示例中相同的參數,但會操縱該值,以便將 iframe 偏移量考慮在內。 如果沒有這個添加,我們會在完全無意的地方滾動我們的用戶。

包裝器模式

使用包裝器可以使代碼在項目之間更乾淨、更易讀和一致。 它還允許隨著時間的推移進行微優化,因為我們對包裝器進行增量改進以使其方法更具性能和可訪問性。 因此,您的項目可以從許多開發人員的經驗中受益。

那麼,包裝器是什麼樣的呢?

包裝器結構

每個包裝器基本上包含三樣東西:Handlebars 模板、包裝器 JS 文件和表示包裝器特定樣式的 SASS 文件。 此外,還有一些構建任務與底層腳手架暴露的事件掛鉤,以便每個包裝器負責自己的預編譯和清理。

這是嵌入包裝器的簡化視圖:

 embed-wrapper/ templates/ wrapper.hbs js/ wrapper.js scss/ wrapper.scss

我們的底層腳手架將您的主項目模板公開為 Handlebars 部分,由包裝器使用。 例如, templates/wrapper.hbs可能包含:

 <div class="bbc-news-vj-wrapper--embed"> {{>your-application}} </div>

scss/wrapper.scss包含您的應用程序代碼不需要自己定義的特定於包裝器的樣式。 例如,嵌入包裝器在 iframe 中復制了許多 BBC 新聞樣式。

最後, js/wrapper.js包含包裝器 API 的 iframed 實現,詳情如下。 它是單獨交付到項目中的,而不是與應用程序代碼一起編譯的——我們在 Webpack 構建過程中將wrapper標記為全局。 這意味著儘管我們將應用程序交付到多個平台,但我們只編譯一次代碼。

包裝器 API

包裝 API 抽象了許多關鍵的瀏覽器交互。 以下是最重要的:

scrollTo(int)

滾動到活動窗口中的給定位置。 包裝器將在觸發滾動之前對提供的整數進行規範化,以便將主機頁面滾動到正確的位置。

getScrollPosition: int

返回用戶的當前(標準化)滾動位置。 在 iframe 的情況下,這意味著傳遞給您的應用程序的滾動位置實際上是負數,直到 iframe 位於視口的頂部。 這非常有用,可以讓我們做一些事情,比如只在組件出現時才對其進行動畫處理。

onScroll(callback)

提供滾動事件的掛鉤。 在獨立包裝器中,​​這本質上是掛鉤到本機滾動事件。 在嵌入包裝器中,​​接收滾動事件會稍有延遲,因為它是通過 postMessage 傳遞的。

viewport: {height: int, width: int}

一種檢索視口高度和寬度的方法(因為從 iframe 中查詢時實現方式非常不同)。

toggleFullScreen

在獨立模式下,我們隱藏 BBC 菜單和頁腳並設置position: fixed在我們的內容上。 在 News App 中,我們什麼都不做——內容已經全屏顯示。 複雜的是 iframe,它依賴於在 iframe 內部和外部應用樣式,通過 postMessage 進行協調。

markPageAsLoaded

告訴包裝器您的內容已加載。 這對於我們的內容在 News App 中的工作至關重要,在我們明確告訴應用我們的內容已準備好之前,它不會嘗試向用戶顯示我們的內容。 它還刪除了我們內容的網絡版本上的加載微調器。

包裝器列表

未來,我們設想為 Facebook Instant Articles 和 Apple News 等大型平台創建額外的包裝器。 迄今為止,我們已經創建了六個包裝器:

獨立包裝器

我們的內容版本應該放在獨立頁面中。 與 BBC 品牌捆綁在一起。

嵌入包裝器

我們內容的 iframe 版本,可以安全地放在文章中或聯合到非 BBC 網站,因為我們保留對內容的控制。

AMP 包裝器

這是作為amp-iframe拉入 AMP 頁面的端點。

新聞應用包裝器

我們的內容必須調用專有的bbcvisualjournalism://協議。

核心包裝器

僅包含 HTML — 不包含我們項目的 CSS 或 JavaScript。

JSON 包裝器

我們內容的 JSON 表示,用於在 BBC 產品之間共享。

將包裝器連接到平台

為了讓我們的內容出現在 BBC 網站上,我們為記者提供了一個命名空間路徑:

 /include/[department]/[unique ID], eg /include/visual-journalism/123-quiz

記者將此“包含路徑”放入CMS,將文章結構保存到數據庫中。 所有產品和服務都位於此發布機制的下游。 每個平台負責選擇它想要的內容風格並從代理服務器請求該內容。

讓我們以之前的唐納德特朗普互動為例。 在這裡,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

...等等

該解決方案可以擴展以在出現更多平台類型時合併它們。

抽像是困難的

構建“一次編寫,隨處部署”的架構聽起來很理想化,而且確實如此。 為了使包裝器架構正常工作,我們必須非常嚴格地在抽像中工作。 這意味著我們必須抵制“做這個駭人聽聞的事情以使其在 [insert platform name here] 中工作”的誘惑。 我們希望我們的內容完全不了解它的交付環境——但這說起來容易做起來難。

平台的功能難以抽象配置

在我們採用抽象方法之前,我們可以完全控制輸出的各個方面,例如,包括 iframe 的標記。 如果我們需要在每個項目的基礎上調整任何內容,例如出於可訪問性原因向 iframe 添加title屬性,我們可以只編輯標記。

現在包裝器標記與項目隔離存在,配置它的唯一方法是在腳手架本身中公開一個鉤子。 對於跨平台功能,我們可以相對輕鬆地做到這一點,但是為特定平台公開掛鉤會破壞抽象。 我們真的不想公開僅由一個包裝器使用的“iframe 標題”配置選項。

我們可以更通用地命名該屬性,例如title ,然後將此值用作 iframe title屬性。 但是,要跟踪在哪裡使用了什麼變得越來越困難,並且我們冒著將配置抽像到不再理解它的風險。 總的來說,我們盡量保持我們的配置精簡,只設置具有全局使用的屬性。

組件行為可能很複雜

在網絡上,我們的 sharetools 模塊吐出社交網絡共享按鈕,這些按鈕可以單獨點擊,並在新窗口中打開預先填充的共享消息。

BBC sharetools 部分的屏幕截圖包含 Twitter 和 Facebook 社交媒體圖標。
BBC 視覺新聞分享工具提供社交分享選項列表

在新聞應用中,我們不想通過移動網絡分享。 如果用戶安裝了相關的應用程序(例如 Twitter),我們希望在應用程序本身中共享。 理想情況下,我們希望向用戶展示原生 iOS/Android 共享菜單,然後讓他們選擇他們的共享選項,然後再通過預先填充的共享消息為他們打開應用程序。 我們可以通過調用專有的bbcvisualjournalism://協議從應用程序觸發本機共享菜單。

Android 上共享菜單的屏幕截圖,其中包含通過消息、藍牙、複製到剪貼板等進行共享的選項。
Android 上的本機共享菜單

但是,無論您在“分享您的結果”部分中點擊“Twitter”還是“Facebook”,都會觸發此屏幕,因此用戶最終不得不做出兩次選擇; 第一次在我們的內容中,第二次在本機彈出窗口中。

這是一個奇怪的用戶旅程,所以我們想從新聞應用程序中刪除單個共享圖標,並顯示一個通用的共享按鈕。 我們可以通過在渲染組件之前顯式檢查正在使用的包裝器來做到這一點。

新聞應用分享按鈕的屏幕截圖。這是一個帶有以下文本的按鈕:“分享你的做法”。
新聞應用程序中使用的通用分享按鈕

構建包裝器抽象層對於整個項目來說效果很好,但是當您選擇的包裝器會影響組件級別的更改時,很難保持清晰的抽象。 在這種情況下,我們失去了一點抽象,我們的代碼中有一些混亂的分叉邏輯。 值得慶幸的是,這些案例很少見。

我們如何處理缺失的功能?

保持抽像一切都很好。 我們的代碼告訴包裝器它希望平台做什麼,例如“全屏顯示”。 但是,如果我們要運送到的平台實際上不能全屏顯示怎麼辦?

包裝器將盡最大努力不完全破壞,但最終您需要一個設計,無論該方法是否成功,它都能優雅地回退到一個工作解決方案。 我們必須進行防守設計。

假設我們有一個包含一些條形圖的結果部分。 我們經常喜歡將條形圖的值保持為零,直到圖表滾動到視圖中,此時我們觸發條形動畫到正確的寬度。

將用戶區域與全國平均水平進行比較的一組條形圖的屏幕截圖。每個條的值都顯示為條右側的文本。
顯示與我所在區域相關的值的條形圖

但是,如果我們沒有機制來掛鉤滾動位置(就像我們的 AMP 包裝器中的情況一樣),那麼這些條將永遠保持為零,這是一種完全誤導的體驗。

條形圖的屏幕截圖與以前相同,但條形圖有 0&#37;寬度和每個條的值固定為 0&#37;。這是不正確的。
如果不轉發滾動事件,條形圖的外觀

我們越來越多地嘗試在我們的設計中採用更多的漸進增強方法。 例如,我們可以提供一個按鈕,默認情況下對所有平台都可見,但如果包裝器支持滾動,它會隱藏。 這樣,如果滾動未能觸發動畫,用戶仍然可以手動觸發動畫。

與錯誤 0&#37; 相同的條形圖屏幕截圖條形圖,但這次帶有微妙的灰色疊加層和一個居中的按鈕,邀請用戶“查看結果”。
我們可以改為顯示一個後備按鈕,它會在點擊時觸發動畫。

對未來的計劃

我們希望為 Apple News 和 Facebook Instant Articles 等平台開發新的包裝器,並為所有新平台提供開箱即用的我們內容的“核心”版本。

我們也希望在漸進增強方面做得更好; 在這個領域取得成功意味著在防守上有所發展。 你永遠不能假設現在和將來的所有平台都會支持給定的交互,但是一個設計良好的項目應該能夠在不遇到第一個技術障礙的情況下傳達其核心信息

在包裝器的範圍內工作是一種範式轉變,就長期解決方案而言,感覺有點像中途旅行。 但在行業成熟到跨平台標準之前,出版商將被迫推出自己的解決方案,或使用 Distro 等工具進行平台到平台的轉換,或者完全忽略他們的整個受眾群體。

這對我們來說還處於早期階段,但到目前為止,我們已經在使用包裝器模式構建我們的內容並將其交付到我們的受眾現在使用的無數平台方面取得了巨大成功。