NRK がスクロールドリブン アニメーションを使用してストーリーに命を吹き込む方法

公開日: 2026 年 2 月 26 日

スクロール ドリブン アニメーションは、ぎくしゃくしたメインスレッドの JavaScript 実装から、スクロール タイムラインやビュー タイムラインなどの最新の CSS と UI の機能を使用して、スムーズでアクセスしやすいメインスレッド外のエクスペリエンスへと進化しました。この移行により、迅速なプロトタイピングと高パフォーマンスのアニメーションが可能になり、この記事で説明するように、チームは洗練されたスクロール テリング ページを作成できるようになります。

NRK とストーリーテリング

NRK(ノルウェー放送協会)は、ノルウェーの公共放送局です。この記事で説明する実装の背後にあるチームは、ノルウェー語で Visuelle Historier と呼ばれ、英語では Visual Stories と訳されます。このチームは、テレビ、ラジオ、ウェブの編集プロジェクトのデザイン、グラフィック、開発に携わり、ビジュアル アイデンティティ、コンテンツ グラフィック、特集記事、新しいビジュアル ストーリーテリング形式を開発しています。また、NRK のデザイン プロファイルとサブブランドとも連携し、NRK のブランド ID に沿ってコンテンツを簡単に公開できるようにツールとテンプレートを作成しています。

NRK がスクロールドリブン アニメーションを使用する方法

スクロール ドリブン アニメーションとスクロール トリガー アニメーションは、記事のインタラクティビティ、エンゲージメント、記憶力を高め、ストーリーテリングを強化します。このアプローチは、画像がほとんどない、またはまったくないノンフィクションのナラティブで特に役立ちます。

これらのアニメーションは、ドラマの要点を強化または作成し、ストーリーを前進させ、テキストと一致または補強する小さな視覚的なナラティブを開発するのに役立ちます。スクロール ドリブンであるため、ユーザーはスクロールによってナラティブの進行を制御できます。

ユーザー エクスペリエンスを向上

NRK のユーザー分析によると、読者はこれらのアニメーションが視線を誘導する方法に満足しています。スクロールする際にテキストやアニメーションをハイライト表示すると、特にスキミングする際に、ユーザーが重要なポイントを特定し、ストーリーの最も重要な部分を理解しやすくなります。

また、グラフィックをアニメーション化することで複雑な情報を簡素化し、ユーザーが時間の経過に伴う関係や変化を理解しやすくなります。情報を動的に構築、追加、ハイライトすることで、NRK はより教育的で魅力的な方法でコンテンツを表示できます。

ムード作り

アニメーションは、ストーリーの雰囲気を設定したり、雰囲気を高めたりするための強力なツールです。アニメーションのタイミング、速度、スタイルを調整することで、NRK はナラティブのトーンに合った感情を呼び起こすことができます。

テキストを分割して視覚的な緩和効果を提供する

NRK では、長いテキスト ブロックをシンプルなディンクスや小さなイラストで分割するために、小さなアニメーション イラストを頻繁に使用しています。これにより、読者はナレーションから一時的に休憩できます。多くのユーザーは、テキストが分割され、読みやすくなることを評価しています。ナレーションの休憩として適切だと感じている。

ユーザー補助のニーズとユーザーの設定を尊重する

NRK の公開ページは、ノルウェーのすべての市民がアクセスできるものでなければなりません。そのため、ページは、モーションの低減に関するユーザーの設定を尊重する必要があります。このブラウザ設定を有効にしたユーザーは、ページのすべてのコンテンツを利用できる必要があります。

スクロールドリブン アニメーションの設計

NRK は、新しいスクロール アニメーション ツールを開発して Sanity コンテンツ管理システム(CMS)に直接統合することで、設計ワークフローを効率化しました。このツールは、サイトと CMS ソリューションを開発、保守するチームとのコラボレーションで開発されました。このツールを使用すると、デザイナーは、アニメーション要素の開始位置と終了位置を視覚的に示すキューを使用して、スクロール アニメーションのプロトタイプを簡単に作成し、実装できます。また、アニメーションをリアルタイムでプレビューすることもできます。この革新により、デザイナーはより細かく制御できるようになり、CMS 内で直接デザイン プロセスを加速できます。

