ブログ記事からスライドを作れるようにしました

作ったもの

  • 記事のfrontmatterにslidesを足すと、/slides/記事のidにスライドができる
  • 記事の段落はスピーカーノートになる
  • リスト・コード・画像・表はスライドに出る

作ったもの

このスライドの表紙。記事のタイトルと日付、右下に自己紹介が出ている
このスライドの表紙。記事のタイトルと日付、右下に自己紹介が出ている

作ろうと思った理由

記事を先に書いて、スライドはあとから作る

書き方のルール

記事 スライド
##の見出し 章の区切り
###の見出し 1枚のスライド
--- 同じ見出しのままスライドを分ける
段落 スピーカーノート(スライドには出ない)
リスト・コード・画像・表 スライドに出る
段落の中の太字 表示するものがないときに大きく出る

書き方のルール

このスライドの「書き方のルール」。表がそのままスライドになっている
このスライドの「書き方のルール」。表がそのままスライドになっている

全体の流れ

  • ビルド時:記事の本文をMarkdownの構文木にして、スライドのHTMLを作る
  • ブラウザ描画時: スライドモードに切り替え、発表者ビューとの同期

記事をスライドに分ける

記事をスライドに分ける

Markdownを構文木にする

function parse(body: string, mdx: boolean): Root {
  return fromMarkdown(body, {
    extensions: mdx ? [gfm(), mdxjs()] : [gfm()],
    mdastExtensions: mdx
      ? [gfmFromMarkdown(), mdxFromMarkdown()]
      : [gfmFromMarkdown()],
  });
}
  1. [1] syntax-tree/mdast-util-from-markdown

記事をスライドに分ける

見出しで区切る

  • ##が来たら、章の名前を覚えて新しいスライドを始める
  • ###が来たら、章の名前を小さく添えたスライドを始める
  • ---が来たら、同じ見出しのまま次のスライドにする

記事をスライドに分ける

見出しで区切る

構文木の要素とスライドの対応。見出しごとにスライドが分かれ、段落はスピーカーノートになり、---のあとは同じ見出しの続きのスライドになる
構文木の要素とスライドの対応。見出しごとにスライドが分かれ、段落はスピーカーノートになり、---のあとは同じ見出しの続きのスライドになる

記事をスライドに分ける

段落とそれ以外を振り分ける

段落は、そのあとに出てくるリストや画像のスピーカーノートになる

記事をスライドに分ける

1枚に載せる量を決める

const HEAVY: ReadonlySet<VisibleKind> = new Set([
  'code', 'image', 'table', 'embed', 'tweet', 'html',
]);

function conflicts(visible: VisibleBlock[], kind: VisibleKind): boolean {
  const hasHeavy = visible.some((v) => HEAVY.has(v.kind));
  const hasList = visible.some((v) => v.kind === 'list');
  if (HEAVY.has(kind)) return hasHeavy || hasList;
  if (kind === 'list') return hasHeavy;
  return false;
}

記事をスライドに分ける

レイアウトを選ぶ

載っているもの レイアウト
リストだけ 箇条書き
コードだけ コード
画像だけ 画像
表だけ 表
種類が混ざっている 汎用
段落しかない 段落中の太字を大きく出す

HTMLにする

HTMLにする

コードの色付け

highlighter ??= createHighlighter({
  themes: [CODE_THEME],
  langs: [],
  engine: createJavaScriptRegexEngine({ forgiving: true }),
});
  1. [2] Shiki: Regex Engines

HTMLにする

参照の番号

return {
  type: 'emphasis',
  data: {
    hName: 'sup',
    hProperties: { className: ['cite'] },
    citation,
  },
  children: [
    {
      type: 'link',
      url: reference.url,
      title: citation.title,
      children: [{ type: 'text', value: `[${citation.n}]` }],
    },
  ],
} as RootContent;
  1. [3] syntax-tree/mdast-util-to-hast

HTMLにする

参照の番号

