maphilight.jsに点滅機能を追加する方法

「[maphilight.js](https://github.com/kemayo/maphilight)」は、クリッカブルマップのエリア要素をハイライトできる便利なライブラリです。

基本的な使い方をお探しの方は、以下を参考にして下さい。
[クリッカブル・マップ（usermap）を視覚化する - りきえる’ｓ ブログ](https://blog.cror.net/jquery-maphilight.html)

## 課題
このライブラリ、残念ながらエリアやアウトラインを点滅させることまでは出来ません。
Githubのissuesでも要望は出ていますが、対応する気配は今のところありません:sob:

## 結論
点滅できました:metal:

[![Image from Gyazo](https://i.gyazo.com/365ccf14eba5b9bdac3aa27c7987e8f8.gif)](https://gyazo.com/365ccf14eba5b9bdac3aa27c7987e8f8)

## 方法
・maphilightのopacityオプションの値をsetIntervalで増減させる。
・値の増加と減少の切り替えには、排他制御を利用する。

## 手順

### ①htmlファイルにscriptタグを追加

新たにjsファイルを作成します。
ファイル名は、「jquery.maphilight-custom.js」としました。
maphilight.jsの下に配置しましょう。

````html:index.html
<head>
  ...
  <script src="./src/js/jquery.maphilight.min.js"></script>
  <script src="./src/js/jquery.maphilight-custom.js"></script>
</head>
````

### ②カスタマイズファイルを作成

新規作成した「jquery.maphilight-custom.js」に以下をコピペして下さい。
個別の解説はあとでしていきます。

````js:jquery.maphilight-custom.js
// maphilightの設定
let options = {
  fill: true, // エリアを塗りつぶす
  fillColor: '000000', // 塗りつぶすカラーコード
  fillOpacity: 1, // 塗りつぶしの透過度(0=透明、1=完全な塗りつぶし)
  stroke: true, // エリアを枠線で囲む(true)
  strokeColor: 'ff0000', // 枠線のカラーコード
  strokeOpacity: 1, // 枠線の透過度
  strokeWidth: 1, // 枠線の太さ
  fade: true,
  alwaysOn: true, // エリアの表示タイミング(true=常に表示、false=オンマウス時のみ表示)
  neverOn: false,
  groupBy: false,
  wrapClass: true,
  shadow: false,
  shadowX: 0,
  shadowY: 0,
  shadowRadius: 6,
  shadowColor: '000000',
  shadowOpacity: 0.8,
  shadowPosition: 'outside',
  shadowFrom: false,

  // 独自に実装したオプション
  fillBlink: false, // エリアを点滅させる（true=点滅させる、false=点滅させない）
  strokeBlink: true, // 枠線を点滅させる（true=点滅させる、false=点滅させない）
  blinkInterval: 1000 // 点滅させる間隔（単位はms）
}

$(function () {
  $('img[usemap]').maphilight(options)

  if (options.strokeBlink || options.fillBlink) {
    const interval = options.blinkInterval / 10
    setInterval(function () {
      blinking()
    }, interval)
  }
})

let mutex = 0

const fill = {
  limit: options.fillOpacity,
  value: function () {
    return this.limit / (options.blinkInterval / 100)
  },
  up: 0,
  down: 0
}

const stroke = {
  limit: options.strokeOpacity,
  value: function () {
    return this.limit / (options.blinkInterval / 100)
  },
  up: 0,
  down: 0
}

function blinking() {
  if (mutex == 0) {
    const data = $('.hilight').mouseout().data('maphilight') || {}
    if (options.strokeBlink) data.strokeOpacity = stroke.up
    if (options.fillBlink) data.fillOpacity = fill.up
    $('.hilight').data('maphilight', data).trigger('alwaysOn.maphilight')
    fill.up += fill.value()
    stroke.up += stroke.value()
    if (
      (options.fillBlink && fill.up >= fill.limit) ||
      (options.strokeBlink && stroke.up >= stroke.limit)
    ) {
      mutex = 1
      fill.down = fill.limit
      stroke.down = stroke.limit
    }
  } else {
    const data = $('.hilight').mouseout().data('maphilight') || {}
    if (options.fillBlink) data.fillOpacity = fill.down
    if (options.strokeBlink) data.strokeOpacity = stroke.down
    $('.hilight').data('maphilight', data).trigger('alwaysOn.maphilight')
    fill.down -= fill.value()
    stroke.down -= stroke.value()
    if (
      (options.fillBlink && fill.down <= 0) ||
      (options.strokeBlink && stroke.down <= 0)
    ) {
      mutex = 0
      fill.up = 0
      stroke.up = 0
    }
  }
}
````

### ③利用方法
以下の３つのオプションを調整してご利用下さい。

````js
  // 独自に実装したオプション
  fillBlink: false, // エリアを点滅させる（true=点滅させる、false=点滅させない）
  strokeBlink: true, // 枠線を点滅させる（true=点滅させる、false=点滅させない）
  blinkInterval: 1000 // 点滅させる間隔（単位はms）
````

お疲れさまでしたー:grin:

## 個別解説（興味があればでOK）

### DOM生成後の処理
DOM生成後にmaphilight.js本体を発火させ、インターバル制御を開始させています。

````js
$(function () {
  $('img[usemap]').maphilight(options)  // maphilight.jsを発火させています

  // 点滅オプションのどちらかがtrueのとき、関数blinkingが動作します
  if (options.strokeBlink || options.fillBlink) {
    // 点滅オプションで設定した時間を10分割して、徐々に透明度を増減させます
    const interval = options.blinkInterval / 10
    // setIntervalメソッドで関数blinkingを繰り返し実行しています
    setInterval(function () {
      blinking()
    }, interval)
  }
})
````

記述方法の補足です。

````js
$(function () {...}) // この書き方は
$("document").ready(function () {...}) // これの略です
````

### パラメータの定義

エリアと枠線それぞれの、透明度の上限値と増減値を定義しています。

````js

// エリアの点滅に必要な値をまとめたオブジェクトを作成します
const fill = {
  // 透明度の上限値です
  limit: options.fillOpacity,
  // 透明度の増減値です
  value: function () {
    return this.limit / (options.blinkInterval / 100)
  },
  // 増加中の透明度を記録します
  up: 0,
  // 減少中の透明度を記録します
  down: 0
}

// アウトラインの点滅に必要な値をまとめたオブジェクトを作成します
const stroke = {
  limit: options.strokeOpacity,
  value: function () {
    return this.limit / (options.blinkInterval / 100)
  },
  up: 0,
  down: 0
}
````

this.limitを使用しているvalueプロパティですが、プリミティブ値ではなく関数を使用しないと
thisの参照先がグローバルオブジェクトとなり、undefinedになってしまうので注意して下さい。

````js
const fill = {
  ...
  // OK
  value: function () {
    return this.limit / (options.blinkInterval / 100) // => 例）0.1
  },

  // NG
  value: this.limit / (options.blinkInterval / 100), // => undefined
  ...
}
````

### 排他制御によるパラメータの増減処理

今回、透明度を増加させるか減少させるかを排他制御で解決しています。
排他制御（mutex・ミューテックス）というのは、「ダブルブッキングしないように制御すること」です。
今回は、「増加という処理」と「減少という処理」がダブルブッキングしないように制御しています。
具体的には、変数mutexが0のときは増加、1のときは減少させています。

参照：　[排他制御とは｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word11316.html)


````js
// mutexが0なら透明度を増加させ、1なら減少させます
let mutex = 0
...
function blinking() {
  if (mutex == 0) {
    ... // 透明度を増加させる処理
    if (
      // 透明度が上限に達した場合、mutexを1にして減少させる処理を開始します
      (options.fillBlink && fill.up >= fill.limit) ||
      (options.strokeBlink && stroke.up >= stroke.limit)
    ) {
      mutex = 1
      ...
    }
  } else {
    ... // 透明度を減少させる処理
    if (
      // 透明度が0に達した場合、mutexを0にして増加させる処理を開始します
      (options.fillBlink && fill.down <= 0) ||
      (options.strokeBlink && stroke.down <= 0)
    ) {
      mutex = 0
      ...
    }
  }
...


````

ありがとうございました！以上です。