Slidevでスライド作ったら使い回しやすかった

Slidevでスライドを作成してみました。 この記事では、良かった点や作成中に調べたことを紹介します。
2022.05.06

Slidev とは?

Slidev を使えば、Markdownでスライドを作成できます。
Vueでコンポーネントを作ってスライドに表示させたり、 Windi CSSでclass属性にスタイルを記述できます。

良かった点

Vueは2年ぶりぐらい、初Windi CSSの筆者でも使えました。
良かった点を先にまとめておきます。

  • Markdownで書ける
    • 使い慣れたテキストエディタで編集できる
    • GitHubで管理できる
  • レイアウト・コンポーネントにより使い回しがしやすい
  • サイズやレイアウトがテキストとして可視化される

インストール

Installation | Slidevに従い、インストールします。

npm init slidev@latest

以下のコマンドでブラウザが開き、スライドが表示されます。

npm run dev

ディレクトリ構成

公式ドキュメントを参考に作成したディレクトリ構成が以下です。

./
|-- .gitignore
|-- .npmrc
|-- README.md
|-- components
|   `-- MyKbd.vue
|-- global-bottom.vue
|-- layouts
|   |-- cover.vue
|   |-- end.vue
|   |-- intro.vue
|   `-- two-col-with-gif.vue
|-- netlify.toml
|-- package-lock.json
|-- package.json
|-- public
|   |-- hoge.png
|   |-- foo.png
|   `-- ...
|-- setup
|   `-- windicss.ts
|-- slides-export
|   |-- 01.png
|   |-- 02.png
|   `-- ...
|-- slides-export.pdf
|-- slides.md
`-- vercel.json

以下、手を加えたファイルや自動では作成されないものについて説明します。

slides.md, slides-export.pdf, slides-export/
slides.md にスライドの内容を書きます。

npx slidev exportを実行することで、slides-export.pdfが生成されます。
package.jsonに同様のコマンドが記述されているため、npm run exportでもPDF出力が可能です。 初回はnpm i -D playwright-chromiumでインストールが必要です。

npx slidev export --format pngを実行すると slides-exportディレクトリが作成され、PNG画像として出力できます。 ファイル名は 01.png, 02.png のように連番です。

npx slidev export --helpでより詳しくオプションを見ることができます。

.gitignore
作成された.gitignoreで指定されていない以下の2つを書き加えました。

  • 出力したPDFファイルslides-export.pdf
  • PNG画像の入ったディレクトリslides-export

components/MyKbd.vue
自分で作成したコンポーネントファイルです。

global-bottom.vue
すべてのスライドの下側に表示する内容を書いたファイルです。

layouts/
スライドのレイアウトを定義したvueファイルを入れるディレクトリです。

public/
画像を入れるディレクトリです。

setup/windicss.ts
Windi CSSの設定をするためのファイルです。

設定とスライド例

スライド記述の基本構成

slides.mdにスライドを記述します。
スライド(ページ)を増やすには、スライド間に---の行を加えます。 各スライドに設定(レイアウトの変更など)をする場合は、---の代わりにFront Matter(---で挟んでyamlを書く形式)を使います。

すべてのスライドに共通する設定(テーマやフォントなど)は、最初のスライドのFront Matterにまとめて書きます。

以下、スライド記述ファイルの例を簡単に示します。

./slides.md

---
theme: seriph
layout: hoge
---

# タイトル

## サブタイトルや発表者名

最初のスライドのレイアウトをhogeに指定した。

---
layout: center
---

# 2番目のスライドの見出し

2番目のスライドの本文。レイアウトをcenterに指定した。

---

3番目のスライドの本文。設定なしであるため、スライド区切りは`---`のみ。

---

4番目のスライドの本文。

...

Prettier を使うと ページ区切りのためのFront Matterが勝手に変換されてしまうため、適宜無効にしましょう。

参考: Markdown Syntax | Slidev

すべてのスライドに共通する設定

今回はテーマをseriph、フォントにヒラギノ丸ゴ ProN W4を指定しました。

