Skip to content
cccskills
FreePublished on GitHub

scrolly-sveltekit

SvelteKit 5 と Svelte 5 の runes を使って、スクロール連動型のビジュアル記事(スクロリーテリング)を構築する。画像スクロリー、動画スクロリー、地図スクロリー、文書モザイクの各パターン用コンポーネントと、貼り付き(sticky)背景+スクロール前景の設計、CSS の落とし穴、ステップ設計、フェード演出までを扱う。

「スクロリーテリングの記事を作りたい」「スクロールで地図や写真が切り替わる特集を組みたい」などの開発依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3 stars(GitHub 3 + cccskills 0)

Run score Not tested

See how to install

Files included (8)

  • SKILL.md29.7 KB
  • references/HeroVisualization.svelte1.1 KB
  • references/MapScrolly.svelte9.0 KB
  • references/NoticeMosaic.svelte5.4 KB
  • references/ScrollyHelper.svelte2.7 KB
  • references/ScrollySection.svelte7.0 KB
  • references/ScrollyTextBox.svelte4.9 KB
  • references/VideoScrollyVisualization.svelte2.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

SvelteKit でのスクロリーテリング

スクロールに連動して背景のビジュアル(写真・動画・地図・グラフ)が切り替わる物語型記事を、SvelteKit 5 と Svelte 5 の runes で構築するための包括的な手引き。

対象読者の注意:このスキルは技術者・データ可視化担当向けです。SvelteKit/Svelte、TypeScript、CSS の実装知識を前提とします。コードを書かない記者・編集者には難度が高いため、企画段階(どの場面で何を見せ、どのテキストを重ねるか)を記者が設計し、実装はフロントエンド担当と分担することを想定しています。記者が単独で完成させるためのツールではありません。

注意:本スキルは一般的な実装手引きであり、特定プロジェクトの動作保証ではありません。コンポーネントの実体は references/ 配下にあります(後述)。フレームワークや依存ライブラリのバージョンは更新が早いため、導入前に各ライブラリの最新ドキュメントを確認してください。

クイックスタート

<script>
  import ScrollySection from '$lib/components/scrolly/ScrollySection.svelte';
  import MyVisualization from './MyVisualization.svelte';

  let activeStep = $state(0);

  const steps = [
    { text: '1つ目の語り(ナラティブ)' },
    { text: '2つ目の語り' },
    { text: '3つ目の語り' }
  ];
</script>

