【CSS】一文字ずつ出現するアニメーション【Animation】

# はじめに

**こういうアニメーションが作りたい**

<p class="codepen" data-height="265" data-theme-id="0" data-default-tab="html,result" data-user="m_n_r" data-slug-hash="xxKPBzw" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="xxKPBzw">
  <span>See the Pen <a href="https://codepen.io/m_n_r/pen/xxKPBzw/">
  xxKPBzw</a> by m_n_r (<a href="https://codepen.io/m_n_r">@m_n_r</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

<p class="codepen" data-height="498" data-theme-id="0" data-default-tab="html,result" data-user="m_n_r" data-slug-hash="NWKwJOL" style="height: 498px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="NWKwJOL">
  <span>See the Pen <a href="https://codepen.io/m_n_r/pen/NWKwJOL/">
  NWKwJOL</a> by m_n_r (<a href="https://codepen.io/m_n_r">@m_n_r</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 環境
Sass（Sass 記法）
[Compass](https://qiita.com/AsagiriDesign/items/db12237796d1e7c9e632)

# ■ のうごき
白い画面で ■ が現れて、 ■ が消えて、文字が現れる
一文字につき、一つの ■ がある

# 一文字につき、一つの ■ をつくる

## HTML
```html
<div class="fade-in-word__vertical">
	<span>W</span><span>E</span><span>L</span><span>C</span><span>O</span><span>M</span><span>E</span>
</div>
```

一文字につき、一つの ■ をつけるために `span` で囲います

```html
<span><span></span>W</span>
```

■ と文字を連動させるために、`span` の中に ■ を入れる必要があります
今回は疑似要素で対応できるので、中の `span` はいりません

```html
<span>W</span>
```

## 文字を表示する SASS

```sass
.fade-in-word
    font-size: 10px
    &__vertical
        font-size: 4em
        line-height: 1.1em
```

この状態では文字がちょっと大きく表示されるだけです
アニメーション部分は mixin に書きます

## mixin ってなに？
何度も使うスタイルをあらかじめ定義しておくことができます
`@include` で呼び出して使用します

[[CSS]Sassの@mixinとは？使い方もサクッと紹介](https://code-schools.com/css-sass-mixin/)

引数を渡すこともできるので、関数的な使い方ができちゃいます

## 一文字につき、一つの ■ をつくる SASS

```sass
@mixin fadeInWordY()
	span
		position: relative
		display: inline-block

		&:before
			content: ''
			display: block
			height: 100%
			width: 100%
			background-color: #000
			position: absolute
			top: 0
			left: 0
```
```sass
.fade-in-word
    font-size: 10px
    &__vertical
        font-size: 4em
        line-height: 1.1em
        @include fadeInWordY()
```


こんな感じのができあがりました

■■■■■■■

一文字につき、一つの ■ を、文字サイズにあわせて覆うことができました

# ■ を交互に動かす
## 文字数を渡して関数化

```sass
@mixin fadeInWord-animationY($length)
```

文字数を渡して繰り返し利用できるように mixin にわけます

```sass
.fade-in-word
    font-size: 10px
    &__vertical
        font-size: 4em
        line-height: 1.1em
        @include fadeInWordY(7)
```
```sass
@mixin fadeInWordY($length: 0)
	span
		position: relative
		display: inline-block

		&:before
			content: ''
			display: block
			height: 100%
			width: 100%
			background-color: #000
			position: absolute
			top: 0
			left: 0

	@include fadeInWord-animationY($length)
```

文字数は `@include fadeInWordY()` で直接指定します

## ■ を交互にフェードアウトさせる SASS

```sass
@mixin fadeInWord-animationY($length, $from: 1)
	$delay: 0

	@for $i from $from through $length
		@if $i%2==0
			span:nth-of-type(#{$i})
				transform-origin: bottom
				&:before
					transform-origin: bottom
		@else
			span:nth-of-type(#{$i})
				transform-origin: top
				&:before
					transform-origin: top

		span:nth-of-type(#{$i})
			&:before
				animation: notScaleY 0.3s ease forwards

		$delay: $delay + 0.1
```
```sass
@keyframes notScaleY
	100%
		transform: scaleY(0)
```

■ がこんな感じで動くようになりました

■■■■■■■

■&nbsp;&nbsp;&nbsp;■
W E L ■■■■
&nbsp;&nbsp;&nbsp;&nbsp;■

■&nbsp;&nbsp;&nbsp;■&nbsp;&nbsp;&nbsp;&nbsp;■&nbsp;&nbsp;&nbsp;&nbsp;■
W E L C O M E
&nbsp;&nbsp;&nbsp;&nbsp;■&nbsp;&nbsp;&nbsp;■&nbsp;&nbsp;&nbsp;&nbsp;■

## 待機中の ■ をみえないように

アニメーションが開始する直前（ロード直後）
■■■■■■■
が全部表示されてしまっています

最初は ■ を非表示にしておいて、フェードアウトし始める少し前に表示されるようにしましょう

```sass
@mixin fadeInWordY($firstLine: 0, $secondLine: 0)
	span
		position: relative
		display: inline-block
		transform: scaleY(0)
```


```sass
@mixin fadeInWord-animationY($length, $from: 1)
	span:nth-of-type(#{$i})
		animation: scaleY 0.36s ease (0.1s + $delay) forwards
		&:before
			animation: notScaleY 0.3s ease (0.45s + $delay) forwards
```

**うーん・・・なんかちょっとちがう・・・**

# 真骨頂「cubic-bezier」
## アニメーションの変化量を細かく調整する
```sass
span:nth-of-type(#{$i})
	animation: scaleY 0.36s cubic-bezier(0.84, 0.01, 0.78, 0.13) (0.1s + $delay) forwards
	&:before
		animation: notScaleY 0.3s cubic-bezier(0.11, 1.05, 0.32, 1.03) (0.45s + $delay) forwards
```

サンプル通りに動くアニメーションができました
設定するとしないで、まったく違う動きになっているように見えます

## cubic-bezier ってなに？
### の、説明のまえに
CSS animation では次の設定を行うことができます

- `animation-name`
- `animation-duration`
- `animation-timing-function`
- `animation-delay`
- `animation-iteration-count`
- `animation-direction`

この中で `animation-timing-function` を指定する際に使用しています

[animation - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/animation)

### cubic-bezier の使い方
アニメーションの変化量をカスタムできます
[CSS3のtransition-timing-functionの値、cubic-bezier()に関して](http://www.knockknock.jp/archives/184)

次のような形で値を設定します

```sass
animation-timing-function: cubic-bezier(0.11, 1.05, 0.32, 1.03)
```

### 変化量を直感的に決められるサイト
細かい数字を自分で調整しなくても、便利なサイトがあります

**[cube-bezier.com](https://cubic-bezier.com)**

今の数字は、ここでちょっとずつ調整しながら決めました

# 2 行でもできるように
1 行目の先頭文字の ■ は下から上に
2 行目の先頭文字の ■ は上から下に

変形がはじまる原点を変える

## 1 行ずつ mixin を適用する
要素が変形する際の原点は、次のプロパティで設定可能です

[transform-origin - CSS: カスケーディングスタイルシート | MDN](https://developer.mozilla.org/ja/docs/Web/CSS/transform-origin)

先頭文字の `transform-origin` を、引数で指定するのはどうでしょう

```html
<div class="fade-in-word__vertical-oneline">
	<span>W</span><span>E</span><span>L</span><span>C</span><span>O</span><span>M</span><span>E</span>
</div>
<br>
<div class="fade-in-word__vertical-twoline">
	<span>P</span><span>A</span><span>G</span><span>E</span>
</div>
```
```sass
.fade-in-word
    font-size: 10px
    &__vertical
        font-size: 4em
        line-height: 1.1em
        &-oneline
            @include fadeInWordY(7, top)
        &-twoline
            @include fadeInWordY(7, bottom)
```
それぞれの要素を `div` で囲っています
これでも構わないのですが、同じ要素内でないと都合が悪い場合もあります

今回はもともと構造が決まっていたうえで、アニメーションを追加する対応だったので
同じ要素内で 2 行が対応できるようにしました

## 1 行目と 2 行目を判定する
2 行目の文字数も引数で渡すようにします
2 行目の場合、先頭文字の `transform-origin` に設定される方向を逆にしました

```html
<div class="fade-in-word__vertical">
	<span>W</span><span>E</span><span>L</span><span>C</span><span>O</span><span>M</span><span>E</span>
	<br>
	<span>P</span><span>A</span><span>G</span><span>E</span>
</div>
```
```sass
.fade-in-word
    font-size: 10px
    &__vertical
        font-size: 4em
        line-height: 1.1em
        @include fadeInWordY(7, 4) // (1 行目の文字数, 2 行目の文字数)
```
```sass
@mixin fadeInWordY($firstLine: 0, $secondLine: 0)
	span
		position: relative
		display: inline-block
		transform: scaleY(0)

		&:before
			content: ''
			display: block
			height: 100%
			width: 100%
			background-color: #000
			position: absolute
			top: 0
			left: 0

	@if $firstLine != 0
		@include fadeInWord-animationY($firstLine)
	@if $secondLine != 0
		@include fadeInWord-animationY($firstLine + $secondLine, $firstLine + 1, TRUE)
```
```sass
@mixin fadeInWord-animationY($length, $from: 1, $vector: FALSE)
	$delay: 0
	$status: 0

	@if $vector==TRUE
		$status: 2
	@else
		$status: 1

	@for $i from $from through $length
		@if $status%2==0
			span:nth-of-type(#{$i})
				transform-origin: bottom
				&:before
					transform-origin: bottom
		@else
			span:nth-of-type(#{$i})
				transform-origin: top
				&:before
					transform-origin: top

		$status: $status + 1

		span:nth-of-type(#{$i})
			animation: scaleY 0.36s cubic-bezier(0.84, 0.01, 0.78, 0.13) (0.1s + $delay) forwards
			&:before
				animation: notScaleY 0.3s cubic-bezier(0.11, 1.05, 0.32, 1.03) (0.45s + $delay) forwards

		$delay: $delay + 0.1
```

# 追記（2019/9/18）
関数の書き方こっちの方がいいかもしれない
きれいな書き方ってむずかしい

```sass
@mixin fadeInWord-animationY($length, $from: 1, $vector: FALSE)
	$delay: 0
	$start: ''
	$next: ''

	span
		animation: scaleY 0.36s cubic-bezier(0.84, 0.01, 0.78, 0.13) forwards
		&:before
			animation: notScaleY 0.3s cubic-bezier(0.11, 1.05, 0.32, 1.03) forwards

	@if $vector==TRUE
		$start: top
		$next: bottom
	@else
		$start: bottom
		$next: top

	span:nth-of-type(odd)
		transform-origin: $start
		&:before
			transform-origin: $start

	span:nth-of-type(even)
		transform-origin: $next
		&:before
			transform-origin: $next

	@for $i from $from through $length
		span:nth-of-type(#{$i})
			animation-delay: 0.1s + $delay
			&:before
				animation-delay: 0.45s + $delay

		$delay: $delay + 0.1
```

# 縦向き
## 変更点をあげる

```sass
@mixin fadeInWordX($firstLine: 0, $secondLine: 0)
	writing-mode: vertical-rl

	span
		position: relative
		display: inline-block
		transform: scaleX(0)

		&:before
			content: ''
			display: block
			height: 100%
			width: 100%
			background-color: #000
			position: absolute
			top: 0
			left: 0

	@if $firstLine != 0
		@include fadeInWord-animationX($firstLine)
	@if $secondLine != 0
		@include fadeInWord-animationX($firstLine + $secondLine, $firstLine + 1, TRUE)
```

- `writing-mode: vertical-rl`

テキストのレイアウトを垂直方向にしました

- `transform: scaleX(0)`

横向きに広がるように `scaleX ` に変えました
基本的に横と縦では `scaleX` と `scaleY` が逆になります

```sass
@mixin fadeInWord-animationX($length, $from: 1, $vector: FALSE)
	$delay: 0
	$status: 0

	@if $vector==TRUE
		$status: 2
	@else
		$status: 1

	@for $i from $from through $length
		@if $status%2==0
			span:nth-of-type(#{$i})
				transform-origin: left
				&:before
					transform-origin: left
		@else
			span:nth-of-type(#{$i})
				transform-origin: right
				&:before
					transform-origin: right

		$status: $status + 1

		span:nth-of-type(#{$i})
			animation: scaleX 0.35s cubic-bezier(0.84, 0.01, 0.78, 0.13) (0.1s + $delay) forwards
			&:before
				animation: notScaleX 0.3s cubic-bezier(0.11, 1.05, 0.32, 1.03) (0.45s + $delay) forwards

		$delay: $delay + 0.1
```

- `transform-origin`

縦と横が変わるので、上下と左右も変わります

- `animation-name: scaleX` と `animation-name: notScaleX`

keyframes はこちら

```sass
@keyframes scaleX
	100%
		transform: scaleX(1)

@keyframes notScaleX
	100%
		transform: scaleX(0)
```

- `animation-duration: 0.35s`

これは好みの問題かもしれませんが、同じ秒数だと印象が違ったのでほんの少し調整しました

# おわりに
1 行のみ、または 2 行に対応しています
現在の書き方は複数行に対応していませんが、引数を増やせば拡張可能です