第15回 JavaScript クリックで拡大（JavaScript＋CSS）

# はじめに
- 第13回は、JavaScriptを使用してクリックによるアニメーションの拡大・縮小
- 第14回は、CSSを使用して`hover`によるアニメーションの拡大・縮小

を試しました。

第14回のCSSの`animation-name`を使用して、CSSだけでクリックしてアニメーションをしようと思いましたが、CSSにはクリックに対する疑似クラスがないことに気づき断念しました。
`activve`を使用すればクリックになるのかと思ったのですが、`active`は、要素を押し続けている状態だけ動作するものであり、クリックではなかったので、期待通りの動作ではありませんでした。

今回は、CSSに加えてJavaScriptを使用することで、`hover`ではなく、クリックによるアニメーションを試したいと思います。

# 今回実施する内容
JavaScriptの`animate`で作成するクリックによる拡大と、JavaScriptとCSSの`animation-name`で作成するクリックによる拡大を実施します。
![Scale_CSS.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/5bef331b-7e54-4c1b-d245-be4163227783.gif)

# ソースコード(Git Hub)
https://github.com/Build-Mount/JS_15_clickAnimation

# 環境
OS: Windows 11 JP (64bit)
Microsoft Edge:バージョン 123.0.2420.65 (公式ビルド) (64 ビット)

