reveal.js の 右下コントロール・ボタン を 公式デモっぽくする

reveal.js を 使って、Markdown で プレゼンテーション・スライド が 作れる ようになりました.
Markdown で 書くので デザインや多様な表現はできないことは承知しているのですが、右下にあるコントロール・ボタンでデザインが気になります. というこうとで、公式のオンライン・デモのコントロール・ボタンっぽくしてみたいと思います.
※ 以降「公式のオンライン・デモ」を「公式デモ」と表記します (ソースに demo.html があるので、ここまではオンラインをつけてましたが長いので

作業環境

  • Windows 10 64bit
  • reveal.js 3.5.0

デザインの違い と 作りたいものの確認

現在の状況について確認します.

まずは、自分で作成したスライドです. 右下のコントロール・ボタンは [ ▶ ] の ような形です.

続いて、公式デモのスライドです. 右下のコントロール・ボタンは [ > ] の ような形です.

この [ ▶ ]を、公式デモの [ > ] に したいというものになります.
あとは、最初のスライドと、下のスライドがある際に、コントロール・ボタンが少し動きます. スライドに動きがあると気になってしまうというのもありますが、ナビゲーションの観点から動いてもいいのかなぁと思います.

今回は、以下の3つを作ります

  • 右下のコントロール・ボタンを [ > ] の デザインにする
  • 最初のスライドはコントロール・ボタンがわずかに動く
  • 下のスライドがある場合は、コントロール・ボタン の [ V ] と [ > ] を わずかに動く

公式デモ の デザイン を 持ってくる

コントロール・ボタンのデザインについて、実は公式デモと配布版では CSS が 異なっています.
特に CONTROLS 以下は完全に違う状態で、これがデザインの [ ▶ ] と [ > ] の 違いになっています.

ということで、公式デモの CONTROLS 部分 の CSS を、自分のスライドの HTML <link> タグの下へ コピーします.

1
<style>
2
.reveal .controls {
3
  display: none;
4
  position: absolute;
5
  top: auto;
6
  bottom: 12px;
7
  right: 12px;
8
  left: auto;
9
  z-index: 1;
10
  color: #000;
11
  pointer-events: none;
12
  font-size: 10px; }
13
  .reveal .controls button {
14
    position: absolute;
15
    padding: 0;
16
    background-color: transparent;
17
    border: 0;
18
    outline: 0;
19
    cursor: pointer;
20
    color: currentColor;
21
    -webkit-transform: scale(0.9999);
22
            transform: scale(0.9999);
23
    transition: color 0.2s ease, opacity 0.2s ease, -webkit-transform 0.2s ease;
24
    transition: color 0.2s ease, opacity 0.2s ease, transform 0.2s ease;
25
    z-index: 2;
26
    pointer-events: auto;
27
    font-size: inherit;
28
    visibility: hidden;
29
    opacity: 0;
30
    -webkit-appearance: none;
31
    -webkit-tap-highlight-color: transparent; }
32
  .reveal .controls .controls-arrow:before,
33
  .reveal .controls .controls-arrow:after {
34
    content: '';
35
    position: absolute;
36
    top: 0;
37
    left: 0;
38
    width: 2.6em;
39
    height: 0.5em;
40
    border-radius: 0.25em;
41
    background-color: currentColor;
42
    transition: all 0.15s ease, background-color 0.8s ease;
43
    -webkit-transform-origin: 0.2em 50%;
44
            transform-origin: 0.2em 50%;
45
    will-change: transform; }
46
  .reveal .controls .controls-arrow {
47
    position: relative;
48
    width: 3.6em;
49
    height: 3.6em; }
50
    .reveal .controls .controls-arrow:before {
51
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
52
              transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
53
    .reveal .controls .controls-arrow:after {
54
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
55
              transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
56
    .reveal .controls .controls-arrow:hover:before {
57
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(40deg);
58
              transform: translateX(0.5em) translateY(1.55em) rotate(40deg); }
59
    .reveal .controls .controls-arrow:hover:after {
60
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-40deg);
61
              transform: translateX(0.5em) translateY(1.55em) rotate(-40deg); }
62
    .reveal .controls .controls-arrow:active:before {
63
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(36deg);
64
              transform: translateX(0.5em) translateY(1.55em) rotate(36deg); }
65
    .reveal .controls .controls-arrow:active:after {
66
      -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-36deg);
67
              transform: translateX(0.5em) translateY(1.55em) rotate(-36deg); }
68
  .reveal .controls .navigate-left {
69
    right: 6.4em;
70
    bottom: 3.2em;
71
    -webkit-transform: translateX(-10px);
72
            transform: translateX(-10px); }
73
  .reveal .controls .navigate-right {
74
    right: 0;
75
    bottom: 3.2em;
76
    -webkit-transform: translateX(10px);
77
            transform: translateX(10px); }
78
    .reveal .controls .navigate-right .controls-arrow {
79
      -webkit-transform: rotate(180deg);
80
              transform: rotate(180deg); }
81
    .reveal .controls .navigate-right.highlight {
82
      -webkit-animation: bounce-right 2s 50 both ease-out;
83
              animation: bounce-right 2s 50 both ease-out; }
84
  .reveal .controls .navigate-up {
85
    right: 3.2em;
86
    bottom: 6.4em;
87
    -webkit-transform: translateY(-10px);
88
            transform: translateY(-10px); }
89
    .reveal .controls .navigate-up .controls-arrow {
90
      -webkit-transform: rotate(90deg);
91
              transform: rotate(90deg); }
92
  .reveal .controls .navigate-down {
93
    right: 3.2em;
94
    bottom: 0;
95
    -webkit-transform: translateY(10px);
96
            transform: translateY(10px); }
97
    .reveal .controls .navigate-down .controls-arrow {
98
      -webkit-transform: rotate(-90deg);
99
              transform: rotate(-90deg); }
100
    .reveal .controls .navigate-down.highlight {
101
      -webkit-animation: bounce-down 2s 50 both ease-out;
102
              animation: bounce-down 2s 50 both ease-out; }
103
  .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled,
104
  .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled {
105
    opacity: 0.3; }
106
    .reveal .controls[data-controls-back-arrows="faded"] .navigate-left.enabled:hover,
107
    .reveal .controls[data-controls-back-arrows="faded"] .navigate-up.enabled:hover {
108
      opacity: 1; }
109
  .reveal .controls[data-controls-back-arrows="hidden"] .navigate-left.enabled,
110
  .reveal .controls[data-controls-back-arrows="hidden"] .navigate-up.enabled {
111
    opacity: 0;
112
    visibility: hidden; }
113
  .reveal .controls .enabled {
114
    visibility: visible;
115
    opacity: 0.9;
116
    cursor: pointer;
117
    -webkit-transform: none;
118
            transform: none; }
119
  .reveal .controls .enabled.fragmented {
120
    opacity: 0.5; }
121
  .reveal .controls .enabled:hover,
122
  .reveal .controls .enabled.fragmented:hover {
123
    opacity: 1; }
124
125
.reveal:not(.has-vertical-slides) .controls .navigate-left {
126
  bottom: 1.4em;
127
  right: 6.4em; }
128
129
.reveal:not(.has-vertical-slides) .controls .navigate-right {
130
  bottom: 1.4em;
131
  right: 1.4em; }
132
133
.reveal:not(.has-horizontal-slides) .controls .navigate-up {
134
  right: 1.4em;
135
  bottom: 6.4em; }
136
137
.reveal:not(.has-horizontal-slides) .controls .navigate-down {
138
  right: 1.4em;
139
  bottom: 1.4em; }
140
141
.reveal.has-dark-background .controls {
142
  color: #fff; }
143
144
.reveal.has-light-background .controls {
145
  color: #000; }
146
147
.reveal.no-hover .controls .controls-arrow:hover:before,
148
.reveal.no-hover .controls .controls-arrow:active:before {
149
  -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(45deg);
150
          transform: translateX(0.5em) translateY(1.55em) rotate(45deg); }
151
152
.reveal.no-hover .controls .controls-arrow:hover:after,
153
.reveal.no-hover .controls .controls-arrow:active:after {
154
  -webkit-transform: translateX(0.5em) translateY(1.55em) rotate(-45deg);
155
          transform: translateX(0.5em) translateY(1.55em) rotate(-45deg); }
156
157
@media screen and (min-width: 500px) {
158
  .reveal .controls[data-controls-layout="edges"] {
159
    top: 0;
160
    right: 0;
161
    bottom: 0;
162
    left: 0; }
163
  .reveal .controls[data-controls-layout="edges"] .navigate-left,
164
  .reveal .controls[data-controls-layout="edges"] .navigate-right,
165
  .reveal .controls[data-controls-layout="edges"] .navigate-up,
166
  .reveal .controls[data-controls-layout="edges"] .navigate-down {
167
    bottom: auto;
168
    right: auto; }
169
  .reveal .controls[data-controls-layout="edges"] .navigate-left {
170
    top: 50%;
171
    left: 8px; }
172
  .reveal .controls[data-controls-layout="edges"] .navigate-right {
173
    top: 50%;
174
    right: 8px; }
175
  .reveal .controls[data-controls-layout="edges"] .navigate-up {
176
    top: 8px;
177
    left: 50%; }
178
  .reveal .controls[data-controls-layout="edges"] .navigate-down {
179
    bottom: 8px;
180
    left: 50%; } }
181
</style>

スタイルを適用する要素を追加する

CSS を 持ってきただけでは、コントロール・ボタンは変化しません.
実は(実はが多い…)、コピーしたスタイル controls-arrow を 適用するためのタグが必要です. これも公式デモと JavaScript が 異なっている点になります.

さすがに reveal.js 本体を変更したくないですし、CDN からとってこれないのも困ります.
なのでイベント・フックを使って HTML が 生成された後に、必要となる要素を追加するようにしました.
以下のコードを HTML の <script> タグ にある、 Reveal.initialize({}); の 下に追加します.

1
<script>
2
Reveal.addEventListener('ready', (event) => {
3
    document.querySelector('aside.controls').dataset.controlsBackArrows = 'faded';
4
    Array.from(document.querySelectorAll('aside.controls > button'), e => {
5
        let child = document.createElement('div');
6
        child.setAttribute('class', 'controls-arrow');
7
        e.appendChild(child);
8
        e.style.color = 'rgb(66, 175, 250)';
9
    });
10
});
11
</script>

reveal.js で コンテンツの準備ができた ready イベントで、以下の処理をしています.

querySelector('aside.controls') で、コントロール・ボタンを保持しているコンテナの要素取得します.
データセット controlsBackArrowsfaded を 設定し、戻るボタン(左と上) に ボタンが薄くなるようなエフェクトをつけます.

querySelectorAll('aside.controls > button') で、すべてのコントロール・ボタンを取得します.
各ボタンの子要素に <div class="controls-arrow"></div> タグをを追加、このタグが先の CSS の 適用先になります.
そして、ボタンのスタイルに rgb(66, 175, 250) で カラーを設定します. ボタンの青のカラーが、上記 CSS の コピーでは上手く持ってこれないので、スタイル属性で適用しました.

これで、コントロール・ボタンのスタイルが公式デモのように [ > ] に なりました!
が、よくよくスライドを進めていくと、下スライドがある時に配置がずれてる…

配置を直し、ボタンを小さくする

どうも、元の [ ▶ ] の時に配置された CSS が 残っているようで、コピーしただけでは上書きできなかったようです.
再度 HTML に <style> を 追加して大きさと配置を修正します.

1
<style>
2
  .reveal .controls .controls-arrow:before, .reveal .controls .controls-arrow:after { width: 1.4em; }
3
  .reveal .controls .navigate-left,  .reveal:not(.has-vertical-slides) .controls .navigate-left  { top: 60px; left: 40px; }
4
  .reveal .controls .navigate-right, .reveal:not(.has-vertical-slides) .controls .navigate-right { top: 60px; left: 60px; }
5
  .reveal .controls .navigate-up,    .reveal:not(.has-vertical-slides) .controls .navigate-up    { top: 50px; left: 50px; }
6
  .reveal .controls .navigate-down,  .reveal:not(.has-vertical-slides) .controls .navigate-down  { top: 70px; left: 50px; }
7
</style>

.controls-arrow:after { width: 1.4em; } が ボタンの大きさになります.
その下の4つが、配置です. 大きさに合わせて配置を揃えていきます.

コンパクトになり、配置もそろいました!

最初のスライド と 下があるスライド の 場合は、少し動いてナビゲーションする

最初のスライドはよいとしても、メインのコンテンツ以外で動くものがあるのは、如何なものかとは思います. とはいえ縦方向にもスライドができるというは、あまりないのかなと思うと、ナビゲーションのために少し動いてもいいのかなと思い、公式デモから持ってきました.

まずは、少し動くアニメーションのスタイルです. 下矢印が動いて、戻るときに右矢印が動きます.
各%の数値を同じにすると、同時に動きます.

1
<style>
2
  @keyframes bounce-right {
3
    0%, 35%, 50%, 65%, 75% { transform: translateX(0); }
4
   45% { transform: translateX(2px); }
5
   55% { transform: translateX(-1px); } }
6
  @keyframes bounce-down {
7
    0%, 10%, 25%, 40%, 50% { transform: translateY(2px); }
8
   20% { transform: translateY(2px); }
9
   30% { transform: translateY(2px); } }
10
</style>

続いて、最初のスライド と 下があるスライド の 際にアニメーションさせる JavaScript を 作ります.
Reveal.addEventListener('ready', (event) => {}) は 上記ボタンの子要素を追加したときに記述した部分へ if(){} を 追記します.

1
<script>
2
    Reveal.addEventListener('ready', (event) => {
3
        if (Reveal.availableRoutes().right) { document.querySelector('.navigate-right').classList.add('highlight'); }
4
        if (Reveal.availableRoutes().down) { document.querySelector('.navigate-down').classList.add('highlight'); }
5
    });
6
7
    Reveal.addEventListener('slidechanged', (event) => {
8
        Array.from(document.querySelectorAll('aside.controls > button.highlight'), e => { e.classList.remove('highlight') });
9
10
        if (event.indexh === 0 && event.indexv ===0) {
11
          document.querySelector('.navigate-right').classList.add('highlight');
12
        }
13
14
        if (Reveal.availableRoutes().down) {
15
            document.querySelector('.navigate-down').classList.add('highlight');
16
            if (Reveal.availableRoutes().right) {
17
                document.querySelector('.navigate-right').classList.add('highlight');
18
            }
19
        }
20
    });
21
</script>

Reveal.addEventListener('ready', (event) => {}); で reveal.js の 描画が終わった後に、右矢印 と 下矢印 の それぞれが有効な場合に highlight の CSS クラスを適用します. Reveal.availableRoutes() で 指定する方向のスライドがあるかを判定できます.

Reveal.addEventListener('slidechanged', (event) => {}); では、スライドが変更された際のイベントで処理ができます.
まずは highlight を すべて解除します.

続いて indexhindexv0 、つまり最初のスライドの場合に、右矢印に highlight を 設定.

そして、下方向がある場合に下矢印に highlight し、さらに右方向がある場合は右矢印にも highlight を 設定します.

これで、完成になります.
キャプチャは、動きが小さすぎて変化が分かるのが取れませんでした.


マイクロソフト伝説マネジャーの 世界№1プレゼン術

日本マイクロソフト株式会社 の 澤さん の プレゼンに関する書籍です. マイクロソフトテクノロジーセンター の センター長 を 勤め、マイクロソフト社のエバンジェリストを率いられています.
こちらの書籍は、プレゼンのためのテクニックというよりも、なぜプレゼンをするのか、なぜプレゼンが必要なのかという点から書かれています. ここでの「プレゼン」は 大人数を前にしたようなプレゼンではなく、営業だとか、自分の考えを伝えるためだとかにも当てはまり、ビジネス・スキルとしても役立ちます.
プレゼンを作る際にはいつも読み返す、基本の立ち位置として重宝しています.

プレゼンの極意はマンガに学べ

漫画家の三田紀房さん の 書籍です. ドラゴン桜エンゼルバンク などが有名ですが、ビジネス関係の書籍も出されています.
こちらは、その中でもプレゼンに関する書籍で、プレゼンを一つの物語として考えてみようというものになります. 一般的なプレゼンの書籍とは一線を画すので、好みが分かれるところだと思います. ご自身の漫画を例に、どのようにスライドを作るのかについて書かれています.
プレゼンだけではなく、アイデアの練り方などにもふれているのがよく、たまに読み返しては様々なことに思いを巡らせたりしています.


デザイン・センスとか、スキルがないのに、どうしてもスライドのデザインとか気になってしまうんですよね… 絵が描けたりする人がうらやましいです.
今回は公式デモを真似するので、CSS を 上手く当てはめることでできましたが、JS や CSS 本体にデモ用の改変が入っていたので、HTML 側だけで合わせるのが難しかったです. 上手くできてよかった.