WebDesign Dackel

【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part2

【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part2

Hatena0
Google+0
Pocket0
Feedly0

はじめに

前回はプラグイン無しでツールチップを表示するところまで進めました。
いよいよ今回から、jQueryプラグインとして機能をまとめていきたいと思います。

恐らくここが、Part1~3の中で一番ワケワカランってなるところだと思うので張り切って行きましょう!

シリーズ

  1. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part1
  2. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part2
  3. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part3

jQueryプラグインについて

jQueryのプラグインといっても、結構色々な作り方があったりします。
まずはそこら辺を理解した上で実装に入りたいと思います。

プラグインの作り方にはいろいろな方法がある

ざっと種類を分類すると、次の2つになります。

  1. インスタンスメソッドを作る
  2. クラスメソッドを作る

(JavaScriptにはクラスがあるとか無いとかは一旦忘れます…)

これじゃあ分からないよ…って方のために、jQueryが持っているAPIを使ってちょっと補足してみます。

インスタンスメソッド

ここで言うインスタンスとはjQuery関数から帰ってきた要素を指します。
言葉にするより実際のコードを見たほうが分かりやすいかもです。

var $paragraph = $("p");
// => $paragraph がjQueryのインスタンス

このインスタンスが使用できるメソッド(関数)をインスタンスメソッドと呼びます。
例えば、前回のツールチップでフェードイン・アウトを実装した際に使用したfadeIn, fadeOutがそうです。

$tooltip.fadeOut(500);

tooltipというプラグインをインスタンスメソッドとして提供する場合は、下記の様に使えるようになる、ということになります。

$(".my-tooltip").tooltip();

jQueryを使っているということは、DOM要素に対して何らかの処理をしたい場合が多いと思うので、恐らく後述するクラスメソッドよりもこちらのプラグインの方が多く公開されていると思います。

今回作成するツールチップは、要素に対して処理をしていきたいのでこちらのインスタンスメソッドを追加する方法を使います。

クラスメソッド

分かりやすいのはjQuery.ajax()かなと思います。

$.ajax(
  url: "/path/to/file.json",
  dataType: "json",
  success: (data, textStatus, jqXHR){
    // 何らかの処理
  }
);

先ほどのインスタンスメソッドとは違い、ajax()を実行する際に要素を指定していません。
jQueryそのものに対して機能を追加する、というイメージで問題ないかと思います。

要素に対して何かをするというよりも、データの操作や値の検証ロジック、といったものによく使われている気がします。

プラグイン作成時に注意する点

あくまでも僕の個人的な意見になってしまいますが、実際プラグインを作って公開しよーっとなった時に注意したい点があります。

提供する機能は一つに絞る

親切心から機能盛り沢山!なオールインワンプラグインを目指すと、使う側からすると混乱を招いてしまいます。。
実現する機能を絞って、極力シンプルなインターフェースを心がけたいものです。

他のプラグインと競合しないように意識

自分で作ったプラグインが他の機能に影響が出まくってしまっては迷惑なので、影響は極力小さい範囲になるようにします。

あと、プラグインの名前もユニークなものに!

これが意外と悩みますが、同じ名前のプラグインだと後に読み込まれた方のプラグインが実行されてしまいますし、思わぬバグにつながるのでユニークな名前をつけてあげましょう。

作成予定である.tooltip()は既に世の中に存在するのでダメな例です。笑

今回の目標

  • jQueryプラグインの作り方の基礎を理解する
  • 前回実装したツールチップをプラグインとしてまとめる
  • 最小限のオプションを渡せるようにする

今回はjQueryプラグインの作成について、なんとなく理解出来るところまで行ければいい感じです。

いよいよ本題!プラグインを作ってみよう

前置きがだいぶ長くなってしまいました…!
やっと前回実装したツールチップをプラグイン化していきます。

ファイルの準備

基本的には前回の構成を使用します。

ただ、プラグインとしてCSSJSは使いまわせるようにファイルを分割していきます。

  • ./js/app.js./js/jquery.tooltip.jsというファイル名に変更
  • ./css/jquery.tooltip.cssというファイルを新規で作成

これで下記のような構成になったかと思います。

.
├── css
│   ├── jquery.tooltip.css #←新規で作成したファイル
│   ├── normalize.css
│   └── style.css
├── index.html
└── js
    ├── jquery-1.11.3.min.js
    └── jquery.tooltip.js #←ファイル名を変更

ファイルの準備が出来ましたので、まずはHTMLCSSのファイルを変更していきます。

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>jQuery自作ツールチップのサンプル</title>
  <link rel="stylesheet" href="./css/normalize.css">
  <link rel="stylesheet" href="./css/jquery.tooltip.css">
  <link rel="stylesheet" href="./css/style.css">
