マジミラ2020プロコンで使用したものまとめ2（文字の装飾） 

#概要
[相生葵の Advent Calendar 2020](https://adventar.org/calendars/5480) 8日目の記事です。

[初音ミク「マジカルミライ 2020」プログラミング・コンテスト](https://magicalmirai.com/2020/procon/)に参加したので使用したもののまとめ記事です。
再生は[こちら](https://aoaoaoaoaoaoaoi.github.io/magicalmirai-procon-2020-3/)でできます。
コードは[こちら](https://github.com/aoaoaoaoaoaoaoi/magicalmirai-procon-2020-3)にあります。

#目次
1. 構造
2. 配置
2. 文字色
3. 名詞から星を出す
4. 名詞の回転と文字の拡大

##1. 構造
構造は以下のようにしました。

```html:index.html

  <body>
    <div id="contents">
      <div id="text">
        <div> <!-- phrase要素のdiv -->
          <div> <!-- word要素のdiv -->
            <span> <!-- chara要素のspan -->
            </span> 
          </div>
        </div>
      </div>
    </div>
  </body>
```
「id="text"」の div要素 の下に歌詞を作成するようにしました。
phrase（フレーズ）ごと、word（単語）ごと、一文字（chara）ごとにそれぞれ装飾や動きを入れられるように入れ子にしています。

##2. 配置
歌詞のテキストが中央にくる＋中央ぞろえ＋単語で改行しないように調整しました。

```css:index.css
#text{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translateY(-50%) translateX(-50%);
    -webkit-transform: translateY(-50%) translateX(-50%);
}

/*phrase要素*/
#text > div {
    text-align: center;
}

/*word要素*/
#text > div > div {
    display: inline-block;
}
```
### #text
ここでtextのdiv要素が画面の中央にくるように調整しています。

|  要素  |  設定値  |  意図  |
| ---- | ---- | ---- |
|  position  |  absolute  |  他の要素によって位置が変わらないようにする  |
|  top  |  50%  |  id="text"のdiv要素を上から50%の位置を起点にする  |
|  left  |  50%  |  id="text"のdiv要素を左から50%の位置を起点にする  |
|  transform  |  translateY(-50%)  |  div要素の縦幅の半分分上にする  |
|    |  translateX(-50%)  |  div要素の縦幅の半分分左にする  |

図解では、以下のページが分かりやすかったです。
[CSSで要素を上下や左右から中央寄せする7つの方法](https://www.granfairs.com/blog/staff/centering-by-css)の「7.transform」というところ

####参考
[CSSで要素を上下や左右から中央寄せする7つの方法](https://www.granfairs.com/blog/staff/centering-by-css)

### #text > div /\*phrase要素\*/
ここでテキスト（実際にはword要素のdiv）が中央ぞろえになるようにしています。
「text-align: center」の機能そのままです。

### #text > div > div /\*word要素\*/
ここでは、div要素が自動で改行されてしまうので word（単語）ごとに改行されないようにword（単語）のdiv要素をブロック要素にしています。
「display: inline-block」の機能そのままです。

##2. 文字色
文字の色については、基本的には色の指定と縁取りをしているだけです。
[TextAliveAPIで品詞が判別できた](https://developer.textalive.jp/packages/textalive-app-api/interfaces/iword.html#pos)ので、名詞の場合は色を少し変えるようにしました。

```css:index.css

body{
    color:#00ced1;
}

span{
    text-shadow: 
    2px  2px 1px #003366,
   -2px  2px 1px #003366,
    2px -2px 1px #003366,
   -2px -2px 1px #003366,
    2px  0px 1px #003366,
    0px  2px 1px #003366,
   -2px  0px 1px #003366,
    0px -2px 1px #003366;  
}

.noun_text {
    color: #40e0d0;
}
```
品詞は単語ごとなので、word要素のdivにつけるようにしています。

```javascript:index.js

let isNoun = word.pos === "N"; //名詞かどうか
if(isNoun){
  wordDiv.classList.add("noun_text");
}
```
bodyのタイプセレクタよりも「noun_text」のclassセレクタの方が優先されるので、名詞の色は「noun_text」で指定した色になります。

##3. 名詞から星を出す
星のエフェクト用のクラスを用意して、名詞の場合はクラスをつけるようにしました。

```css:index.css

.text_particle {
    position: absolute;
    left: 25%;
    color: #e6b422;
    text-shadow: none;
}

.text_particle_anim_0 {
    animation: textParticle 0.5s ease-out forwards;
}

.text_particle_anim_45 {
    animation: textParticle45 0.5s ease-out forwards;
}

.text_particle_anim_225 {
    animation: textParticle225 0.5s ease-out forwards;
}

@keyframes textParticle {
    0% {
        opacity: 0;
        font-size: 0px;
        transform: translate(0, 0%)
    }
    20% {
        opacity: 0.8;
        font-size: 30px;
        transform: translate(0, -20%)
    }
    100% {
        opacity: 0;
        font-size: 40px;
        transform: translate(0, -100%)
    }
}

@keyframes textParticle45 {
    0% {
        opacity: 0;
        font-size: 0px;
        transform: translate(0, 0%)
    }
    20% {
        opacity: 0.8;
        font-size: 30px;
        transform: translate(-20%, -20%)
    }
    100% {
        opacity: 0;
        font-size: 40px;
        transform: translate(-100%, -100%)
    }
}

@keyframes textParticle225 {
    0% {
        opacity: 0;
        font-size: 0px;
        transform: translate(0, 0%)
    }
    20% {
        opacity: 0.8;
        font-size: 30px;
        transform: translate(20%, 20%)
    }
    100% {
        opacity: 0;
        font-size: 40px;
        transform: translate(100%, 100%)
    }
}
```
```javascript:index.js

let isNoun = word.pos === "N"; //名詞かどうか
if(isNoun){
  setParticle(wordDiv, 0);
  setParticle(wordDiv, 45);
  setParticle(wordDiv, 225);
}

const setParticle = (wordDiv, animNum) =>{
  const span = document.createElement("span");
  span.innerHTML = "★";
  span.classList.add('text_particle');
  let className = 'text_particle_anim_' + animNum;
  span.classList.add(className);
  wordDiv.appendChild(span);
}
```

###エフェクトのspan要素の位置
wordのdiv要素直下に入れているので以下のようになります。

```html
  <body>
    <div id="contents">
      <div id="text">
        <div> <!-- phrase要素のdiv -->
          <div> <!-- word要素のdiv -->
            <!-- chara要素のspan -->
            <span>み</span>
            <span>く</span>
            <span>さ</span>
            <span>ん</span>
            <span class="text_particle text_particle_anim_0">★</span>
            <span class="text_particle text_particle_anim_45">★</span>
            <span class="text_particle text_particle_anim_225">★</span>
          </div>
        </div>
      </div>
    </div>
  </body>
```

###cssの共通要素
アニメーションは3種類用意しました。
3種類共通になるものを「text_particle」クラスに定義しています。

```css

.text_particle {
    position: absolute;
    left: 25%;
    color: #e6b422;
    text-shadow: none;
}
```
各パラメータについては以下です。

|  要素  |  設定値  |  意図  |
| ---- | ---- | ---- |
|  position  |  absolute  |  他の要素によって位置が変わらないようにする  |
|  left  |  25%  |  単語の半分の位置にくるようにする  |
|  color  |  #e6b422  |  黄色で上書き  |
|  text-shadow  |  none  |  縁取りなしで上書き  |

####left:25%について
エフェクトのspan要素の位置を見るとわかるように星の位置は単語要素の最後にあります。
そのまま表示すると「みくさん★」になります。
（absoluteなので、「み」に重なってもよさそうですが次の文字の位置に出ていました）
そのため、leftで位置を調節しています。

25%になったのは、調節していったら25%が単語の中央にくる位置だったからです。
「みくさん★」の位置にあるときの、「★」の位置は「left:100%」の位置で「left:0%」が「み」の位置でした。
単語の中央の「く」と「さ」の間にもっていきたいので、37%くらいの位置が中央になりそうですが謎です。

###cssのアニメーション要素
3種類ありますが、値を入れ替えただけのものなので一つだけ見ます。

```css

.text_particle_anim_0 {
    animation: textParticle 0.5s ease-out forwards;
}

@keyframes textParticle {
    0% {
        opacity: 0;
        font-size: 0px;
        transform: translate(0, 0%)
    }
    20% {
        opacity: 0.8;
        font-size: 30px;
        transform: translate(0, -20%)
    }
    100% {
        opacity: 0;
        font-size: 40px;
        transform: translate(0, -100%)
    }
}
```
各パラメータについては以下です。

####text_particle_anim_0

|  要素  |  意図  |
| ---- | ---- |
|  textParticle  |  「textParticle」アニメーションを指定  |
|  0.5s  |  時間を0.5秒で指定  |
|  ease-out  |  イージングのease-outを指定  |
|  forwards  |  終了時点の効果をそのままにする  |

####keyframes textParticle

|  要素  |  0%  |  20%  |  100%  |  意図  |
| ---- | ---- | ---- | ---- | ---- |
|  opacity  |  0  |  0.8  |  0  |  初めは透明で、早めに濃くなり、その後また薄くなっていく  |
|  font-size  |  0px  |  30px  |  40px  |  初めは見えず、早めに30pxになり、その後40pxまで徐々に大きくなる  |
|  transform  |  translate(0, 0%)  |  translate(0, -20%)  |  translate(0, -100%)  |  初めは初期の位置で、-20%の位置まではサッと上昇し、その後は少しずつ上にいく  |

設定値は値を変えながら再生して調節しているので感覚です。
残りの2つのアニメーションは「transform」を変更して、星が移動する方向を変えています。

###js

```javascript

let isNoun = word.pos === "N"; //名詞かどうか
if(isNoun){
  setParticle(wordDiv, 0);
  setParticle(wordDiv, 45);
  setParticle(wordDiv, 225);
}
```
名詞であれば、パーティクルを作成するようにしています。
1つの単語から3つのパーティクルを出すので、3回パーティクルを作成するメソッドを呼び出しています。

```javascript

const setParticle = (wordDiv, animNum) =>{
  const span = document.createElement("span");
  span.innerHTML = "★";
  span.classList.add('text_particle');
  let className = 'text_particle_anim_' + animNum;
  span.classList.add(className);
  wordDiv.appendChild(span);
}
```
このメソッドでは、星のパーティクルオブジェクトの作成を行っています。
「wordDiv」と「animNum」を引数にして、wordのdivとアニメーションの指定を外部から指定できるようにしました。

####参考
[CSS Particle Effects](https://codepen.io/z-/pen/bpxgWZ)

##4. 名詞の回転と文字の拡大

```css

#text > div > div {
    animation: showLyrics 0.1s ease-out forwards, moveLyrics 100s infinite linear;
}

@keyframes showLyrics {
    0% {
      font-size: 0px;
    }
    100% {
      font-size: 50px;
    }   
}

@keyframes moveLyrics {
    0% {
        transform: rotateZ(0deg);
    }
    50% {
        transform: rotateZ(180deg);
    }
    100% {
        transform: rotateZ(360deg);
    }
}
```
word要素のdivにアニメーションを指定しています。
ここでは、「showLyrics」と「moveLyrics」の2つを設定しています。

各パラメータについては以下の通りです。

###showLyrics
文字サイズを0～50pxにだんだん大きくしています。
アニメーションの時間は「0.1」秒と短くして、はじけるような動作を意図しました。

###moveLyrics
文字の回転をしています。
一定の速度で0度～360度まで動きます。
時間は「100」秒にし、ゆっくり回転するようにしました。