このスライドの参照の一覧
このスライドの参照の一覧

スライドの表示

スライドの表示

16:9を保つ

.deck {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  container-type: size;
}

.slide {
  grid-area: 1 / 1;
  inline-size: min(100cqi, 100cqb * 16 / 9);
  aspect-ratio: 16 / 9;
  container-type: size;
  overflow: clip;
}
  1. [4] MDN: Container size and style queries

スライドの表示

デザインをCSS変数で切り替える

名前 動き
simple アニメーションなし
keynote ふわっと拡大して出る
paper 横に流れる
pop 弾んで出る
terminal 見出しを打ち込むように出る

スライドの表示

デザインをCSS変数で切り替える

.slide.is-active .anim,
.slide.is-active .block-list li {
  animation: var(--enter) var(--dur) var(--ease) backwards;
  animation-delay: calc(var(--delay) + var(--i, 0) * var(--stagger));
}

スライドの表示

デザインをCSS変数で切り替える

このスライドをダークモードで出したところ。コードは暗い背景で出る
このスライドをダークモードで出したところ。コードは暗い背景で出る

スライドの表示

デザインをCSS変数で切り替える

このスライドの右下の操作バー。デザインを選ぶメニューがある
このスライドの右下の操作バー。デザインを選ぶメニューがある

スライドの表示

表示中のスライドだけ操作できるようにする

  1. [5] MDN: inert

スライドの表示

発表者ビューとの同期

このスライドの発表者ビュー。左に今のスライドと次のスライド、右にスピーカーノートが出ている
このスライドの発表者ビュー。左に今のスライドと次のスライド、右にスピーカーノートが出ている
  1. [6] MDN: Broadcast Channel API

スライドの表示

発表者ビューとの同期

  • Nでスライドの下にスピーカーノートを出す
  • Fで全画面にする
  • 印刷するとPDFにできる

はみ出し対策

  • リストとコードや画像は、同じスライドに載せない
  • はみ出すスライドだけ、表示するときに文字を小さくする
  • ビルドしたスライドをブラウザで開いて、はみ出していないかをテストする

はみ出し対策

表示するときに文字を小さくする

if (fitsAt(1)) return { fit: 1, overflow: false };
if (!fitsAt(MIN_FIT)) return { fit: MIN_FIT, overflow: true };
let low = MIN_FIT;
let high = 1;
while (high - low > PRECISION) {
  const mid = (low + high) / 2;
  if (fitsAt(mid)) low = mid;
  else high = mid;
}
return { fit: Math.floor(low * 100) / 100, overflow: false };

はみ出し対策

はみ出しの判定

  • スライドの本文の領域や、その中のブロックから、子の要素の矩形が出ていないか
  • コードのようにスクロールする要素で、中身が要素より大きくなっていないか

まとめ

  • 記事を書けば、スライドもできる
  • 段落はスピーカーノート、リストや画像はスライドになる
  • 分け方とレイアウトはビルドのときに決め、はみ出しはブラウザで調整する
  • はみ出しはテストで確かめている

参照

  1. syntax-tree/mdast-util-from-markdowngithub.com/syntax-tree/mdast-util-from-markdownMarkdownをmdast(Markdownの構文木)にするライブラリ
  2. Shiki: Regex Enginesshiki.style/guide/regex-enginesShikiの正規表現エンジン(WASMとJavaScript)
  3. syntax-tree/mdast-util-to-hastgithub.com/syntax-tree/mdast-util-to-hastmdastをHTMLの構文木(hast)にするライブラリ。data.hNameで要素名を変えられる
  4. MDN: Container size and style queriesdeveloper.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queriesコンテナ単位(cqw / cqh)の説明
  5. MDN: inertdeveloper.mozilla.org/ja/docs/Web/HTML/Global_attributes/inert
  6. MDN: Broadcast Channel APIdeveloper.mozilla.org/ja/docs/Web/API/Broadcast_Channel_API

ありがとうございました