</head>
<body>
  <div class="container">
    <h1>jQuery自作ツールチップのサンプル</h1>
    <p>
      <a href="#" class="my-tooltip" title="ツールチップです!">マウスを乗せるとツールチップ</a>が表示されます。<br />
      こちらの<a href="#" class="my-tooltip" title="表示されましたか?">リンク</a>テキストも同様です。
    </p>
  </div>

  <script type="text/javascript" src="./js/jquery-1.11.3.min.js"></script>
  <script type="text/javascript" src="./js/jquery.tooltip.js"></script>
  <script type="text/javascript">
  $(function(){
    $(".my-tooltip").tooltip();
  });
  </script>
</body>
</html>

基本的にはこの前と同じですが、下記の点が変わっています。

  1. ./css/jquery.tooltip.cssを読み込み
  2. ./js/jquery.tooltip.jsを読み込み
  3. jQuery.fn.tooltip()の呼び出し

自作するプラグインを呼び出していますが、まだプラグイン化していないので、この時点ではエラーが出るかと思います。

CSS

まず、./css/style.cssを変更します。

@charset "utf-8";

body,
html {
  color:#444;
  font-size:18px;
  font-family:sans-serif;
}

.container {
  width:700px;
  margin:0 auto;
  padding:30px;
}

ツールチップ用のスタイルを削除しました。
次に新しく作った./css/jquery.tooltip.cssを編集します。

@charset "utf-8";

/* ツールチップ本体 */
.tooltip {
  position:absolute;
  z-index:9999;
  display:block;
  color:#fff;
  font-size:14px;
  line-height:1.2;
}

/* ツールチップの内容 */
.tooltip__body {
  position:relative;
  top:-15px;
  padding:10px;
  background:#222;
  -webkit-border-radius:3px;
     -moz-border-radius:3px;
      -ms-border-radius:3px;
          border-radius:3px;
  -webkit-box-shadow:0 2px 4px rgba(0, 0, 0, .4);
     -moz-box-shadow:0 2px 4px rgba(0, 0, 0, .4);
      -ms-box-shadow:0 2px 4px rgba(0, 0, 0, .4);
          box-shadow:0 2px 4px rgba(0, 0, 0, .4);
}

/* ツールチップらしく矢印をつける */
.tooltip__body:after {
  content:"";
  position:absolute;
  bottom:-5px;
  left:50%;
  display:block;
  width:0;
  height:0;
  margin-left:-5px;
  border-width:5px 5px 0 5px;
  border-style:solid;
  border-color:#222 transparent transparent transparent;
}

内容は前回./css/style.cssにあったツールチップのスタイルをそのままコピペしたものです。

プラグインのファイル名について補足

既にお気づきの方もいると思いますが、CSSJSのファイル名が下記のようになっています。その理由について少し補足です。

jquery.プラグイン名.js(css)

上記のフォーマットを使用すると、ファイル名を見ただけでjQueryに依存していることが分かるため、ファイルの意図が伝わりやすくなり、読み込む順番などの予想がつきやすくなります。

また、多くのプラグインで使われる慣習でプラグイン作成時のマナーみたいなものです。
ここらへんは深く考えずこのフォーマットに沿うのが無難かと思います!

プラグインのひな形

./js/jquery.tooltip.jsのファイルを変更していきます。
以前のコードが残ったままだと思うので、一思いに全て削除して新しくコードを書いていきたいと思います。(心配な方はコメントアウトしておいて下さい)

;(function(factory){
  // AMD
  if( typeof define === "function" && define.amd ){
    define(["jquery"], factory);
  // CommonJS
  }else if( typeof exports === "object" ){
    module.exports = factory(require("jquery"));
  // Default (browser)
  }else{
    factory(jQuery);
  }

}(function($){

  // jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
  $.fn.tooltip = function(){
  };

}));

上何行かは謎のコードに見えると思います…。
少し前までは下記のようなフォーマットをよく見ました。

;(function(window, $, undefined){

  $.fn.tooltip = function(){
  };

}(window, jQuery));

こっちの方がシンプルです!!

ただ、ここで詳しくは解説しませんが、最近のモダンなフロントエンド開発ではHTML内で<script>を使ってファイルを読み込むという方法以外に、AMDCommonJSと呼ばれるモジュール管理が進んでいます。
そういった管理の中を採用している場合、上記のシンプルな方法ではせっかく作ったプラグインが読み込まれなくなるといった事象が発生します。(解決策は色々とありますが…)