---
theme: seriph
fonts:
  sans: "HiraMaruProN-W4"
---
ここから1枚めのスライドの本文。

フォントを変更した場合は、もう一度npm run devを実行する必要があります。

フォントの指定にはPostScript名を書くようです。
以下はMacのFont Book.appのフォント情報の表示例です。

フォントの指定は package.jsonで行う方法もあります。

参考:

最初のスライド

実装を見ると、 1枚目のスライドでレイアウトを指定しない場合はデフォルトのレイアウトではなくcoverになるようです。

レイアウトcoverでは h1pタグについて設定されています。

以下、スライド例です。

---
theme: seriph
fonts:
  sans: "HiraMaruProN-W4"
---

# この世、
# ショートカットキー
# 多すぎ

## えーたん

1枚目のスライドであるため、全体設定のthemefontsのFront Matterを含みます。

上記ではタイトルを改行して表現するため#を3つ使いましたが、brタグでも可能です。

別のレイアウトを試す

今度は中央に配置するためのレイアウトcenterを使う例です。

---
layout: center
---

# 一日どれくらい スクロール していますか?

画像を表示する

ローカルの画像はpublicディレクトリに入れ、/hoge.pngのように指定します。

---
layout: center
---

![mouse](/computer_mouse_top.png)

画像サイズを指定する場合はimgタグを使い、class属性にWindi CSSでサイズを書きます。
Windi CSSでは、高さはh-xxのように指定します。 詳しくは Sizing | Windi CSS をご覧ください。
今回はh-80を指定しました。

---
layout: center
---

<img class="h-80" src="/trackpad.jpg">

w-50のように 高さではなく幅を使うこともできます。


GUIのスライド作成ツールでは、画像ファイルをD&Dしたりメニューをポチポチする必要があります。
Slidevでは、スニペットを使ったりコピー&ペーストして数文字編集するだけで画像を追加できます。

逆に、GUIのスライド作成ツールでは 図形や吹き出しの追加が数クリックで簡単に行えますが、
Slidevはそれらも画像として用意したりコードとして書く必要があります。 線と矢印についてはSlidevのPresenter Modeで追加できます(後述)。

参考:

flex で並べ、フォントサイズも変える。

要素の横並びはflexgridを使い、フォントサイズはtext-hogeのように指定します。 詳しくは Flexbox | Windi CSSTypography | Windi CSS をご覧ください。

---
layout: center
---

# この世、ショートカットキー多すぎ

<div class="text-2xl">
  全部は覚えられない、、、
</div>

<div class="flex">
  <img class="h-72" src="/pien.png">
  <img class="h-72" src="/many-shortcut-key.png">
</div>

文字の背景色を変える

文字の背景色を変えるには、divタグで囲み Windi CSS の Background Colorで指定します。
今回は黄色にするためbg-yellow-300を指定しました。

---
layout: center
---

<div class="text-7xl flex items-center space-x-5">
  <div class="bg-yellow-300 text-center leading-32 h-32 w-24">j</div>
  <div class="bg-yellow-300 text-center leading-32 h-32 w-24">k</div>
  <p>をセットで覚える</p>
</div>

コンポーネントとレイアウトを使ってみる

VueのコンポーネントとSlidevのレイアウトを自分で作ってみます。

まず、完成したスライドが以下です。

このスライドに登場する主な要素は5つです。

  1. 見出し
    • Markdownの#(h1タグに相当)で書く
    • スライドの一番上の「Google Calendar」
  2. 「黄色の背景に文字」+「文字」の組み合わせが左右に並んだもの
    • スライドの「j:次の日」「k:前の日」
  3. ショートカットキーの動作を示すためのGIF
  4. GIFの右にある説明
    • 任意の要素を入れられるようにします
    • スライドの「d:[日]ビュー」の部分で、これも「黄色の背景に文字」+「文字」の組み合わせです
  5. 「黄色の背景に文字」+「文字」の組み合わせの背景が緑になったもの
    • スライド右下の「?:ショートカットのヘルプ」

コンポーネント