ツールでスクロールして表示された領域を表示する。
アニメーション要素の開始位置と終了位置の視覚的な手がかりの類似例(実際の CMS ツールではありません)。

ブラウザでのスクロールドリブン アニメーション

ストーリー重視のアニメーション

誰にも惜しまれず消えた男

アパートで 9 年間遺体のままだった男性に関する記事では、他の視覚的要素がないため、イラストに大きく依存せざるを得ませんでした。イラストはスクロールによってアニメーション化され、物語を強調しています。たとえば、夜が来て、高層ビルの照明が徐々に点灯し、最後に点灯しないアパートが 1 つだけになるアニメーションなどです。このアニメーションは、NRK の社内スクロールドリブン アニメーション ツールを使用して作成されました。

テキストのフェード アニメーション

永久凍土

この記事は、映画のオープニング シーンを反映した簡単な導入から始まります。簡潔なテキストと全画面表示のビジュアルを組み合わせることで、記事のコンテンツをほのめかし、読者が記事全体を読み進めたくなるような期待感を高めています。タイトルページは映画ポスターに似せて作られており、スクロール ドリブン アニメーションが採用され、テキストを上下にスムーズにアニメーション化することで、映画のような感覚を強化しています。

.article-section {
  animation: fade-up linear;
  animation-timeline: view();
  animation-range: entry 100% exit 100%;
}

スクロール アニメーションのタイポグラフィ

記事のタイトルのアニメーション化されたタイポグラフィ - Sick leave(病気休暇)。

NRK は、「Sjukt sjuke」(「病気な病気」の意)という導入文で、ノルウェーでの病気休暇率の増加に関する記事に読者を引き込みました。このタイトルは、読者に、これは通常の退屈な数字重視のストーリーではないことを示唆する視覚的な目を引くものとして意図されています。NRK チームは、タイポグラフィとスクロール ドリブンのアニメーションを使用して、テキストとイラストを作品のテーマに沿ったものにしたいと考えていました。この記事では、NRK ニュースの新しいフォントとデザイン プロファイルが使用されています。

<h1 aria-label="sjuke">
  <span>s</span><span>j</span><span>u</span><span>k</span><span>e</span>
<h1>
h1 span {
  display: inline-block;
}
if (window.matchMedia('print, (prefers-reduced-motion: reduce)').matches) {
  return;
}

const heading = document.querySelector("h1");
const letters = heading.querySelectorAll("span");

const timeline = new ViewTimeline({ subject: heading });
const scales = [/**/];
const rotations = [/**/];

for ([index, el] of letters.entries()) {
  el.animate(
    {
      scale: ["1", scales[index]],
      rotate: ["0deg", rotations[index]]
    },
    {
      timeline,
      fill: "both",
      rangeStart: "contain 30%",
      rangeEnd: "contain 70%",
      easing: "ease-out"
    }
  );
}

スクロール スナップされたアイテムのハイライト表示

施設にいる子ども

記事の最後まで読んだ読者は、同じ問題についてさらに詳しく知りたいと思うことがよくあります。施設内で薬物乱用している若者に関する記事で、NRK は、次に読む記事として 1 つの記事をおすすめしたいと考えていました。また、読者が希望すれば、他のいくつかの記事も選択できるようにしたいと考えていました。解決策は、スクロール スナップとスクロール駆動アニメーションで実装されたスワイプ可能なナビゲーションでした。アニメーションにより、アクティブな要素がフォーカスされ、残りの要素は暗く表示されました。

for (let item of items) {
  const timeline = new ViewTimeline({ subject: item, axis: "inline" });
  const animation = new Animation(effect, timeline);
  item.animate(
    {
      opacity: [0.3, 1, 0.3]
    },
    { timeline, easing: "ease-in-out", fill: "both" }
  );
  animation.rangeStart = "cover calc(50% - 100px)";
  animation.rangeEnd = "cover calc(50% + 100px)";
}