なので、今からプラグインを作るなら、一番上に書いたひな形に沿って作成することをおすすめします。
詳しく解説ができなかったところに興味のある方は、AMDCommonJSBrowserifywebpackなどのキーワードをぐぐってみると知見が広がりそうです!

とりあえず、これでひな形は出来ました。
この時点で既にjQueryのインスタンスメソッドとしてtooltipが追加されています。

前回のツールチップ実装を移植する

;(function(factory){
  // AMD
  if( typeof define === "function" && define.amd ){
    define(["jquery"], factory);
  // CommonJS
  }else if( typeof exports === "object" ){
    module.exports = factory(require("jquery"));
  // Default (browser)
  }else{
    factory(jQuery);
  }

}(function($){

  // jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
  $.fn.tooltip = function(){

    // 各要素に対して処理をしていきます
    this.each(function(){

      var $body = $("body");
      var $this = $(this); //何度か使うため、変数にしまっておきます
      var title = $this.attr("title"); //要素の`title`属性を取得

      // ツールチップ本体を生成します
      var $tooltip = $([
        "<span class='tooltip'>",
          "<span class='tooltip__body'>",
            title,
          "</span>",
        "</span>"
      ].join(""));

      // デフォルトで表示されるツールチップを消す
      $this.attr("title", "");

      // 最初ツールチップは非表示にしておきます
      $tooltip.hide();

      // イベントの設定
      $this
        // マウスが乗ったら...
        .on("mouseenter", function(){

          // <body>へツールチップを追加します
          $body.append($tooltip);

          // 要素の表示位置
          var offset = $this.offset();

          // 要素のサイズ
          var size = {
            width: $this.outerWidth(),
            height: $this.outerHeight()
          };

          // ツールチップのサイズ
          var ttSize = {
            width: $tooltip.outerWidth(),
            height: $tooltip.outerHeight()
          };

          // 要素の上に横中央で配置
          $tooltip
            .css({
              top: offset.top - ttSize.height,
              left: offset.left + size.width / 2 - ttSize.width / 2
            })
            .stop(true, false)
            .fadeIn(500);
        })
        // マウスが離れたら...
        .on("mouseleave", function(){

          // <body>から、ツールチップを削除します
          $tooltip.stop(true, false).fadeOut(500, function(){
            $tooltip.remove();
          });
        });

    });
  };


}));

ほとんど前回使用したコードのコピペです。
違う点は恐らくこの2点です。

  1. 変数$bodyの宣言箇所
  2. $(".my-tooltip")だった箇所をthisに変更

1つめの変更箇所は前回のコードと見比べてみると簡単に分かると思います。
2つめの変更については少し詳しく書いていきます。

実行した要素に対して処理をする

プラグインを使用しないものだと下記のようになっていました。

$(".my-tooltip").each(function(){
  // ツールチップの処理
});

これをそのままプラグインとして持ってくると、こんな感じですよね。

}(function($){

  // jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
  $.fn.tooltip = function(){
    $(".my-tooltip").each(function(){
      // ツールチップの処理
    });
  };

}));

これをプラグインの外部から実行すると、下記の例では.your-tooltipにツールチップを出したいのに、.my-tooltipにツールチップが出てしまう、なんとも謎のプラグインが出来上がります。

$(".your-tooltip").tooltip();

これではプラグインにする意味が無いので、ちゃんと実行した要素自体に処理をする必要があります。

}(function($){

  // jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
  $.fn.tooltip = function(){
    // ここでは $(this) みたいにしなくても平気!
    this.each(function(){
      // each の中では $(this) にする必要がある
      var $this = $(this);
    });
  };

}));

最初に出てきたthisが下記の例で言うと、.your-tooltipになります。

$(".your-tooltip").tooltip();

こんな感じで色々な要素に対して同じ処理が使いまわせるようになりました!

$("a").tooltip();
$(".my-tooltip").tooltip();
$(".sample").tooltip();

メソッドチェーンを保つ

ただ、このままだとjQueryの特徴の一つであるメソッドチェーンが途切れてしまっています。

どういうことかと言うと、例えば次のようにツールチップが出るようにしてから、文字色を赤色にしたいんだよね〜という場合。

$(".my-tooltip").tooltip().css("color", "red");
// => エラーが出る!

これはtooltip()というメソッドがインスタンスを返していないことが原因になっています。
解決方法は簡単で、tooltip()メソッドがthis(実行した要素自体)を返すようにするだけです。

// jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
$.fn.tooltip = function(){
  return this.each(function(){
    // ... 省略
  });
};