まずは、スライドの2,4,5に共通する「黄色の背景に文字」+「文字」をコンポーネント化します。
コンポーネントはcomponentsディレクトリに配置します。 今回はファイル名をMyKbd.vueとしました。

なお、最初から使えるコンポーネントの説明は Components | Slidev に、 実装は GitHub slidevjs/slidev にあります。

components/MyKbd.vue

<script setup lang="ts">
import { computed } from "@vue/reactivity";
const props = defineProps<{
  height: number;
  width?: number;
  keybind: string;
  textSize: string;
}>();

const keyClass = computed(() => {
  let width = props.width;
  if (!width) {
    width = props.height;
  }
  return `bg-yellow-300 text-center leading-${props.height} h-${props.height} w-${width}`;
});

const textClass = computed(() => {
  return `flex items-center ${props.textSize}`;
});
</script>

<template>
  <div v-bind:class="textClass">
    <div v-bind:class="keyClass">
      {{ props.keybind }}
    </div>
    <slot />
  </div>
</template>
簡単なコード説明

{{ props.keybind }}では、受け取ったプロパティをそのままテキスト展開しています。
scriptタグの中でクラス名を返す変数書くことで、templateタグの中のv-bind:class=hogeの部分でクラスを動的に指定できます。 slotタグを入れることで、MyKbd開始タグと終了タグの間に書いた要素をslotタグと置き換えができます。

クラスを動的に指定するため、Windi CSSのsafelistを使いました。 SlidevのWindi CSSの設定はsetup/windicss.tsdefineWindiSetUpを使って書きます。

setup/windicss.ts

import { defineWindiSetup } from '@slidev/types'

export default defineWindiSetup( ()=> ({
  safelist: "h-20 w-20 w-30 leading-20"
}))

参考:


ここで、コンポーネントを使わずに書いた例、使って書いた例を出します。

コンポーネントを使わずに書く例:

<div class="flex items-center text-3xl">
  <div class="bg-yellow-300 text-center leading-12 h-12 w-12">
    d
  </div>
  ショートカットキーの説明文
</div>

コンポーネントを使って書く例:

<MyKbd keybind="d" height=12 textSize="text-3xl">
  ショートカットキーの説明文
</MyKbd>

コンポーネントを使うことで、(適切な名前をつけていれば)何を表す部分か分かりやすくなります
また、一ヵ所修正するだけで複数ページの要素をまとめて更新できるため、使い回しやすいです。

さらに、別の発表のスライドで使い回す時にも便利です。
コンポーネントなしで使い回す場合は、slides.mdから該当部分を探しテキストを範囲選択してからコピー&ペーストする必要があります。
コンポーネントありの場合は、コンポーネントファイルをコピー&ペーストするだけです。

レイアウトを作る

続いて、レイアウトを作成します。 レイアウトはlayoutsディレクトリに配置します。
なお、最初から使えるレイアウトの説明は公式ドキュメント LayoutsDirectory Structure に、 実装は GitHub slidevjs/slidev にあります。

今回はファイル名をtwo-col-with-gif.vueにしました。

レイアウトの実装例

layouts/two-col-with-gif.vue

  <script setup lang="ts">
  interface Props {
    help_key?: string;
  }
  const props = withDefaults(defineProps<Props>(), {
    help_key: "?",
  });
  </script>

  <template>
    <div class="slidev-layout">
      <!-- 縦に要素を並べる -->
      <div class="h-full flex flex-col">
        <!-- 1. 見出し -->
        <slot name="title" />

        <!-- 2. 「黄色の背景に文字」+「文字」の組み合わせが左右に並んだもの -->
        <div class="grid grid-cols-2">
          <!-- ショートカットキー 1 -->
          <MyKbd keybind="j" height="12" textSize="text-3xl">
            :
            <slot name="j_feature" />
          </MyKbd>

          <!-- ショートカットキー 2 -->
          <MyKbd keybind="k" height="12" textSize="text-3xl">
            :
            <slot name="k_feature" />
          </MyKbd>
        </div>

        <!-- 下寄せ かつ 左右の端に要素を並べる -->
        <div class="mt-auto flex justify-between">
          <!-- 3. GIF -->
          <slot />

          <!-- 縦に要素を並べる -->
          <div class="flex flex-col">
            <!-- 上下中央 -->
            <div class="my-auto">
              <!-- 4. 説明欄 -->
              <!-- ショートカットキー 3 -->
              <slot name="description" />
            </div>

            <!-- 5. ショートカットキーのヘルプ -->
            <!-- 背景緑で中央に配置 -->
            <div class="flex items-center bg-lime-300 h-12 p-4">
              <!-- ショートカットキー 4 -->
              <MyKbd
                v-bind:keybind="props.help_key"
                height="8"
                textSize="text-xl"
              >
                :
                <slot name="help_description"> ショートカットのヘルプ </slot>
              </MyKbd>
            </div>
          </div>
        </div>
      </div>
    </div>
  </template>