# 参考
[setTimeout()](https://developer.mozilla.org/ja/docs/Web/API/setTimeout)

# 用語

# JavaScriptの`animate`で作成するクリック拡大とJavaScriptとCSSの`animation-name`で作成するクリックによる拡大

<details><summary>clickAnimation.html</summary>

```html:clickAnimation.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Click Animation</title>
    <link rel="stylesheet" href="clickAnimation.css" type="text/css">
    <script src="clickAnimation.js" defer></script>
</head>
<body>
    <div id="scale1">拡大(javascript)はここをクリック</div>
    <div id="scale2">拡大(javascript+css)はここをクリック</div>
</body>
</html>
```
</details>

<details><summary>clickAnimation.js</summary>

```javascript:clickAnimation.js
document.getElementById("scale1").addEventListener("click", () => {
  clickScaleIn("scale1");
});

document.getElementById("scale2").addEventListener("click", () => {
  clickScaleIn2("scale2");
});

function clickScaleIn(name) {
  const animation = document.getElementById(name).animate(
    [
      //キーフレーム
      {transform: 'scale(1)'},
      {transform: 'scale(2)'}
    ],    
    {
      //タイミングオプション
      duration: 1000,
      fill: "forwards"
    }
  );
}


function clickScaleIn2(name) {
  document.getElementById(name).classList.remove("scaleIn");

  setTimeout(() => {
    document.getElementById(name).classList.add("scaleIn");
  },1);
}

```
</details>

<details><summary>clickAnimation.css</summary>

```css:clickAnimation.css
#scale1 {
  background-color:yellow;
  position: absolute;
  width: 300px;
  height: 100px;
  top: 100px;
  left: 200px;
}

#scale2 {
  background-color:red;
  position: absolute;
  width: 300px;
  height: 100px;
  top: 300px;
  left: 200px;
}

.scaleIn {
  animation-duration: 1000ms;
  animation-fill-mode: forwards;
  animation-name: scaleIn;
}

@keyframes scaleIn {
  /*キーフレーム*/
  from
  {transform:scale(1)}
  to
  {transform:scale(2)}
}

```
</details>

## clickAnimation.htmlの説明
```html
<body>
    <div id="scale1">拡大(javascript)はここをクリック</div>
    <div id="scale2">拡大(javascript+css)はここをクリック</div>
</body>
```

第13回で **拡大** として作成した内容と同じです。
１つ目の`div`は、第13回の時に作成したJavaScriptで拡大する要素です。
２つ目の`div`は、JavaScriptとCSSで拡大する要素です。

## clickAnimation.jsの説明
```javascript
document.getElementById("scale1").addEventListener("click", () => {
  clickScaleIn("scale1");
});

document.getElementById("scale2").addEventListener("click", () => {
  clickScaleIn2("scale2");
});
```

- １つ目は、htmlの１つ目の`div`要素（id: `scale1`）をクリックした時に`clickScaleIn("scale1")`を実施するイベントリスナーを追加します。

- ２つ目は、htmlの２つ目の`div`要素（id: `scale2`）をクリックした時に`clickScaleIn2("scale2")`を実施するイベントリスナーを追加します。

```javascript
function clickScaleIn(name) {
  const animation = document.getElementById(name).animate(
    [
      //キーフレーム
      {transform: 'scale(1)'},
      {transform: 'scale(2)'}
    ],    
    {
      //タイミングオプション
      duration: 1000,
      fill: "forwards"
    }
  );
}
```

第13回で作成した拡大のソースと同じです。
- htmlの１つ目の`div`要素（id: `scale1`）をクリックした時の動作を規定しています。
キーフレームは、「キーフレームの開始」と「キーフレームの終了」のscale(1)とscale(2) を設定し、「キーフレームの開始」は等倍で、「キーフレームの終了」は、２倍拡大します。
タイミングオプションは、
    - duration: 1000でアニメーションは１秒間
    - fill: "forwards"
- で、最後のアニメーションを保持であり、この例では２倍に拡大した状態が維持されます。
クリックしてアニメーション終了後に再度クリックすると、再度等倍に戻って、２倍まで拡大します。

```javascript
function clickScaleIn2(name) {
  document.getElementById(name).classList.remove("scaleIn");

  setTimeout(() => {
    document.getElementById(name).classList.add("scaleIn");
  },1);
}
```

`document.getElementById("scale2").classList.remove("scaleIn");`では、２つ目の`div`要素（id: `scale2`）から`scaleIn`のクラスを削除します。
`scaleIn`のクラスが追加されていない場合は削除されず、追加されている場合は削除されます。

クラスを追加してアニメーションが終わった後、**再度クリックしても、元の拡大前の状態に戻って拡大しなおすことはない** ため、**要素から追加のクラスを削除することで元の状態に戻します**。

また、最初にこのクラスを削除することで、**要素をクリックして拡大アニメーション中にクリックする場合、拡大中から元の状態に戻して拡大することもできます。**
この動作は、JavaScriptで、`fill: "forwards"`を設定したときと同じ動作です。


次は`setTimeout(() => {...},1);`の部分についてです。
[setTimeout()](https://developer.mozilla.org/ja/docs/Web/API/setTimeout)は、以下の通り記載されています。

> グローバルの setTimeout() メソッドは、時間切れになると関数または指定されたコードの断片を実行するタイマーを設定します。
>
> ## 構文
> ```javascript:JS
>setTimeout(code)
>setTimeout(code, delay)
>
>setTimeout(functionRef)
>setTimeout(functionRef, delay)
>setTimeout(functionRef, delay, param1)
>setTimeout(functionRef, delay, param1, param2)
>setTimeout(functionRef, delay, param1, param2, /* … ,*/ paramN)
>```
>
> ## 引数
> `functionRef`
> タイマーが満了した後に実行する関数。
> 
> `code`
> 関数の代わりに文字列を含める代替構文も許容されており、タイマーが満了したときに文字列をコンパイルして実行します。 eval() の使用にリスクがあるのと同じ理由で、この構文は推奨しません。
>
> `delay` 省略可
> 指定した関数やコードを実行する前に待つタイマーの時間をミリ秒 (1/1000 秒) 単位で指定します。この引数を省略すると値 0 を使用しますので "直ちに" 実行する、より正確に言えばできるだけ早く実行することを意味します。
>
> なお、どちらの場合も、実際の遅延が想定より長くなることがあります。後述する遅延が指定値より長い理由をご覧ください。
> 
> また、値が数値でない場合、暗黙のうちに型強制が行われ、数値に変換されることにも注意してください。これは予期しない、驚くべき結果につながる可能性があります。例として、delay の値が数値でない場合は暗黙に数値に強制されるを参照してください。
> 
> `param1`, …, `paramN` 省略可
> タイマーが満了したときに、 functionRef で指定された関数に渡す追加の引数です。

ということですので、`setTimeout(() => {...},1);`の関数記載箇所にタイマー満了後の動作を記載します。タイマー（`delay`）の値は、1msを設定します。
これによって、`scaleIn`のクラスの削除を実施した1ms後に関数を実施するようにします。
関数では、`scaleIn`のクラス追加をします。
クラスの具体的な内容は、`clickAnimation.css`に記載しますが、実施内容は、第13回で作成した拡大のソースと同じです。

`setTimeout`を使用したのは、JavaScriptは非同期プログラミングであるため、`setTimeout`を設定せずに、「クラスの削除」、「クラスの追加」を設定した場合、「クラスの削除」が動作しないまま、「クラスの追加」が発生しているような動作となり、期待通り動作しないためです。
タイマー（`delay`）の値は、最小値を1msで最小値で動作しましたが、もしうまく動作しないことがあるなら10ms程度でもぱっと見動作的には遅延は感じなかったため、少し伸ばすのはありそうです。

## clickAnimation.cssの説明

```css
.scaleIn {
  animation-duration: 1000ms;
  animation-fill-mode: forwards;
  animation-name: scaleIn;
}

@keyframes scaleIn {
  /*キーフレーム*/
  from
  {transform:scale(1)}
  to
  {transform:scale(2)}
}
```

第14回で **拡大** として作成した内容と同じです。
２つ目の`div`を1倍から2倍に1秒間で拡大します。
`animatin-fill-mode`は`forward`を設定し、アニメーション終了後は最後の状態を保持します。

# おわりに
JavaScriptの`animate`とJavaScriptとCSSの`animation-name`で、クリックした時に拡大するアニメーションを作りました。
作ってみて気づいたのですが、同じ1秒のアニメーションなのですが、JavaScriptだけの場合はちょうど１秒くらいで、JavaScriptとCSSの組み合わせでは若干早く800ms～900msくらいでアニメーションしました。
この違いは処理の問題なんだろうか？