eachの前にreturnを付け加えました。
これで、先ほどのエラーがでなくなりました。

$(".my-tooltip").tooltip().css("color", "red");
// => エラーが消えてちゃんと文字が赤くなる!

とりあえず最小限のオプションを渡せるようにする

ここまでで、プラグインとして普通に使えるようになりました!
ただ、このままでは

  • ツールチップを出す方向を変えたい
  • フェードアニメーションの速度をもっと早めたい

みたいになった時、いちいちプラグインの中身を変える必要があります。
そこで、設定を柔軟に変更できるようにしていきたいと思います。

早速プラグイン側のコードを編集して、フェードアニメーションの速度を変えられるようにしていきます。

// デフォルトの設定
var defaults = {
  duration: 300
};

// jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
$.fn.tooltip = function(options){
  var _options = $.extend(true, {}, defaults, options);

  // ... 省略
};

プラグインを呼び出した際に、設定を渡していない場合に使用されるデフォルトの値を用意しておきます。
それを実行時に指定した設定とマージすることで、下記の様に優先順位が付いた状態で設定を取得することが出来ます。

指定した設定 > デフォルトの設定

オプションを受け取れるようになったので、これをfadeInfadeOutそれぞれの速度に反映します。

// 要素の上に横中央で配置
$tooltip
  .css({
    top: offset.top - ttSize.height,
    left: offset.left + size.width / 2 - ttSize.width / 2
  })
  .stop(true, false)
  .fadeIn(_options.duration);
// <body>から、ツールチップを削除します
$tooltip.stop(true, false).fadeOut(_options.duration, function(){
  $tooltip.remove();
});

これで、下記のようにして外部からアニメーションの速度を自由に変えられるようになりました!

$(function(){
  $(".my-tooltip").tooltip({
    duration: 1000
  });
});

オプションの渡し方について、ざっとしか説明できていないので分からない場合は下記のサイトが参考になりそうです!

jQueryのmergeとextendとjQueryプラグイン – Qiita

最終的なコード

今回もまたバラバラと書いてしまったので、ここで全体のコードをのせておきます。

;(function(factory){
  // AMD
  if( typeof define === "function" && define.amd ){
    define(["jquery"], factory);
  // CommonJS
  }else if( typeof exports === "object" ){
    module.exports = factory(require("jquery"));
  // Default (browser)
  }else{
    factory(jQuery);
  }

}(function($){

  var defaults = {
    duration: 300
  };

  // jQueryのインスタンスメソッドに "tooltip" というプラグインを登録する
  $.fn.tooltip = function(options){

    var _options = $.extend(true, {}, defaults, options);

    // 各要素に対して処理をしていきます
    return this.each(function(){

      var $body = $("body");
      var $this = $(this); //何度か使うため、変数にしまっておきます
      var title = $this.attr("title"); //要素の`title`属性を取得

      // ツールチップ本体を生成します
      var $tooltip = $([
        "<span class='tooltip'>",
          "<span class='tooltip__body'>",
            title,
          "</span>",
        "</span>"
      ].join(""));

      // デフォルトで表示されるツールチップを消す
      $this.attr("title", "");

      // 最初ツールチップは非表示にしておきます
      $tooltip.hide();

      // イベントの設定
      $this
        // マウスが乗ったら...
        .on("mouseenter", function(){

          // <body>へツールチップを追加します
          $body.append($tooltip);

          // 要素の表示位置
          var offset = $this.offset();

          // 要素のサイズ
          var size = {
            width: $this.outerWidth(),
            height: $this.outerHeight()
          };

          // ツールチップのサイズ
          var ttSize = {
            width: $tooltip.outerWidth(),
            height: $tooltip.outerHeight()
          };

          // 要素の上に横中央で配置
          $tooltip
            .css({
              top: offset.top - ttSize.height,
              left: offset.left + size.width / 2 - ttSize.width / 2
            })
            .stop(true, false)
            .fadeIn(_options.duration);
        })
        // マウスが離れたら...
        .on("mouseleave", function(){

          // <body>から、ツールチップを削除します
          $tooltip.stop(true, false).fadeOut(_options.duration, function(){
            $tooltip.remove();
          });
        });

    });
  };

}));

まとめ

長々と書いてしまいました…。

次回は、プラグインらしくより実践的に使えるようになるためのカスタマイズ(応用編)の予定です。
今回の内容がしっかりと理解できていれば難しくは無いと思うので、ぜひ実際にコードを書いてトライ&エラーを繰り返しながら理解を深めてみて下さい。

  1. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part1
  2. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part2
  3. 【初心者向け】jQueryでツールチップのプラグインを自作しよう – Part3