スクロール アニメーションが通常のアニメーションをトリガーする

予算

ノルウェーの国家予算に関するこの記事で、NRK は、数字ベースの重厚で退屈なストーリーをよりアクセスしやすく、パーソナライズされたものにすることを目標としました。目標は、膨大で理解しがたい予算額を分類し、税金が何に使われているのかを個人レベルで把握できるようにすることでした。各サブセクションは、国家予算の特定の項目に焦点を当てています。読者の税金の合計は青い棒で示され、個々の項目に対する読者の貢献度を示すように分割されていました。スクロール ドリブンのアニメーションによって、個々のアイテムがアニメーション化されるようにトリガーされ、遷移が実現されました。

const timeline = new ViewTimeline({
  subject: containerElement
});

// Setup scroll-driven animation
const scrollAnimation = containerElement.animate(
  {
    "--cover-color": ["blue", "lightblue"],
    scale: ["1 0.2", "1 3"]
  },
  {
    timeline,
    easing: "cubic-bezier(1, 0, 0, 0)",
    rangeStart: "cover 0%",
    rangeEnd: "cover 50%"
  }
);

// Wait for scroll-driven animation to complete
await scrollAnimation.finished;
scrollAnimation.cancel();

// Trigger time-driven animations
for (let [index, postElement] of postElements.entries()) {
  const animation = postElement?.animate(
    { scale: ["1 3", "1 1"] },
    {
      duration: 200,
      delay: index * 33,
      easing: "ease-out",
      fill: "backwards"
    }
  );
}

「Google は長い間、スクロールドリブン アニメーションを作成してきました。Web Animations API が存在する前は、スクロール イベントを使用していましたが、後に Intersection Observer API と組み合わせました。これまでは非常に時間のかかるタスクでしたが、Web Animations API と Scroll-Driven Animations API によって簡単にできるようになりました」- Helge Silset、NRK フロントエンド デベロッパー