コンポーネントと違い、レイアウトの引数は各スライドのFront Matterの中で渡します。

以下の例では、レイアウトtwo-col-with-gifへ 引数help_keyとして文字列:hを渡しています。

./slides.md

---
layout: two-col-with-gif
help_key: ":h"
---

slidev-layoutクラスは 最初から使えるレイアウトの半分以上に使われており、 h1litableなどの基本的なタグにスタイルを当てています。 GitHub slidevjs/slidev で実装を見ることができます。

slotの中身をslides.mdで書く場合は、templateタグで囲むか、::hoge::の下に書きます。
たとえば、レイアウトの中に書いたname="j_feature"スロットの内容は、slides.mdでは::j_feature::の下に書きます(例は後述)。

::hoge::記法のパースの実装 を見ると、 スロットのnameは「アルファベット、数字、アンダースコア」のいずれかの文字しか使えないようです。

x: <slot name="j-feature"></slot>
o: <slot name="j_feature"></slot>

新しいレイアウトファイルを追加したらrestartで反映しましょう。

自作のコンポーネントとレイアウトを使った例

自作のコンポーネントMyKbdとレイアウトtwo-col-with-gifを使って書いた例です。

---
layout: two-col-with-gif
---
::title::
# Google Calendar

::j_feature::
次の日

::k_feature::
前の日

::default::
<img class="h-88" src="/google-calendar.gif">

::description::
<MyKbd keybind="d" height=12 textSize="text-3xl">
  :[日]ビュー
</MyKbd>

長くなるため、自作のコンポーネントとレイアウトを使わずに書いた例は折りたたみで表示します。

自作のコンポーネントとレイアウトを使わずに書いた例
---
layout: default
---
<!-- 縦に要素を並べる -->
<div class="h-full flex flex-col">
  <!-- 1. 見出し -->
  <h1>Google Calendar</h1>

  <!-- 2. 「黄色の背景に文字」+「文字」の組み合わせが左右に並んだもの -->
  <div class="grid grid-cols-2">
    <!-- ショートカットキー 1 -->
    <div class="flex items-center text-3xl">
      <div class="bg-yellow-300 text-center leading-12 h-12 w-12">j</div>
      :
      <p>次の日</p>
    </div>
    <!-- ショートカットキー 2 -->
    <div class="flex items-center text-3xl">
      <div class="bg-yellow-300 text-center leading-12 h-12 w-12">k</div>
      :
      <p>前の日</p>
    </div>
  </div>

  <!-- 下寄せ かつ 左右の端に要素を並べる -->
  <div class="mt-auto flex justify-between">
    <!-- 3. GIF -->
    <img class="h-88" src="/google-calendar.gif">
    <!-- 縦に要素を並べる -->
    <div class="flex flex-col">
      <!-- 上下中央 -->
      <div class="my-auto">
        <!-- 4. 説明欄 -->
        <!-- ショートカットキー 3 -->
        <div class="flex items-center text-3xl">
          <div class="bg-yellow-300 text-center leading-12 h-12 w-12">d</div>
          :[日]ビュー
        </div>
      </div>
      <!-- 5. ショートカットキーのヘルプ -->
      <!-- 背景緑で中央に配置 -->
      <div class="flex items-center bg-lime-300 h-12 p-4">
        <!-- ショートカットキー 4 -->
        <div class="flex items-center text-xl">
          <div class="bg-yellow-300 text-center leading-8 h-8 w-8">?</div>
            : ショートカットのヘルプ 
        </div>
      </div>
    </div>
  </div>
