登壇する時に使っているスライド発表ツールの紹介
一ヶ月前に勉強会で話してきました。
2017年の2月から運用しています。
リポジトリ
目的
- gitで管理したい
- 自己紹介とかの共通ページを毎回スライド書くごとに書きたくない
- Markdownでスライドを書きたい
- 極力JSを書かないようにして、もしカスタマイズしたいときは書けば良い。(基本MarkdownとCSSだけ)
- 発表者モードができる(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が提供しているメソッドを使います。
// 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();
imagemin
imageminのwebpackのloaderを使います。
// 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です。
// 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
発表者モード
普段は発表者ノートなしでやっていますが、英語の発表だと発表者ノートがほしくなったので、プレゼンターモードを実装しました。
スライドに発表者モード追加してみた https://t.co/Ud0ucV2YZD
— hiroppy😶 (@about_hiroppy) 2017年11月3日
現在のページの表示も実装したけど、いいデザインがなくてコメントアウト中
表示用に新しい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); } });
送っている情報はタイムスタンプと現在のページだけですが、実際イベントをリッスンしてページを動かしているのでタイムスタンプは使いません。
2015年ぐらいにニコナレってサービスで同じ実装をしたので、特に困らずにできた。
操作
Appleの公式サイトでも紹介されているLogicool Spotlight Presentation Remoteをいつも使っています。
すごい便利でアプリを入れれば、ポインターとかも使えます。
マークダウンで書けるサービス
spectacle
ライブコーディングもできてすごいハイスペック。
マークダウンで書けるけど、メインはJSX。