ポートフォリオを暗号化対応する

#暗号化しました
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ポートフォリオを暗号化対応させました <a href="https://t.co/IlssJZjNE6">pic.twitter.com/IlssJZjNE6</a></p>&mdash; defu@夜だけ個人開発 (@defunty_jp) <a href="https://twitter.com/defunty_jp/status/1112382206121381888?ref_src=twsrc%5Etfw">2019年3月31日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


[暗号化したポートフォリオサイト](https://defunty.dev/)

#概要
実務でasync/awaitを利用する機会がなかったので、勉強がてら上記処理を実装しました。
私自身はフロントエンドに明るくなく、 **あんぐらぁ**・**れぁくと**・**ぶゅー**についても詳しくないため、生JavaScriptで記述しています。

可読性についてはあまり意識していません。
こう書いた方がいいよ！という意見があれば参考にさせて頂きますので、お気軽にコメントください。:grinning::grinning::grinning:

ポートフォリオのソースコードはgithubに公開しています。
https://github.com/defunty/portfolio

#コンテンツを一つずつ非同期に消す

```index.js
//eleArrのi番目のelementのclassを変更し、delayミリ秒待機する
function eraseElements(eleArr, i, delay) {
  let text = '';
  return new Promise(resolve => {
    if(eleArr.length > i){
      //is--changedクラスを付与して透明化させる（別途CSSで記述）
      eleArr[i].classList.add('is--changed');
      //消去した要素の文字列
      text += eleArr[i].textContent
      if(eleArr[i].className.indexOf('is--last-content') != -1){
        parentSearch(eleArr[i],'js--default-section')
      }
      //消した要素のtextを返す
      setTimeout(() => resolve(text), delay);

      //baseEleの親要素の中から一番近いtargetClassの要素を探索する
      function parentSearch(baseEle, targetClass) {
        let parents = [];
        //const parentElem = function (ele) {
        let parentElem = ele => {
          if (ele.parentNode) {
            if(ele.parentNode.className){
              if(ele.parentNode.className.indexOf(targetClass) != -1){
                //ele.parentNode.style.display = 'none';
                setTimeout(() => {ele.parentNode.style.display = 'none';}, delay + 100)
          }
            }
            parents.push(ele.parentNode);
            parentElem(ele.parentNode);
          }
        };
        parents['prevObject'] = baseEle;
        parentElem(baseEle);
      };
    }
  });
}
```

####各elementを非同期処理で取り扱う
`eraseElements()`では引数で指定された配列（複数のelement）の`i`番目の配列のclassを変更し、`delay`ミリ秒だけ待機します。この待機時間により、一瞬で全てのelementが消えるのを防ぎ、上から順々に消えるようにしています。
for文のループごとに`setTimeout()`を利用しても`delay`秒後に一瞬で全てのelementが消えるだけなので、Promiseを使って非同期処理を実現しています。

最終的に消した要素のtextContentをまとめて返すようにしています。
（これは暗号化直前の文字列として利用します）

#### 任意のclassを持つ親（子孫）要素を探索する
上からelementを`dispaly:none`にしていくと、そのたびに残っているelementのレイアウトが詰められていくのは挙動として不自然になってしまいます。
そのような不自然さを回避するため、上からelementを`opacity: 0`にして、各種コンテンツを囲むコンテナの要素が全て`opacity: 0`にされたタイミングでコンテナを`dispaly:none`とするようにしています。

上記処理を実現するため調査したところ、JavaScriptでは任意のelementから子孫elementを条件付き検索するような関数は用意されてなさそうでしたので、`parentSearch()`という再帰的に親elementを取得する関数を用意しました。
引数の`baseEle`が探索を開始するelementで、`targetClass`が探索目標のclass名です。
この処理で、一番最後のelement(`.is--last-content`)を`opacity: 0`にしたあとにその親element(`.js--default-section`)を`dispaly:none`にします。

子孫要素を探索する処理については下記記事を参考にさせて頂きました。
参考：[jQuery の parents() を JavaScript オンリーでやる - Qiita](https://qiita.com/naoki_koreeda/items/4e4d92ebf9c0996b0b3e)

`.is--changed`に関するスタイリングは以下のように設定しています。

```index.scss
.c--default{
  transition: 1.0s all;
  &.is--changed{
    opacity: 0;
    transform: translateY(200px);
  }
}
/*
cssの場合
.c--default{
  transition: 1.0s all;
}
.c--default.is--changed{
  opacity: 0;
  transform: translateY(200px);
}
*/
```



#消したコンテンツの文字列を一箇所にまとめる

```index.js
async function insertText() {
  let eleDefault = document.getElementsByClassName('js--default');
  let text = '';
  for(let i=0;i<eleDefault.length;i++){
    //eleDefaultの内部要素を消して待機する
    text += await eraseElements(eleDefault, i, 400);
    //最後のforループでの処理
    if(i === eleDefault.length - 1){
      let ele = document.getElementById('js--bcrypt-container');
      let html = '';
      for(let j=0;j<text.length;j++){
        html += '<span class="js--char">' + text[j] + '</span>'
      }
      ele.innerHTML = html;
      ele.classList.add('is--changed');

      return ele;
    }
  }
}
```

#### async/awaitで非同期にコンテンツを消す処理を実行する
`insertText()`は**async関数**として宣言していますので、**await式**を含むことができます。
await式の実行中はasync関数の処理を一時停止しPromiseの解決を待つので、for文を非同期に回すことができます。

このようにすることで、

```
await eraseElements(eleDefault, 0, 400);
  400ミリ秒待機
    await eraseElements(eleDefault, 1, 400);
      400ミリ秒待機
        ...
```

というような流れで処理が実行されていきます。


#### 消した要素の文字列を指定の要素へ挿入する
awaitでは消したelementの要素を返します。for文の最後に、この文字列をあらかじめ用意しておいた空のelementに挿入し、可視化します。

ここでは単に文字列を挿入するのではなく、

```index.js
for(let j=0;j<text.length;j++){
    html += '<span class="js--char">' + text[j] + '</span>';
}
//html = '<span class="js--char">あ</span><span class="js--char">い</span> ... ';
```

として、一文字ずつ別elementとして区分けしています。
（理由は後述します。）


# 文字列の変換

```index.js
async function bcryptProcess(){
  let resultEle = await insertText();
  //変換先の文字列候補
  let randomCharsData = "abcdefghijklmnopqrstuvwxyz0123456789#$&%?!@";
  let ele = resultEle.getElementsByClassName('js--char');
  for(let i=0; i<ele.length; i++){
    setTimeout(() => {
      let randomChar = randomCharsData[Math.floor(Math.random()*randomCharsData.length)];
      ele[i].textContent = randomChar;
      //それっぽく見せるために100回変換処理を行う
      for(let j=0;j<100;j++){
        let randomChar = randomCharsData[Math.floor(Math.random()*randomCharsData.length)];
        ele[i].textContent = randomChar;
      }
      
      //最後のforループでの処理
      if(i == ele.length - 1){
        let bcryptBtnEle = document.getElementById('js--bcrypt-btn');
        let contactBtnEle = document.getElementById('js--contact-btn');
        bcryptBtnEle.classList.add('is--changed');
        setTimeout(() => {
          contactBtnEle.classList.add('is--changed');
        }, 1100)
      }
      
    },500);
  }
}
```

#### 処理の流れ
`bcryptProcess()`では、単純に文字列を上からランダムで書き換えているだけです。

ここで、**「なんで`insertText()`でPromiseを返していないのに非同期処理になるの？」**
と思われた方もいるかもしれません。
**async関数** では暗黙的にPromiseを返すようになるため、Promiseを返す処理をわざわざ書かなくても**await式**で非同期処理として取り扱うことができます。便利ですね！:raised_hands:

個人的に**ズバババーッ**と文字列が書き換わるのが格好良いと思ったので、一文字につき100回の変換処理を無駄に行なっています。
もっと格好良い感じにしたいですが、デザイン力が皆無なのでおすすめの案とか教えてもらえると嬉しいです！