こんなときどうする？Intro.js設定・Tips集

# まえがき
Webサイトのチュートリアルを表示するのにIntro.jsを結構重宝しているのですが、割りと使い方に悩んだところがいっぱいあって、仕様面のドキュメントが乏しいのでIntro.jsのサイトだけだとわからないことがいっぱいありました。
ソースコード読んでみたら結構いろんなことがわかったので共有しようと思います。

# 使い方

```.html
<link href="introjs.css" rel="stylesheet">
```

```.html
<script type="text/javascript" src="intro.js"></script>
```
CSSとJSを読んで

```.html
<h1 data-intro="これは見出しです" data-step="1">見出し</h1>
```
ハイライトしたい要素にコンテンツを設定して

```.html
<script type="text/javascript">
 (function(){  
    introJs().start();
  })();
</script>
```

開始。

# コンテンツを動的に設定する(Rails)
「初めて使う人にだけ表示したい」「特定の状態にあるユーザーにだけ表示したい」という場合はHTMLにdata-introやdata-stepをベタ書きすると実現できないでRailsで制御します。

```index.html.slim
h1 data-intro="これは見出しです" data-step="1"
  |見出し
```

先ほどの見出しをSlimで書いてみました。例えば「無料ユーザーだけ表示したい」とかだったら、

```index.html.slim
h1 data-intro="#{current_user.free? ? "これは見出しです" : nil}" data-step="#{current_user.free? ? "1" : nil}"
  |見出し
```

これでいけますがさすがに収まりが悪いのでデコレータに押し込みます。active_decoratorの使用を想定しています。

```user_decorator.rb
module UserDecorator
  def data_intro(text)
    text if self.free?
  end

  def data_step(step)
    step if self.free?
  end
end
```

```index.html.slim
h1 data-intro=current_user.data_intro("これは見出しです") data-step=current_user.data_step(1)
  |見出し
```

デコレータ側は属性設定の可否だけを判断し、表示させる内容はビューに持たせることができました。

# コンテンツを動的に設定する(JS)
RailsとJS両方でコンテンツを設定するのは気がひけるのですが、「each回して設定するビューの一つだけにチュートリアルを設定したい」みたいな場合だとRailsでの処理が面倒な上に、無駄なメソッド呼び出しがかさむのでJSで設定しています。

```index.html.slim
- @users.each do |user|
  .user_table
    .user_table__name
      = user.name
    .user_table__address
      = user.address
```

```.js
$('.user_table').first()
        .attr('data-intro', 'ユーザーの個別データです')
        .attr('data-step', 2);
```

深い意図はありませんが普段使っているjQueryで。

余談ですが、「レスポンシブデザインで単一のビューを使うけれど画面サイズによって別々のコンポーネントを表示する、チュートリアルは両方の画面で表示する」なんて場合もJSでの制御が必須です。画面サイズに応じたコンポーネントにピンポイントでコンテンツを設定する必要があります。


# コンフィグを設定する
デモサイトの説明だと相当断片的ですが、setOptions()にオブジェクトを与えることでコンフィグを変更できます。
主要なものをざっくり解説します。

## nextLabel, prevLabel, skipLabel, doneLabel
表示される各ボタンのラベルです。デフォルトは英語なのでエンドユーザー向けなら設定を変えておきたいところ。

## tooltipPosition
auto, top, bottom, left, right, floating, bottom-right-aligned, bottom-middle-aligned, bottom-left-alignedが設定できます。デフォルトはbottomです。

特にスマホ画面と縦長の要素でコンボをかけると「ハイライトしている要素の重要な箇所がさっぱり見えない」ということが起こりえるため、どのオプションを使うべきかをページごと、デバイスごとにきちんと設定すべきです。PCならauto設定でもなんとかなりますがスマホでは全く見栄えに信用が置けません。

### scrollToElement
チュートリアル表示時や、「Next」, 「Prev」のボタンを押した時にハイライトした要素にスクロールするかを設定します。デフォルトだとtrueです。
これもビューしだいでは「スクロールしたら逆に見づらくなった」というパターンが起こりえるのでビューごとにチェックした方がいいです。

### showButtons, showBullets, showProgress
それぞれボタン、ページを表す点のバレット、プログレスバー、を表示するかのオプションです。
デフォルトではtrue, true, false。

### showStepNumbers
ステップ数の数字を表示するかのオプションです。デフォルトではtrue。
コンテンツが1つだけなら無いほうがよさげ。

# 画面ごとにコンフィグを出し分ける
先ほどコンフィグを個別に設定したほうがいいと言いましたが具体的な例を紹介します。

```.js
var introOption = {
        index: {
          pc: { tooltipPosition: 'auto', scrollToElement: false },
          sp: { tooltipPosition: 'top', scrollToElement: false }
        },
        show: {
          pc: { tooltipPosition: 'auto', scrollToElement: false },
          sp: { tooltipPosition: 'bottom', scrollToElement: true }
        }
```
Railsだとsprocketsでapplication.jsにJSが固められるので、複数のページで使用するオプションを定義し、現在のページを判定してからキーを設定します。

```.js
var device = window
          .matchMedia('screen and (min-width:720px)').matches ? 'pc' : 'sp';
```
画面サイズの判定をして結果に応じてオプションのキーを変えます。
この場合はあんまり綿密な区別はせずPCか否か、タブレットの個別設定は考えない、くらいのざっくりしたチェックです。

```.js
introJs().setOptions(introOption[page][device]).setOptions({
        nextLabel: '次へ &rarr;',
        prevLabel: '&larr; 戻る',
        skipLabel: '閉じる',
        doneLabel: '完了'
      });
```

実行するときはこんな感じです。割愛しましたが、現在ページを求めるときにはlocation.pathnameをRegExp.prototype.test()に食わせています。

# JSで制御する
startする以外にも結構色々制御できます。

## start, exit
IntroJSのハイライトを開始・終了します
## setOptions
オプションのオブジェクトを設定します。
単一のオプションを設定するsetOptionもあります。
## nextStep, previousStep
それぞれステップを1つ進め、1つ戻します。ボタンを押したのと同じような動作です。
## goToStep
引数で取ったステップに順番を移動させます。
ちなみに、`introJs().start().goToStep(3)`とやるとdata-step=3が表示されますが、`introJs().goToStep(3).start()`とやると**data-step=4が表示される**初見殺し仕様なので注意が必要です。

# チュートリアルの現在のステップを取得する
いくつかのやり方があります。というかこの記事を書くにあたって2つ見つけました。

- introjs-helperNumberLayerクラスのtextから取得
  - 見えてる要素から取得する力技です。下2つを知っていれば下策。
- introJs()._currentStepにて取得
  - start()したintroJsオブジェクトを変数で握っている必要があります。
- コールバック引数のelementからdata-stepを取得
  - 後述します。気づかんってこんなん。


# コールバックを設定する
onbeforechange, onchange, onafterchange, oncomplete, onhintsadded, onhintclick, onhintclose, onexitのそれぞれにコールバックの関数を設定できます。
コールバックの第一引数にハイライトされているelementが投げられるので、以下のようにすればチュートリアルを進める/戻すごとにdata-stepがコンソール出力されます。

```.js
introJs().onafterchange(function(element){ console.log(element.getAttribute("data-step"))}).start()
```

なお、コールバックは**undefined不可**です。エラーで落ちるので条件によって実行するコールバックが変わってくる時には注意が必要です。