BBCインタラクティブコンテンツがAMP、アプリ、およびWeb全体でどのように機能するか
公開: 2022-03-10BBCのビジュアルジャーナリズムチームでは、電卓から視覚化の新しいストーリーテリング形式に至るまで、刺激的で魅力的でインタラクティブなコンテンツを制作しています。
各アプリケーションは、それ自体で作成するための固有の課題ですが、ほとんどのプロジェクトを多くの異なる言語で展開する必要があることを考えると、さらに重要です。 私たちのコンテンツは、BBC News and SportsのWebサイトだけでなく、iOSとAndroidの同等のアプリ、およびBBCコンテンツを使用するサードパーティのサイトでも機能する必要があります。
ここで、AMP、Facebook Instant Articles、AppleNewsなどの新しいプラットフォームが増えていることを考えてみてください。 各プラットフォームには、独自の制限と独自の公開メカニズムがあります。 これらすべての環境で機能するインタラクティブコンテンツを作成することは、実際の課題です。 BBCでこの問題にどのように取り組んだかを説明します。
例:CanonicalとAMP
これは、実際に動作するのを見るまでは少し理論的なものなので、例を直接掘り下げてみましょう。
ビジュアルジャーナリズムのコンテンツを含むBBCの記事は次のとおりです。
これは、記事の正規バージョン、つまり、ホームページから記事に移動した場合に取得するデフォルトバージョンです。
それでは、記事のAMPバージョンを見てみましょう。
正規バージョンとAMPバージョンは同じように見えますが、実際には、動作が異なる2つの異なるエンドポイントです。
- 正規バージョンでは、フォームを送信すると、選択した国にスクロールします。
- AMP iframe内から親ページをスクロールできないため、AMPバージョンではスクロールしません。
- AMPバージョンでは、ビューポートのサイズとスクロール位置に応じて、[もっと見る]ボタンが付いたトリミングされたiframeが表示されます。 これはAMPの機能です。
この記事の正規バージョンとAMPバージョンに加えて、このプロジェクトはニュースアプリにも出荷されました。ニュースアプリは、独自の複雑さと制限があるさらに別のプラットフォームです。 では、これらすべてのプラットフォームをどのようにサポートするのでしょうか。
ツーリングが重要
コンテンツを最初から作成することはありません。 Nodeを使用して単一のコマンドでボイラープレートプロジェクトを生成するYeomanベースのスキャフォールドがあります。
新しいプロジェクトには、Webpack、SASS、デプロイメント、およびコンポーネント化構造が付属しています。 ハンドルバーテンプレートシステムを使用して、国際化もプロジェクトに組み込まれています。 Tom Maslenは、レスポンシブWebデザインを多言語化するための13のヒントを、彼の投稿で詳しく説明しています。
箱から出して、これは1つのプラットフォーム用にコンパイルするのに非常にうまく機能しますが、複数のプラットフォームをサポートする必要があります。 いくつかのコードを掘り下げてみましょう。
埋め込みとスタンドアロン
ビジュアルジャーナリズムでは、コンテンツをiframe内に出力して、グローバルなスクリプトやスタイルの影響を受けずに、記事に自己完結型の「埋め込み」を行えるようにすることがあります。 この例は、この記事の前半の標準的な例に埋め込まれているドナルドトランプインタラクティブです。
一方、コンテンツを生のHTMLとして出力することもあります。 これを行うのは、ページ全体を制御できる場合、または本当に応答性の高いスクロール操作が必要な場合のみです。 これらをそれぞれ「埋め込み」出力と「スタンドアロン」出力と呼びましょう。
「ロボットがあなたの仕事を引き受けますか?」をどのように構築するか想像してみましょう。 「埋め込み」形式と「スタンドアロン」形式の両方でインタラクティブ。
コンテンツの両方のバージョンは、コードの大部分を共有しますが、2つのバージョン間で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オフセットが考慮されるように値を操作します。 この追加がなければ、ユーザーを完全に意図しない場所にスクロールしていたでしょう。
ラッパーパターン
ラッパーを使用すると、プロジェクト間でよりクリーンで読みやすく、一貫性のあるコードが得られます。 また、ラッパーを段階的に改善してメソッドのパフォーマンスとアクセス性を向上させるため、時間の経過とともにマイクロ最適化が可能になります。 したがって、プロジェクトは多くの開発者の経験から恩恵を受けることができます。
では、ラッパーはどのように見えますか?
ラッパー構造
各ラッパーは基本的に、Handlebarsテンプレート、ラッパーJSファイル、およびラッパー固有のスタイルを示すSASSファイルの3つで構成されます。 さらに、基礎となるスキャフォールディングによって公開されるイベントにフックするビルドタスクがあり、各ラッパーが独自の事前コンパイルとクリーンアップを担当します。
これは、埋め込みラッパーの簡略化されたビューです。
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
には、アプリケーションコードがそれ自体を定義する必要のないラッパー固有のスタイルが含まれています。 たとえば、埋め込みラッパーは、iframe内に多くのBBCニュースのスタイルを複製します。
最後に、 js/wrapper.js
/wrapper.jsにはラッパーAPIのiframed実装が含まれています。詳細は以下のとおりです。 これは、アプリケーションコードとともにコンパイルされるのではなく、プロジェクトに個別に出荷されます。Webpackビルドプロセスでwrapper
にグローバルのフラグを付けます。 つまり、アプリケーションを複数のプラットフォームに配信しますが、コードをコンパイルするのは1回だけです。
ラッパーAPI
ラッパーAPIは、いくつかの主要なブラウザーの相互作用を抽象化します。 最も重要なものは次のとおりです。
scrollTo(int)
アクティブなウィンドウの指定された位置までスクロールします。 ラッパーは、スクロールをトリガーする前に指定された整数を正規化して、ホストページが正しい位置にスクロールされるようにします。
getScrollPosition: int
ユーザーの現在の(正規化された)スクロール位置を返します。 iframeの場合、これは、iframeがビューポートの上部に来るまで、アプリケーションに渡されるスクロール位置が実際には負であることを意味します。 これは非常に便利で、コンポーネントが表示されたときにのみアニメーション化するなどの操作を実行できます。
onScroll(callback)
スクロールイベントへのフックを提供します。 スタンドアロンラッパーでは、これは基本的にネイティブスクロールイベントにフックします。 埋め込みラッパーでは、postMessageを介して渡されるため、スクロールイベントの受信にわずかな遅延が発生します。
viewport: {height: int, width: int}
ビューポートの高さと幅を取得する方法(これは、iframe内からクエリを実行すると非常に異なる方法で実装されるため)。
toggleFullScreen
スタンドアロンモードでは、BBCメニューとフッターを非表示にし、 position: fixed
れています。 ニュースアプリでは、何もしません—コンテンツはすでにフルスクリーンです。 複雑なのはiframeです。これは、postMessageを介して調整されたiframeの内側と外側の両方にスタイルを適用することに依存しています。
markPageAsLoaded
コンテンツが読み込まれたことをラッパーに通知します。 これは、コンテンツがニュースアプリで機能するために重要です。ニュースアプリは、コンテンツの準備ができていることをアプリに明示的に通知するまで、ユーザーにコンテンツを表示しようとしません。 また、コンテンツのWebバージョンの読み込みスピナーも削除されます。
ラッパーのリスト
将来的には、Facebook InstantArticlesやAppleNewsなどの大規模なプラットフォーム用に追加のラッパーを作成することを想定しています。 これまでに6つのラッパーを作成しました。
スタンドアロンラッパー
スタンドアロンページに配置する必要があるコンテンツのバージョン。 BBCブランドがバンドルされています。
埋め込みラッパー
コンテンツの管理を維持しているため、記事内に配置したり、BBC以外のサイトにシンジケートしたりするのに安全なコンテンツのiframedバージョン。
AMPラッパー
これは、 amp-iframe
としてAMPページに取り込まれるエンドポイントです。
ニュースアプリラッパー
私たちのコンテンツは、独自のbbcvisualjournalism://
プロトコルを呼び出す必要があります。
コアラッパー
HTMLのみが含まれています—プロジェクトのCSSまたはJavaScriptは含まれていません。
JSONラッパー
BBC製品間で共有するためのコンテンツのJSON表現。
プラットフォームまでの配線ラッパー
私たちのコンテンツを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
...等々
このソリューションは、必要に応じて、より多くのプラットフォームタイプを組み込むように拡張できます。
抽象化は難しい
「一度書けばどこにでもデプロイできる」アーキテクチャを構築することは非常に理想的であり、そうです。 ラッパーアーキテクチャが機能するためには、抽象化内での作業に非常に厳密である必要があります。 これは、「[ここにプラットフォーム名を挿入]で機能させるために、このハッキーなことをする」という誘惑と戦わなければならないことを意味します。 コンテンツが出荷される環境を完全に認識しないようにしたいのですが、これは口で言うほど簡単ではありません。
プラットフォームの機能を抽象的に構成するのは難しい
抽象化アプローチの前は、iframeのマークアップなど、出力のあらゆる側面を完全に制御していました。 アクセシビリティの理由でiframeにtitle
属性を追加するなど、プロジェクトごとに何かを微調整する必要がある場合は、マークアップを編集するだけで済みます。
ラッパーマークアップがプロジェクトから分離して存在するようになったので、ラッパーマークアップを構成する唯一の方法は、スキャフォールド自体のフックを公開することです。 クロスプラットフォーム機能の場合、これは比較的簡単に実行できますが、特定のプラットフォームのフックを公開すると、抽象化が崩れます。 1つのラッパーでのみ使用される「iframeタイトル」構成オプションを公開する必要はありません。
プロパティにもっと一般的な名前を付けることができます(例: title
)。次に、この値をiframeのtitle
属性として使用します。 ただし、何がどこで使用されているかを追跡することが難しくなり始め、構成を抽象化して理解できなくなるリスクがあります。 概して、グローバルに使用されるプロパティのみを設定して、構成を可能な限り無駄のないものに保つようにしています。
コンポーネントの動作は複雑になる可能性があります
ウェブ上では、sharetoolsモジュールは、個別にクリック可能なソーシャルネットワーク共有ボタンを吐き出し、新しいウィンドウで事前入力された共有メッセージを開きます。
ニュースアプリでは、モバイルウェブを介して共有したくありません。 ユーザーが関連するアプリケーション(Twitterなど)をインストールしている場合は、アプリ自体で共有します。 理想的には、ネイティブのiOS / Android共有メニューをユーザーに提示し、事前に入力された共有メッセージでアプリを開く前に、ユーザーに共有オプションを選択させます。 独自のbbcvisualjournalism://
プロトコルを呼び出すことで、アプリからネイティブ共有メニューをトリガーできます。
ただし、この画面は、[結果の共有]セクションで[Twitter]または[Facebook]のどちらをタップしてもトリガーされるため、ユーザーは2回選択する必要があります。 1回目はコンテンツ内で、2回目はネイティブポップアップで。
これは奇妙なユーザージャーニーなので、ニュースアプリから個々の共有アイコンを削除し、代わりに一般的な共有ボタンを表示したいと思います。 コンポーネントをレンダリングする前に、どのラッパーが使用されているかを明示的にチェックすることで、これを行うことができます。
ラッパー抽象化レイヤーの構築はプロジェクト全体でうまく機能しますが、ラッパーの選択がコンポーネントレベルでの変更に影響を与える場合、クリーンな抽象化を維持することは非常に困難です。 この場合、少し抽象化が失われ、コードに厄介なフォークロジックが含まれています。 ありがたいことに、これらのケースはごくわずかであり、その間にあります。
欠落している機能をどのように処理しますか?
抽象化を維持することはすべてうまくいっています。 私たちのコードは、「フルスクリーンにする」など、プラットフォームに何をさせたいかをラッパーに伝えます。 しかし、出荷先のプラットフォームが実際にフルスクリーンにならない場合はどうなるでしょうか。
ラッパーは完全に壊れないように最善を尽くしますが、最終的には、メソッドが成功するかどうかに関係なく、正常に機能するソリューションにフォールバックする設計が必要です。 防御的に設計する必要があります。
いくつかの棒グラフを含む結果セクションがあるとしましょう。 グラフがスクロールされて表示されるまで、棒グラフの値をゼロに保つことがよくあります。その時点で、棒が正しい幅にアニメーション化されます。
ただし、AMPラッパーの場合のように、スクロール位置にフックするメカニズムがない場合、バーは永久にゼロのままになります。これは、完全に誤解を招く経験です。
私たちはますます、私たちの設計にプログレッシブエンハンスメントアプローチを採用しようとしています。 たとえば、デフォルトですべてのプラットフォームで表示されるボタンを提供できますが、ラッパーがスクロールをサポートしている場合は非表示になります。 そうすれば、スクロールがアニメーションのトリガーに失敗した場合でも、ユーザーは手動でアニメーションをトリガーできます。
将来の計画
AppleNewsやFacebookInstant Articlesなどのプラットフォーム用の新しいラッパーを開発し、すべての新しいプラットフォームにコンテンツの「コア」バージョンをすぐに提供できるようにしたいと考えています。
また、プログレッシブエンハンスメントが向上することを望んでいます。 この分野で成功するということは、防御的に発展することを意味します。 現在および将来、すべてのプラットフォームが特定のインタラクションをサポートするとは限りませんが、適切に設計されたプロジェクトは、最初の技術的なハードルに陥ることなく、コアメッセージを伝えることができるはずです。
ラッパーの範囲内で作業することは、パラダイムシフトのビットであり、長期的な解決策の観点からは、中途半端な家のように感じます。 しかし、業界がクロスプラットフォーム標準に成熟するまで、パブリッシャーは独自のソリューションを展開するか、プラットフォームからプラットフォームへの変換にDistroなどのツールを使用するか、オーディエンスのセクション全体を完全に無視することを余儀なくされます。
まだ初期の段階ですが、これまでのところ、ラッパーパターンを使用してコンテンツを一度作成し、視聴者が現在使用している無数のプラットフォームに配信することに大きな成功を収めています。