技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

登壇する時に使っているスライド発表ツールの紹介

一ヶ月前に勉強会で話してきました。

hiroppy.github.io

2017年の2月から運用しています。

リポジトリ

github.com

目的

  • gitで管理したい
  • 自己紹介とかの共通ページを毎回スライド書くごとに書きたくない
  • Markdownでスライドを書きたい
  • 極力JSを書かないようにして、もしカスタマイズしたいときは書けば良い。(基本MarkdownCSSだけ)
  • 発表者モードができる(new!!)

ツール・フレームワーク

JavaScript

CSS

  • postcss-cssnext
  • postcss-mixins
  • postcss-smart-import
  • postcss-reporter
  • postcss-browser-reporter

ビルドフロー

webpack

Markdown -> HTML -> React

// in webpack

{
  test: /\.md$/,
  use: [
    'html-loader',
    'markdown-loader'
  ]
}

ローダーチェインはmarkdown-loaderを通し、html-loaderを通します。

slideを取得する

require.contextというwebpackが提供しているメソッドを使います。

Dependency Management

  // fetch all slides 
  const context = require.context('./slides', true, /(md|html)$/);
  const res = {
    id    : context.id,
    slides: context
      .keys()
      .sort() // sort by File Name
      .map((e) => context(e))
  };

ファイル名とディレクトリ名は数字で記載し、それをスライドの並び順として定義します。
e.g. 0-title.md, 01-context.md, 02-dir/0-title.md

注意点として、require.contextは動的には使えないので、第一引数の目的のパスは文字列でなければなりません。

取得したHTMLをReactへ

ReactのdangerouslySetInnerHTMLを使います。

const Base = (props) => (
  <article>
    {
      props.slides.map((slide, i) => (
        <section
          key={slide.meta.id}
          className={slide.meta.className}
          data-bespoke-backdrop={slide.meta.background}
          dangerouslySetInnerHTML={{ __html: slide.context }}
        />
      ))
    }
  </article>
);

これで完成。

プロダクション

Service Worker

offline-pluginを使い、Service WorkerによりJSとCSSと画像のキャッシュをしています。

// webpack.prod.config.js

module.exports = {
  plugins: [
    new OfflinePlugin(),
    ...
  ]
};

// offline.js

import offlineRuntime from 'offline-plugin/runtime';

offlineRuntime.install();

github.com

imagemin

imageminのwebpackのloaderを使います。

github.com

// webpack.config.js

{
  test: /\.(png|jpg|gif|svg?)$/,
  use: [
    'file-loader',
    'image-webpack-loader'
  ]
}

ローダーチェインはimage-webpack-loader -> file-loader。

Dynamic Import

発表者モードは使う場面が限られるのでdynamic importで制御しています。
閲覧の場合は、プレゼンターモードを読み込ませません。 Dynamic Importは現在stage-3です。

github.com

// https://github.com/abouthiroppy/slides/blob/master/src/lib/AppContainer.js#L31

// AppContainer.js

constructor(props) {
  if (mode === 'view') {
    import(/* webpackChunkName: 'presenter.view' */ './ContentView/View')
      .then((e) => {
        ...
      });
  }
  else if (mode === 'host') {
    import(/* webpackChunkName: 'presenter.host' */ './ContentView/Host')
      .then((e) => {
        ...
      });
  }
}
Hash: 08e7000cd507fa241759
Time: 77409ms
                    Asset       Size  Chunks                    Chunk Names
0.08e7000cd507fa241759.js    14.4 kB       0  [emitted]
1.08e7000cd507fa241759.js    3.65 kB       1  [emitted]         presenter.host
2.08e7000cd507fa241759.js    1.45 kB       2  [emitted]         presenter.view
  08e7000cd507fa241759.js    1.27 MB       3  [emitted]  [big]  main

発表者モード

普段は発表者ノートなしでやっていますが、英語の発表だと発表者ノートがほしくなったので、プレゼンターモードを実装しました。

f:id:about_hiroppy:20171207094242p:plain

現在のページの表示も実装したけど、いいデザインがなくてコメントアウト

表示用に新しいwindowが立ち上がり、スピーカー用に上記のページがリロードされます。

これは、localstorageを使いページ情報を表示用とスピーカー用で共有をしています。

// host.js
window.slide.bespoke.on('activate', (e) => {
  localStorage.setItem('page', JSON.stringify({
    date: Date.now(),
    page: e.index
  }));
});

// view.js
window.addEventListener('storage', (e) => {
  if (e.key === 'page') {
    const page = JSON.parse(e.newValue).page;

    window.slide.bespoke.slide(page);
  }
});

github.com

送っている情報はタイムスタンプと現在のページだけですが、実際イベントをリッスンしてページを動かしているのでタイムスタンプは使いません。

2015年ぐらいにニコナレってサービスで同じ実装をしたので、特に困らずにできた。

niconare.nicovideo.jp

操作

Appleの公式サイトでも紹介されているLogicool Spotlight Presentation Remoteをいつも使っています。

www.apple.com

すごい便利でアプリを入れれば、ポインターとかも使えます。

マークダウンで書けるサービス

spectacle

github.com

ライブコーディングもできてすごいハイスペック。
マークダウンで書けるけど、メインはJSX。

GitPitch

https://gitpitch.com/gitpitch.com

githubmarkdownをpushするとスライドになる。