<ScrollySection bind:activeStep={activeStep} {steps}>
  {#snippet children({ activeStep })}
    <MyVisualization {activeStep} />
  {/snippet}
</ScrollySection>

中核となる設計

スクロリーのパターン

スクロリーテリングは、貼り付き(sticky)背景+スクロールする前景というパターンで成り立つ。

┌─────────────────────────────────┐
│     貼り付くビジュアル層         │  ← position: sticky, z-index: 1
│   (画像/動画/地図/グラフ)   │     テキストがスクロールしても固定される
│                                 │
│   ┌─────────────────────────┐   │
│   │   テキストボックス(②)  │   │  ← テキスト列がビジュアルに重なる
│   └─────────────────────────┘   │     z-index: 2
│                                 │
└─────────────────────────────────┘

要点:ビジュアル層を position: sticky で固定し、その上をテキスト列が通り過ぎていくことで、「スクロールするとビジュアルが切り替わる」という錯覚を作る。


コンポーネント・リファレンス

以下で示すコンポーネントの実体(.svelte 7本)は references/ ディレクトリに収録してある。新規プロジェクトでは、これらを $lib/components/scrolly/(または $lib/components/cleared/)にコピーして使う。本文では原典に合わせて $lib/... のパスで記載するが、参照元の実装は references/<コンポーネント名>.svelte を見ること。

ScrollySection

貼り付き/スクロールの関係を管理する中心的なコンテナ。

実装:references/ScrollySection.svelte(配置先の例:$lib/components/scrolly/ScrollySection.svelte)

Props

Prop型既定値説明
activeStepnumber0バインド可。現在のステップ番号
stepsStep[]必須各ステップの内容の配列
backgroundColorstring'#0a0a0a'背景色
showTextBoxesbooleantrueステップごとのテキストボックスを表示するか
textBoxVariant'light' | 'dark''light'テキストボックスの配色
textBoxPosition'center' | 'left' | 'right''center'テキストボックスの横位置
firstStepOffsetnumber0最初のステップを下へ押し下げるビューポート比率
onStepEnter(step, direction) => void-ステップが変わったときのコールバック
onScrollProgress(progress) => void-スクロール進捗(0〜1)を返すコールバック
childrenSnippet<[{ activeStep }]>-ビジュアル表示用のスニペット

Step インターフェース

interface Step {
  text?: string;           // テキストボックスに入れる HTML
  title?: string;          // 任意のタイトル
  image?: string;          // テキストボックスに表示する画像 URL
  bgColor?: string;        // 個別の背景色
  raw?: boolean;           // true ならラッパーなしで text をそのまま描画
  source?: { text: string; url: string };  // 出典表記
  imageCredit?: string;    // 写真クレジット
}

使用例

<ScrollySection
  bind:activeStep={heroStep}
  steps={heroSteps}
  backgroundColor="#000000"
  showTextBoxes={true}
  textBoxVariant="light"
  onScrollProgress={(p) => (scrollProgress = p)}
>
  {#snippet children({ activeStep })}
    <HeroVisualization currentImage={heroImages[activeStep]} />
  {/snippet}
</ScrollySection>

ScrollyHelper

IntersectionObserver を使ったステップ追跡コンポーネント。どのステップが最も見えているかを判定する。

実装:references/ScrollyHelper.svelte(配置先の例:$lib/components/scrolly/ScrollyHelper.svelte)

仕組み

  1. 子要素(各ステップ)を包む
  2. 子要素ごとに IntersectionObserver を作る
  3. 各ステップの交差比率(intersection ratio)を追跡する
  4. 最も見えているステップの番号を value バインディングとして公開する
  5. どのステップも見えていないときは undefined を返す

Props

Prop型既定値説明
valuenumber | undefined-バインド可。現在のステップ番号
rootElement | nullnull交差判定の基準(root)
topnumber0上マージン(ピクセル)
bottomnumber0下マージン(ピクセル)
incrementsnumber100しきい値の粒度

使い方

<script>
  let currentStep = $state(undefined);
</script>

<ScrollyHelper bind:value={currentStep} top={200} bottom={200}>
  <div class="step">ステップ0の内容</div>
  <div class="step">ステップ1の内容</div>
  <div class="step">ステップ2の内容</div>
</ScrollyHelper>

ScrollyTextBox

語り(ナラティブ)の本文を表示するためのスタイル付きテキストボックス。

実装:references/ScrollyTextBox.svelte(配置先の例:$lib/components/scrolly/ScrollyTextBox.svelte)

Props

Prop型既定値説明
titlestring''ボックスのタイトル
imagestring | nullnull画像 URL
bgColorstring | nullnull個別の背景色
activebooleantrueアクティブ状態か
variant'light' | 'dark''light'配色
maxWidth'narrow' | 'wide''narrow'ボックスの幅
source{ text, url } | nullnull出典表記
imageCreditstring | nullnull写真クレジット
childrenSnippet-内容スロット

スタイル

  • light(明):白背景+ぼかし(blur)
  • dark(暗):ほぼ黒の背景+ぼかし
  • アクティブ時:不透明度100%、translateY(0)
  • 非アクティブ時:不透明度75%、translateY(8px)
/* アクティブへの遷移 */
.scrolly-text-box {
  opacity: 0.75;
  transform: translateY(8px);
  transition: all 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.scrolly-text-box.active {
  opacity: 1;
  transform: translateY(0);
}

ビジュアル表示コンポーネント

画像スクロリー(HeroVisualization)

フェード効果つきのシンプルな画像切り替え。

実装:references/HeroVisualization.svelte(配置先の例:$lib/components/cleared/HeroVisualization.svelte)

<script lang="ts">
  interface Props {
    currentImage: string;
    fadeProgress?: number; // 0〜1
  }

  let { currentImage, fadeProgress = 0 }: Props = $props();
</script>

<div class="hero-visualization">
  <img src={currentImage} alt="場面" class="hero-image" />
  <div class="hero-overlay"></div>
  {#if fadeProgress > 0}
    <div class="fade-overlay" style:opacity={fadeProgress}></div>
  {/if}
</div>

<style>
  .hero-visualization {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    background: #000;
  }

  .hero-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .hero-overlay {
    position: absolute;
    inset: 0;
    background: linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.5) 0%,
      rgba(0, 0, 0, 0.3) 50%,
      rgba(0, 0, 0, 0.5) 100%
    );
  }

  .fade-overlay {
    position: absolute;
    inset: 0;
    background: #0a0a0a;
    pointer-events: none;
  }
</style>

親コンポーネントでの使い方

<script>
  let heroStep = $state(0);
  let heroScrollProgress = $state(0);

  const heroImages = [
    '/images/scene1.jpg',
    '/images/scene2.jpg',
    '/images/scene3.jpg'
  ];

  let currentHeroImage = $derived(heroImages[heroStep] ?? heroImages[0]);

  // 最後のステップで黒へフェード
  let heroFadeProgress = $derived(() => {
    if (heroStep !== 2) return 0;
    const fadeStart = 0.75;
    if (heroScrollProgress < fadeStart) return 0;
    return Math.min(1, (heroScrollProgress - fadeStart) / (1 - fadeStart));
  });
</script>

<ScrollySection
  bind:activeStep={heroStep}
  steps={heroSteps}
  onScrollProgress={(p) => (heroScrollProgress = p)}
>
  {#snippet children({ activeStep })}
    <HeroVisualization
      currentImage={currentHeroImage}
      fadeProgress={heroFadeProgress()}
    />
  {/snippet}
</ScrollySection>

動画スクロリー(VideoScrollyVisualization)

ステップごとに切り替える複数動画の表示。

実装:references/VideoScrollyVisualization.svelte(配置先の例:$lib/components/scrolly/VideoScrollyVisualization.svelte)

<script lang="ts">
  interface VideoStep {
    videoSrc: string;
    poster?: string;
  }

  interface Props {
    activeStep: number;
    videoSteps: VideoStep[];
  }

  let { activeStep, videoSteps }: Props = $props();

  let videoRefs: HTMLVideoElement[] = $state([]);

  $effect(() => {
    // すべて一時停止し、アクティブな動画だけ再生する
    videoRefs.forEach((video, i) => {
      if (video) {
        if (i === activeStep) {
          video.play().catch(() => {});
        } else {
          video.pause();
          video.currentTime = 0;
        }
      }
    });
  });
</script>

<div class="video-scrolly-viz">
  <div class="video-frame">
    {#each videoSteps as step, i}
      <video
        src={step.videoSrc}
        poster={step.poster}
        bind:this={videoRefs[i]}
        class="video-layer"
        class:active={i === activeStep}
        muted
        loop
        playsinline
        preload="metadata"
      />
    {/each}
  </div>
  <div class="video-vignette"></div>
</div>

<style>
  .video-scrolly-viz {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    background: #000;
  }

  .video-frame {
    position: absolute;
    inset: 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .video-layer {
    position: absolute;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    transition: opacity 500ms ease;
    pointer-events: none;
  }

  .video-layer.active {
    opacity: 1;
  }

  .video-vignette {
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.6) 100%);
    pointer-events: none;
  }
</style>

自動再生の注意:多くのブラウザは音声付き動画の自動再生をブロックする。スクロール連動で自動再生するには muted(消音)と playsinline(インライン再生)を必ず付ける。play() は Promise を返し、ブロック時に reject されるため .catch(() => {}) で握りつぶしている。

親コンポーネントでの使い方

<script>
  let videoStep = $state(0);

  const videoData = [
    { videoSrc: '/videos/clip1.mp4' },
    { videoSrc: '/videos/clip2.mp4' },
    { videoSrc: '/videos/clip3.mp4' }
  ];

  const videoSteps = [
    { raw: true, text: '<div class="quote-card">引用1</div>' },
    { raw: true, text: '<div class="quote-card">引用2</div>' },
    { raw: true, text: '<div class="quote-card">引用3</div>' }
  ];
</script>

<ScrollySection
  bind:activeStep={videoStep}
  steps={videoSteps}
  backgroundColor="#000000"
  showTextBoxes={true}
>
  {#snippet children({ activeStep })}
    <VideoScrollyVisualization {activeStep} videoSteps={videoData} />
  {/snippet}
</ScrollySection>

地図スクロリー(MapScrolly)

ステップ駆動でカメラ移動とレイヤー表示を切り替えるインタラクティブ地図。

実装:references/MapScrolly.svelte(配置先の例:$lib/components/cleared/MapScrolly.svelte)

主な機能

  • MapTiler/Mapbox GL の連携
  • ステップ間の fly-to(飛行)アニメーション
  • GeoJSON レイヤーの管理
  • フェードイン/フェードアウトのオーバーレイ

地図タイルの注意:MapTiler/Mapbox はいずれも API キーが必要で、利用規約・課金・帰属表示(attribution)の条件がある。日本の地図を扱う場合は、国土地理院の地理院タイルなど国内の地図ソースも選択肢になる(利用規約・出典表記の要件を必ず確認)。背景地図の出典クレジットは記事面に明記すること。

Props

Prop型既定値説明
activeStepnumber必須現在のステップ番号
fadeProgressnumber0フェードインのオーバーレイ(1=黒、0=見える)
fadeOutProgressnumber0終端でのフェードアウト

ステップのデータ構造

{
  "steps": [
    {
      "coordinates": [139.6917, 35.6895],
      "zoom": 7,
      "pitch": 0,
      "bearing": 0,
      "duration": 2000,
      "layers": {
        "evictions": { "opacity": 0.6 },
        "villages": { "visible": true, "opacity": 1.0 }
      }
    }
  ]
}

coordinates は [経度, 緯度] の順であることに注意(GeoJSON/Mapbox の慣例。緯度・経度の順ではない)。上の例は東京周辺の座標。

fly-to の実装

function flyToStep(index: number) {
  if (!map || !mapReady) return;

  const step = steps[index];
  if (!step) return;

  map.flyTo({
    center: step.coordinates,
    zoom: step.zoom,
    bearing: step.bearing || 0,
    pitch: step.pitch || 0,
    duration: step.duration || 2000,
    easing: (t: number) => t * (2 - t)  // ease-out quad(終わりが滑らかな減速)
  });

  updateLayerVisibility(index);
}

文書モザイク(NoticeMosaic)

書類を散らして配置し、段階的に見せていく表示。立ち退き通知や告示・公文書などを「証拠の山」として演出する用途に向く。

実装:references/NoticeMosaic.svelte(配置先の例:$lib/components/scrolly/NoticeMosaic.svelte)

<script lang="ts">
  interface Notice {
    image: string;
    alt: string;
    title: string;
    subtitle: string;
    excerpt: string;
  }

  interface Props {
    activeStep: number;
    notices: Notice[];
    backgroundColor?: string;
  }

  let { activeStep, notices, backgroundColor = '#1a1715' }: Props = $props();

  let currentNoticeIndex = $derived(Math.min(activeStep, notices.length - 1));

  // ばらつき(jitter)を加えた決定論的な配置
  function getPosition(index: number): [string, string, number] {
    const positions: [number, number, number][] = [
      [20, 15, -3],
      [50, 25, 2.5],
      [15, 45, -1.5],
      [55, 10, 3]
    ];

    const pos = positions[index % positions.length];
    const seed = index * 7919;
    const jitterX = ((seed % 10) - 5) * 0.5;
    const jitterY = (((seed * 3) % 10) - 5) * 0.5;

    return [`${pos[0] + jitterX}%`, `${pos[1] + jitterY}%`, pos[2]];
  }
</script>

配置を Math.random() ではなく index から導く「決定論的(deterministic)」な計算にしているのは、再描画やサーバーサイドレンダリング(SSR)でも位置がぶれないようにするため。乱数だと毎回レイアウトが変わり、ハイドレーションのずれや見た目のちらつきの原因になる。


重要な CSS ルール

オーバーレイに position: fixed を使わない

問題:fixed のオーバーレイはビューポート全体(その下にある別セクションも含む)を覆ってしまう。

/* 悪い例 — ページ全体が暗くなる */
.fade-overlay {
  position: fixed;
  inset: 0;
  background: #0a0a0a;
  z-index: 5;
}

/* 良い例 — 親コンテナの内側に限定される */
.fade-overlay {
  position: absolute;
  inset: 0;
  background: #0a0a0a;
  pointer-events: none;
}

z-index の指針

z-index は単純な値に保ち、スコープを限定する。

層z-index説明
ビジュアル層(sticky)1背景のビジュアル
テキスト列2スクロールする本文
フェードのオーバーレイ10遷移演出(セクション内部)
フッター10フッターを確実に見せる

ScrollySection に隣り合うセクションに大きな z-index を付けないこと。

<!-- 悪い例 — スクロリーの上に重なってしまう -->
<section class="intro relative z-10">

<!-- 良い例 — 自然な重なり順 -->
<section class="intro relative">

貼り付き(sticky)配置の要件

position: sticky を効かせるには次が必要。

  1. overflow: hidden ではなく overflow-x: clip を使う:

    :global(html), :global(body) {
      overflow-x: clip;  /* sticky を壊さない */
    }
    
  2. スクロリーのコンテナに isolation を付ける:

    .scroll-section {
      position: relative;
      isolation: isolate;
    }
    
  3. ビジュアル層の構造:

    .visual-layer {
      position: sticky;
      top: 0;
      width: 100%;
      height: 100vh;
      z-index: 1;
    }
    

スクロリー以外のコンテンツセクション

スクロールロックを伴わない、通常の記事本文の作り方。

コンテンツセクションの構造

<section class="content-section">
  <div class="content-container">
    <h2 class="content-heading">セクション見出し</h2>
    <div class="prose-content">
      <p>記事本文…</p>
    </div>
  </div>
</section>

CSS

/* 暗いテーマのコンテンツセクション */
.content-section {
  position: relative;
  background: #0a0a0a;
  padding: 5rem 1.5rem;
}

.content-container {
  max-width: 42rem;
  margin: 0 auto;
}

.content-heading {
  font-family: 'Playfair Display', Georgia, serif;
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  font-weight: 600;
  line-height: 1.2;
  color: #ffffff;
  margin: 0 0 2rem 0;
}

.prose-content {
  font-family: 'Source Sans 3', system-ui, sans-serif;
  font-size: 1.125rem;
  line-height: 1.85;
  color: rgba(255, 255, 255, 0.85);
}

.prose-content p {
  margin: 0 0 1.5rem 0;
}

.prose-content strong {
  font-weight: 600;
  color: #ffffff;
}

.prose-content blockquote {
  border-left: 4px solid rgba(255, 255, 255, 0.2);
  padding-left: 1.5rem;
  margin: 2rem 0;
  font-style: italic;
  color: rgba(255, 255, 255, 0.6);
}

日本語フォントの注意:上の Playfair Display/Source Sans 3 は欧文向けのフォント。日本語本文では Noto Sans JP・Noto Serif JP・游ゴシック/游明朝などの和文フォントを併せて指定する。明朝体(serif)は格調・重厚さ、ゴシック体(sans-serif)は可読性に向く。本文の行間(line-height)は和文では 1.8 前後がやや広めで読みやすい。縦書きを使う場合は writing-mode: vertical-rl を検討する。

年表(タイムライン)

.timeline-events {
  margin: 2rem 0;
}

.event {
  padding-left: 1.5rem;
  border-left: 4px solid #dc2626;
  margin-bottom: 1.5rem;
}

.event-date {
  font-weight: 600;
  color: #ffffff;
  margin: 0 0 0.25rem 0;
}

ステップのデータ設計

標準のテキストステップ

const steps = [
  {
    title: '2021年9月23日',
    text: '出来事の説明。<strong>強調</strong>もできる。',
    source: { text: '共同通信', url: 'https://...' },
    imageCredit: '撮影者名'
  }
];

生 HTML のステップ(カスタムカード)

const steps = [
  {
    raw: true,
    text: `
      <div class="custom-card">
        <span class="date">2025年8月4日</span>
        <blockquote>「ここに引用文」</blockquote>
        <a href="..." class="source-link">出典を見る</a>
      </div>
    `
  }
];

ヒーロー見出し(最初のステップ)

const heroSteps = [
  {
    raw: true,
    text: `
      <div class="hero-header">
        <h1 class="hero-title">記事タイトル</h1>
        <p class="hero-desc">サブタイトルの説明</p>
        <div class="hero-byline">
          <p class="byline-label">取材・執筆</p>
          <p class="byline-authors">執筆者名</p>
          <p class="byline-date">2026年1月</p>
        </div>
      </div>
    `
  },
  // …以降のステップが続く
];

フェード演出

セクション末尾で黒へフェード

let scrollProgress = $state(0);

// フェード計算:スクロール75%地点で開始し、100%で完了
let fadeProgress = $derived(() => {
  const fadeStart = 0.75;
  if (scrollProgress < fadeStart) return 0;
  return Math.min(1, (scrollProgress - fadeStart) / (1 - fadeStart));
});

セクション冒頭でフェードイン

// 最初の25%で、黒(1)から見える状態(0)へ
let fadeInProgress = $derived(() => {
  const fadeEnd = 0.25;
  if (scrollProgress >= fadeEnd) return 0;
  return 1 - (scrollProgress / fadeEnd);
});

フェードのオーバーレイを当てる

{#if fadeProgress > 0}
  <div class="fade-overlay" style:opacity={fadeProgress}></div>
{/if}

<style>
  .fade-overlay {
    position: absolute;
    inset: 0;
    background: #0a0a0a;
    pointer-events: none;
    z-index: 10;
  }
</style>

ページ全体の構成

<div class="article-container">
  <!-- ヒーロー(画像)スクロリー -->
  <ScrollySection bind:activeStep={heroStep} steps={heroSteps}>
    {#snippet children({ activeStep })}
      <HeroVisualization ... />
    {/snippet}
  </ScrollySection>

  <!-- データ可視化スクロリー -->
  <ScrollySection bind:activeStep={dataStep} steps={dataSteps}>
    {#snippet children({ activeStep })}
      <DataVisualization ... />
    {/snippet}
  </ScrollySection>

  <!-- 通常のコンテンツセクション -->
  <section class="content-section">
    <div class="content-container">
      <h2>セクション見出し</h2>
      <div class="prose-content">…</div>
    </div>
  </section>

  <!-- 地図スクロリー -->
  <ScrollySection bind:activeStep={mapStep} steps={mapSteps}>
    {#snippet children({ activeStep })}
      <MapScrolly ... />
    {/snippet}
  </ScrollySection>

  <!-- フッター -->
  <footer class="article-footer">…</footer>
</div>

<style>
  .article-container {
    background: #0a0a0a;
    min-height: 100vh;
  }

  :global(html), :global(body) {
    overflow-x: clip;
  }
</style>

トラブルシューティング

動画スクロリーが表示されない

  1. 周囲のセクションの z-index を確認する(z-10 を外す)
  2. ビューポートを覆う position: fixed のオーバーレイがないか確認する
  3. 指定したパスに動画ファイルが実在するか確認する

暗いオーバーレイがページ全体を覆う

原因:フェードのオーバーレイが absolute ではなく fixed になっている。

対処:

.fade-overlay {
  position: absolute;  /* fixed にしない! */
  inset: 0;
}

sticky が効かない

  1. 祖先要素に overflow: hidden がないか確認する(overflow-x: clip を使う)
  2. position: sticky に top: 0 があるか確認する
  3. コンテナに高さ、または十分な内容があるか確認する

ステップが切り替わらない

  1. ステップ要素に十分な高さがあるか確認する(min-height: 70vh など)
  2. IntersectionObserver のマージン設定を確認する
  3. ステップが ScrollyHelper の直接の子になっているか確認する

タイポグラフィの指針

/* 見出し(欧文) */
font-family: 'Playfair Display', Georgia, serif;

/* 本文(欧文) */
font-family: 'Source Sans 3', system-ui, sans-serif;

/* 等幅・日付 */
font-family: 'JetBrains Mono', 'Courier New', monospace;

日本語記事では和文フォントを併記する(例):

/* 見出し(和文・明朝) */
font-family: 'Playfair Display', 'Noto Serif JP', 'Yu Mincho', Georgia, serif;

/* 本文(和文・ゴシック) */
font-family: 'Source Sans 3', 'Noto Sans JP', 'Yu Gothic', system-ui, sans-serif;

フォントの読み込み

<svelte:head>
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
  <link
    href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Playfair+Display:wght@400;500;600;700&family=Source+Sans+3:wght@300;400;500;600&display=swap"
    rel="stylesheet"
  />
  <!-- 和文フォントを使う場合は Noto Sans JP / Noto Serif JP も併せて読み込む。
       和文は容量が大きいため、必要なウェイトだけに絞ると表示が速くなる -->
</svelte:head>

依存ライブラリ

{
  "dependencies": {
    "svelte": "^5.0.0",
    "@sveltejs/kit": "^2.0.0"
  }
}

特定のビジュアルで任意に追加:

  • 地図:mapbox-gl または @maptiler/sdk
  • データ可視化:d3
  • 複雑なアニメーション:gsap

アクセシビリティ・公開前の確認

スクロリーテリングは演出が強いぶん、読者によっては使いにくくなりやすい。公開前に次を確認する。

  • キーボード・スクリーンリーダー:スクロール連動の情報が、本文テキストとしても順に読めること。ビジュアルの切り替えだけに意味を持たせず、テキストで内容が完結するようにする。
  • モーション設定の尊重:prefers-reduced-motion を尊重し、fly-to や激しいフェードを抑える分岐を入れる。
  • モバイル:縦長画面・低速回線での挙動を確認する。動画・地図タイルは通信量が大きいので、preload の設定や画像の最適化を検討する。
  • 代替テキスト:画像の alt、地図・グラフの説明を必ず付ける。
  • 出典・クレジット:写真・動画・地図・データの出典を記事面に明示する(source・imageCredit を活用)。地図タイルの帰属表示は規約上の義務であることが多い。

出典・ライセンス(帰属表示)

本スキルは AI Journalism Skills(by Florent "Flo" Daudens, CC BY 4.0, https://huggingface.co/spaces/fdaudens/ai-journalism-skills )の scrolly-sveltekit スキルを 原典の構成(設計思想・コンポーネントリファレンス・CSS の落とし穴・ステップ設計・フェード演出・トラブルシューティング・付属の .svelte コンポーネント)に沿って日本語化し、 地図ソース・フォント・出典表記など一部を日本の報道実務(地理院タイル、和文フォント、出典クレジット、アクセシビリティ)向けに補足・再構成したものです。 コンポーネントの実体(.svelte 7本)は references/ に収録しています。コードは機能を保持し、コメントのみ日本語化しています。 原典の著作物本文(第三者の素材)は転載していません。改変を加えており、原作者の公認・推奨を受けたものではありません。 ライセンス(CC BY 4.0)の全文は https://creativecommons.org/licenses/by/4.0/ を参照。

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

AIが生成しがちな「いかにもAIっぽい」日本語の定型表現・水増し・空疎な形容を取り除き、読者の信頼を損なわない自然な文章に直す。記事・原稿・プレスリリース・告知・SNS投稿など、AIの支援を受けて書いた文章を公開前に点検するときに使う。「AI臭を消したい」「文章が機械っぽい」「人が書いたように直したい」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

取材・調査・データ報道で使える無料データ源(API)のカタログ。政府統計(e-Stat)、地域経済(RESAS)、気象(気象庁)、法令(e-Gov)、企業開示(EDINET)、書誌・国会会議録(国立国会図書館)、地理空間(国土地理院)、汎用の国際API(GDELT・Open-Meteo・Wayback等)を用途別に整理し、登録要否・利用条件・選び方を示す。「無料のデータ源が欲しい」「このテーマのAPIは?」「公的データをプログラムで取りたい」といった依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

データ報道(データジャーナリズム)の手順を支援する。データセットの分析、グラフや地図の作成、雑然としたデータのクリーニング、統計の算出、データに基づく記事の構築に使う。「e-Statのデータを分析したい」「人口あたりで比較したい」「グラフが誤解を招かないか確認したい」「統計から記事を組み立てたい」などの依頼で使う。記者・編集者・調査担当向け。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

取材で集めた資料(開示文書・記事・SNS・反訳など)をデジタルアーカイブ化し、AIで要約・分類・エンティティ抽出・関係づけ(知識グラフ化)して横断検索できる調査基盤を作るための手引き。「取材資料を整理したい」「大量の開示文書をデータベース化したい」「人物・組織・出来事の関係を可視化したい」「調査報道のアーカイブを作りたい」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

発言・記事・SNS投稿などの主張を体系的にファクトチェックする。主張の抽出、検証可能な命題への分解、根拠の収集、当事者への確認、判定(レーティング)、記録、訂正対応までを支援する。掲載前の裏取り、ファクトチェック記事の作成、編集部の検証手順づくりに使う。「ファクトチェックして」「この発言は本当か」「裏取りの手順を」などの依頼で使う。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

テキスト・記事・ウェブページの内容を、厳密な検証方法論にもとづいてファクトチェックする。検証可能な主張を抽出し、信頼できる一次情報・公的データ・検証機関と突き合わせ、各主張の真偽と確からしさ(確信度)、情報源の信頼度スコアを評価し、根拠つきの検証レポートにまとめる。「この主張を検証して」「記事をファクトチェックして」「情報源の信頼性を見て」「真偽を判定して」「誤情報か確かめて」などの依頼で使う。付属の references(信頼できる情報源/検証方法論)を参照する検証重視版。

The English summary is being prepared. Showing the original description.

sgbms1/ja-news-skills3Updated Jul 14, 2026

See all skills by sgbms1

Report a problem with this skill