</div>

レイアウトもコンポーネント同様、 何を表すのかはっきりしたり、複数ページの要素をまとめて更新できたり、使い回しやすくなります。

複雑なレイアウトやページによってレイアウトの違うスライドはslides.mdに書く量が増えるため注意です。

最後のスライド

Slidevではslides.mdで書いたスライドのほか、レイアウトendのスライドが最後に自動で追加されます。

slides.mdで書いたスライドが12ページ分であれば、レイアウトendのスライドのページ番号は13になります。 発表時のツールバーには 1 / 122 / 12 → ...→ 12 / 1213 / 12 とページ番号が表示されます。 いきなり本番で使うと混乱するので注意しましょう。

同名のレイアウトを実装した場合はローカルが優先となるため、layouts/end.vueを配置すれば書き換えることができます。

ページ番号を入れる

ページ番号を入れるには、全スライド(ページ)に表示させる要素を書くglobal-top.vueglobal-bottom.vueを使います。 前者はの上、後者は下に表示されます。

以下のコードは Global Layers | Slidev より引用です。 ページの左下に現在のページ数 / 全ページ数を表示します。

./global-bottom.vue

<template>
  <footer
    v-if="$slidev.nav.currentLayout !== 'cover'"
    class="absolute bottom-0 left-0 right-0 p-2"
  >
    {{ $slidev.nav.currentPage }} / {{ $slidev.nav.total }}
  </footer>
</template>

$slidevが Sidev のページや設定を保持している変数です。 Vue Global Context | Slidev で詳しく見ることができます。

npm run dev(=slidev --open) を実行中に./global-bottom.vueを作成した場合、再実行する必要があります。

GUIのスライド作成ツールでは 編集しているスライドが何ページ目かをすぐに把握できますが、
Slidevでは 編集しているslides.mdとブラウザでのプレビューを照らし合わせて確認します。

発表ツール

スライド下のツールバー もしくはnpm run dev実行時に表示されるURL(例:http://localhost:3030/presenter)で Presenter Mode を開けます。

Presenter Modeを開くと以下の5つが表示されます。

  • 現在表示しているスライド
  • 現在表示しているスライドのノート
  • 次のスライド
  • 経過時間
  • ツールバー

経過時間は右上に表示され、タブが開かれた瞬間からスタートです。 リセットするには時間の左にあるリセットボタンをクリックします。

発表者ノート

各スライド(ページ)の最後のコメントがノートとしてPresenter Modeで表示されます。

---

# h1タグ。Windi CSS では text-4xl です

<!-- これはただのコメントで、ノートにはならない -->

本文はpタグになります。  

<!--
各スライドの最後のコメントのみノートになる  
asdfdkfjdls<br>
sdjflsdjfs
-->

ノートでの改行もMarkdownのようです。
行末半角スペース2つか、brタグです。

線や矢印をペイントできます。

マウスカーソルの巨大化もできますが、Google Slidesのレーザーポインタのような機能はありませんでした。

その他

リンク集

よく開いた公式ドキュメントのページのリンクをまとめておきます。

日本語のSlidev公式ドキュメント もあります。

情報を探す時

Slidevについての情報を探すとき、ググるとGoogle Slidesやパワポの情報も出てくることがあります。
筆者は以下のようにして情報を探しました。

  • 公式ドキュメント 内の検索機能を活用
  • Google検索でサイト指定 site:sli.dev hoge
  • slidev hogeではなくsli.dev hogeのようにURLに合わせてピリオドを入れて検索
  • GitHub リポジトリ の実装を見る

スライドの内容

スライドの内容は、社内のLTで発表したものを抜粋して作成しました。
気になった方は以下のリンクの記事版をどうぞ。

書いたコードのGitHubのリンクは以下です。