この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
大阪オフィスの岡田です。 2020年も1ヶ月半が過ぎましたが、2019年の弊社大阪オフィスの忘年会の出し物として プレゼンテーションカラオケ をやるのにあたり、サービスをサクッと作りました。
技術を試してみるという意味もあったので、サクッとブログに残しておきます。
経緯
私の所属している大阪オフィスでは忘年会で プレゼンテーションカラオケ(パワ-ポイントカラオケ) をやることになったのですが、その決定現場にたまたま居合わせたため、サービスを作ってみようと手を上げました。
最近はAPIサーバばかり作っていたので、久々にフロントエンドも触っておこうという下心もあり、一石二鳥です。
プレゼンテーションカラオケとは?
制限時間5分でランダムに表示される画像に対して即興でプレゼンテーションします。 詳細はこちらを御覧ください。 (参照:日本プレゼンカラオケ協会)
作ったもの(2020/02/14追記)
デモです。
技術と選定理由
要件は 画像をランダムに表示させる のみなので、フロントエンドだけで完結出来ると考えました。
後になって気づいたことですが、この時点では忘年会会場の電波状態がわからなかったので、この判断は正しかったですね。
個人的にもPolymerを触ったことがあり、今回は lit-html
に挑戦することにしました。
- node
- yarn
- lit-html
lit-htmlとは?
lit-htmlは、JavaScriptでHTMLテンプレートを書くことができ(HTML in JS)、DOMを作成・更新するのに必要なデータとテンプレートを効率的に描画・再描画します (参照:https://lit-html.polymer-jp.org/)
WEBComponent を利用したHTMLのレンダリングを行うライブラリです。
JSXのような感じですが、VDOMを利用しておらず、ネイティブクローニングをによるDOMの更新を行うのが特徴です。
提供される関数は html
render
の2つで、html で要素を作り、renderで描画します。
ソースコード
全体は github に公開しています。 メインのアプリケーション部分はこんな感じ。
import { html, render } from 'lit-html'
import { images } from './images.js'
const MAX_SLIDES = 4
// 配列をシャッフルしてMAX_SLIDES枚返す
const shuffleImages = imgs => {
const shuffledImgs = imgs
.map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0])
.map((a, i) => {
return { path: `./images/${a[1]}`, pageNo: i + 1 }
})
return shuffledImgs.slice(0, MAX_SLIDES)
}
// 状態を更新する
const createStore = () => {
let state = {}
return newState => {
if (!state.slides || newState) {
if (!state.slides || newState.slideIndex === undefined) {
const slides = [
{ path: './resources/PPK_first.png', first: true },
...shuffleImages(images),
{ path: './resources/PPK_end.png', last: true },
]
state = { ...state, ...newState, slides }
} else {
state = { ...state, ...newState }
}
renderApp()
}
return state
}
}
const store = createStore()
// スライドを開始
const startSlide = () => {
store({ slideIndex: 0 })
}
// 次のスライド
const nextSlide = () => {
const { slideIndex, slides } = store()
const slide = slides[slideIndex]
if (slide && slide.last === undefined) {
store({ slideIndex: slideIndex + 1 })
} else {
console.warn(`Not have next slide. index = ${slideIndex}`)
}
}
// 前のスライド
const prevSlide = () => {
const { slideIndex } = store()
if (slideIndex > 0) {
store({ slideIndex: slideIndex - 1 })
} else {
console.warn(`Not have prev slide. index = ${slideIndex}`)
}
}
// タイトルに戻る
const backToTitle = () => {
store({ slideIndex: undefined })
}
// スライド用のStyle
const slideStyle = path => {
return `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: black;
background-image: url("${path}");
background-size: contain;
background-position: center;
background-repeat: no-repeat;
`
}
// スライドのページ(html)
const slidePage = () => {
const { slideIndex, slides } = store()
console.debug('image:', slides[slideIndex])
const path = slides[slideIndex].path
const pageNo = slides[slideIndex].pageNo
const pageInfo = html`
<div
style="color: white; text-align: right; position: relative; z-index: 100;"
>
${pageNo}/${MAX_SLIDES}
</div>
`
return html`
${pageNo && pageInfo}
<div
@click=${slides[slideIndex].last ? backToTitle : nextSlide}
style=${slideStyle(path)}
/>
`
}
const page = () => {
const { slideIndex, slides } = store()
if (slideIndex === undefined) {
return html`
<div
@click=${startSlide}
style=${slideStyle('./resources/PPK_titleback.png')}
/>
`
} else {
return html`
${slidePage()}
`
}
}
// pageを描画する
function renderApp() {
return render(page(), document.body)
}
// キー操作系 右・下・Enterで次のスライド/左・上で前のスライド
document.addEventListener('keydown', e => {
const key = e.key
switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
case 'Enter':
nextSlide()
break
case 'ArrowLeft':
case 'ArrowUp':
prevSlide()
break
}
})
renderApp()
コマンド
$ yarn create:images // /resources 内にある画像ファイル名の配列を作る(サーバ起動前)
$ polymer serve` //サーバ起動
$ polymer build` //ビルド
ハマりどころ
状態をどこで持つか?
React等では標準搭載されている機能かと思いますが、lit-htmlにはありません。より関数的に作れるのでは個人的には好みなのですが、プレゼンテーションの状態によって表示するページを出し分けたかったので、状態を管理する必要が出てきました。
こちらの記事 を参考にさせていただき、自前で実装しています。
画像の情報をどうやってもたせるか
これは lit-html は関係ない話ですが、フロントエンドのみで画像をランダム表示させるのに、画像情報をどうやってもたせるかを悩んでいました。色々悩んだ末、画像名の配列を出力する npm script を用意することにしました。もっといいやり方があったら教えて下さい。
まとめ
機能がシンプルすぎるため、lit-htmlだけでサービスを作ることは難しい印象でした。
依存ライブラリも少ないため、最終的に作られるものの容量を意識しなければ行けない場面(lambda等) では威力を発揮するかもしれません。
肝心のプレゼンテーションカラオケは大盛況で、忘年会だけで収まらず、納会でも実施されるほどでした。