NRK には、ScrollAnimationDriver<scroll-animation-driver>)というカスタム要素にプラグインできるさまざまなウェブ コンポーネントがあり、次のアニメーションをサポートしています。

  • [KeyframeEffects](https://developer.mozilla.org/docs/Web/API/KeyframeEffect) を含むレイヤ
  • Lottie アニメーション
  • mp4
  • three.js
  • <canvas>

次の例では、KeyframeEffects でレイヤを使用しています。

<scroll-animation-driver data-range-start='entry-crossing 50%' data-range-end='exit-crossing 50%'>
  <layered-animation-effect>
    <picture>
      <source />
      <img />
    </picture>

    <picture>
      <source />
      <img />
    </picture>

    <picture>
      <source />
      <img />
    </picture>
  </layered-animation-effect>
</scroll-animation-driver>

NRK の <scroll-animation-driver> カスタム要素の JavaScript 実装:

export default class ScrollAnimationDriver extends HTMLElement {
  #timeline

  connectedCallback() {
    this.#timeline = new ViewTimeline({subject: this})
    for (const child of this.children) {
      for (const effect of child.effects ?? []) {
        this.#setupAnimationEffect(effect)
      }
    }
  }

  #setupAnimationEffect(effect) {
    const animation = new Animation(effect, this.#timeline) 
    animation.rangeStart = this.rangeStart
    animation.rangeEnd = this.rangeEnd

    if (this.prefersReducedMotion) {
      animation.currentTime = CSS.percent(this.defaultProgress * 100)
    } else {
      animation.play()
    }
  }
}

export default class LayeredAnimationEffect extends HTMLElement {
  get effects() {
    return this.layers.flatMap(layer => toKeyframeEffects(layer))
  }
}

スクロールのパフォーマンス

NRK は、スクロール ドリブン アニメーションを使用する前は、非常にパフォーマンスの高い JavaScript 実装を使用していましたが、スクロール ドリブン アニメーションを使用すると、低電力のデバイスでもスクロールのジャンクを心配することなく、さらに優れたパフォーマンスを実現できます。

  • SDA 以外のタスクの所要時間: 1 ミリ秒。
  • SDA タスクの所要時間: 0.16 ms。
Chrome DevTools の [パフォーマンス] タブ。
CPU が 6 倍に遅くなった場合、Chrome DevTools の [Performance] タブの記録では、新しいフレーム内のタスクごとに 0.16 ms と表示されます。

JavaScript 実装とスクロール ドリブン アニメーションのスクロール パフォーマンスの違いについて詳しくは、スクロール ドリブン アニメーションのパフォーマンスに関するケーススタディをご覧ください。

ユーザー補助と UX に関する考慮事項

NRK の公開ページは、さまざまな状況下でノルウェーのすべての市民がアクセスできる必要があるため、ユーザー補助が重要な役割を果たします。NRK では、スクロール アニメーションに複数の方法でアクセスできるようにしています。

  • モーションの抑制に関するユーザーの設定を尊重する: メディアクエリ screen and (prefers-reduced-motion: no-preference) を使用して、アニメーションを段階的な拡張機能として適用します。印刷スタイルを同時に処理する場合にも便利です。
  • 幅広いデバイスとスクロール入力の精度の違いを考慮する: 一部のユーザーは、ステップでスクロール(Space キーや上下キー、スクリーン リーダーを使用してランドマークに移動)し、アニメーション全体を表示しない場合があります。重要な情報が漏れないようにします。
  • コンテンツを表示または非表示にするアニメーションに注意する: オペレーティング システム(OS)のズームを使用しているユーザーは、スクロールすると非表示のコンテンツが表示されることを認識しにくい場合があります。ユーザーが検索しないようにします。コンテンツの非表示または表示が必要な場合は、表示と非表示の場所が一貫していることを確認します。
  • アニメーションの明るさやコントラストの急激な変化を避ける: スクロール ドリブンのアニメーションはユーザー操作に依存するため、明るさが急激に変化すると点滅のように見え、一部のユーザーで発作を引き起こす可能性があります。
@media (prefers-reduced-motion: no-preference) {
  .article-image {
    opacity: 0;
    transition: opacity 1s ease-in-out;
  }
  .article-image.visible {
    opacity: 1;
  }
}

ブラウザ サポート

ScrollTimelineViewTimeline のブラウザ サポートを拡大するため、NRK はオープンソースのポリフィルを使用しています。このポリフィルには、コミュニティが積極的に貢献しています

現在、ポリフィルは ScrollTimeline が使用できない場合に条件付きで読み込まれ、CSS サポートのないポリフィルの簡素化されたバージョンが使用されます。

if (!('ScrollTimeline' in window)) {
  await import('scroll-timeline.js')
}

CSS でのブラウザ サポートの検出と処理:

@supports not (animation-timeline: view()) {
  .article-section {
    translate: 0 calc(-15vh * var(--fallback-progress));
    opacity: var(--fallback-progress);
  }
}

@supports (animation-timeline: view()) {
  .article-section {
    animation: --fade-up linear;
    animation-timeline: view();
    animation-range: entry 100% exit 100%;
  }
}

サポートされていないブラウザの前の例では、NRK は translate プロパティと opacity プロパティのアニメーション タイムラインを制御するためのフォールバックとして、CSS 変数 --fallback-progress を使用しています。

次に、JavaScript の scroll イベント リスナーrequestAnimationFrame を使用して、--fallback-progress CSS 変数が更新されます。

function updateProgress() {
  const end = el.offsetTop + el.offsetHeight;
  const start = end - window.innerHeight;
  const scrollTop = document.scrollingElement.scrollTop;
  const progress = (scrollTop - start) / (end - start);
  document.body.style.setProperty('--fallback-progress', clamp(progress, 0, 1));
}


if (!CSS.supports("animation-timeline: view()")) {
  document.addEventListener('scroll', () => {
    if (!visible || updating) {
      return;
    }

    window.requestAnimationFrame(() => {
      updateProgress();
      updating = false;
    });

    updating = true;
  });
}

リソース

この作業に貴重な貢献をしてくれた Google の Hannah Van Opstal、Bramus、Andrew Kean Guan、NRK の Ingrid Reime に感謝します。