CSSだけで動かしてみよう!メッセージアプリのようなチャットUI

作るもの

今回は、JavaScriptやjQueryは使用せず、CSSのみでLINEやSkypeのようなメッセージが自動的に動くものを作ってみたいと思います。

最終形態はこちらです。
メッセージアプリのような動くチャットUIをCSSで作る

デモページはこちら(http://sample.creativeflake.com/blog/chatui.html)

事前準備

今回使用するCSSは動作のためのキーフレーム
@keyframes

そして、CSSで変数を利用する
var()

の2つがポイントとなります。

CSS Variablesのブラウザ対応状況

後者の変数ですが、CSSで利用するにはまだ開発中の機能であり、どのブラウザでも使えるとは限りません。
また開発に伴い、構文と挙動について、仕様変更の可能性もありますので、導入の際は注意してください。

CSS Variablesの、主要各ブラウザは下記のとおり、緑の箇所のバージョンが対応しています。 (2017年8月時点)

FirefoxやSafari、Chrome等のPCブラウザ、スマホブラウザでもiOS Safari、Android対応しています。

*現時点でIEは非対応、Edgeも非推奨なので、使用する際は要注意です。

参考: http://caniuse.com/

HTMLを作っていこう

HTMLには、枠とメッセージを組み込みます。

おはよう\(^o^)/
今起きた。
映画見てて気づいたら朝だったしw
お腹すいたー( ´−`)。oO
お腹すいたね( ´−`)。oO
なんかおすすめない?
今日午後新しくできたカフェに行かない??
いいよ? 何時に待ち合わせ?

特に気にすることなく、枠とメッセージ本文を書いていけばOKです。

ここで、それぞれのメッセージには、ID名をつけてあげましょう。

クラス名のmsg-receiveとmsg-sendは、スタイルシート用の色分けです。

CSSを書いてみよう

下記がCSS全文です。

長いですが、解説はCSSの下のとおりです。

:root {
	--msg-content-height: 510px;
	--msg-spacing: 30px;
	--msg1-height: 50px;
	--msg2-height: 70px;
	--msg3-height: 50px;
	--msg4-height: 70px;
	--msg5-height: 70px;
	--msg6-height: 50px;
}

.msg-send {
	background: #bef18c;
	border-radius: 10px;
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
	color: #36393d;
	position: absolute;
	padding: 10px;
	animation-iteration-count: 1;
	animation-duration: 8s;
	animation-fill-mode: forwards;
	max-width: 320px;
}

.msg-receive {
	background: #e4e8eb;
	border-radius: 10px;
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
	color: #36393d;
	position: absolute;
	padding: 10px;
	animation-iteration-count: 1;
	animation-duration: 8s;
	animation-fill-mode: forwards;
	max-width: 320px;
		}

.msg-container {
	background: #fff;
	box-shadow: 0 10px 25px 0px rgba(0,0,0,0.15);
	box-sizing: border-box;
	padding: 20px;
	margin: 50px auto;
	width: 400px;
}

.msg-content {
	height: var(--msg-content-height);
	position: relative;
}

#msg1 {
	animation-name: msg1;
}

#msg2 {
	animation-name: msg2;
}

#msg3 {
	animation-name: msg3;
}

#msg4 {
	animation-name: msg4;
}

#msg5 {
	animation-name: msg5;
}

#msg6 {
	animation-name: msg6;
}

@keyframes msg1 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg1-height));
		left: 0;
		transform: scale(0);
	}
	5% {
		top: calc(var(--msg-content-height) - var(--msg1-height));
		left: 0;
		transform: scale(1);
	}
	12% {
		top: calc(var(--msg-content-height) - var(--msg1-height));
	}
	17% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height));
	}
	30% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height));
	}
	35% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height));
	}
	49% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height));
	}
	54% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	70% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	75% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg1-height) - var(--msg-spacing) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
	}
	100% {
		top: 0;
		left: 0;
	}
}

@keyframes msg2 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg2-height));
		left: 0;
		transform: scale(0);
		opacity: 0;
	}
	12% {
		top: calc(var(--msg-content-height) - var(--msg2-height));
		left: 0;
		transform: scale(0);
	}
	17% {
		top: calc(var(--msg-content-height) - var(--msg2-height));
		left: 0;
		transform: scale(1);
		opacity: 1;
	}
	30% {
		top: calc(var(--msg-content-height) - var(--msg2-height));
	}
	35% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height));
	}
	49% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height));
	}
	54% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	70% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	75% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
		left: 0;
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
	}
	100% {
		top: calc(var(--msg-content-height) - var(--msg2-height) - var(--msg-spacing) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height) - var(--msg-spacing) - var(--msg6-height));
		left: 0;
	}
}

@keyframes msg3 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg3-height));
		left: 0;
		opacity: 0;
		transform: scale(0);
	}
	30% {
		top: calc(var(--msg-content-height) - var(--msg3-height));
		opacity: 0;
		transform: scale(0);
	}
	35% {
		top: calc(var(--msg-content-height) - var(--msg3-height));
		right: 0;
		transform: scale(1);
		opacity: 1;
	}
	49% {
		top: calc(var(--msg-content-height) - var(--msg3-height));
	}
	54% {
		top: calc(var(--msg-content-height) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	70% {
		top: calc(var(--msg-content-height) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height));
	}
	75% {
		top: calc(var(--msg-content-height) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
		right: 0;
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
	}
	100% {
		top: calc(var(--msg-content-height) - var(--msg3-height) - var(--msg-spacing) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height) - var(--msg-spacing) - var(--msg6-height));
		right: 0;
	}
}

@keyframes msg4 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg4-height));
		left: 0;
		opacity: 0;
		transform: scale(0);
	}
	49% {
		top: calc(var(--msg-content-height) - var(--msg4-height));
		left: 0;
		opacity: 0;
		transform: scale(0);
	}
	54% {
		top: calc(var(--msg-content-height) - var(--msg4-height));
		left: 0;
		transform: scale(1);
		opacity: 1;
	}
	70% {
		top: calc(var(--msg-content-height) - var(--msg4-height));
	}
	75% {
		top: calc(var(--msg-content-height) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
		left: 0;
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height));
	}
	100% {
		top: calc(var(--msg-content-height) - var(--msg4-height) - var(--msg-spacing) - var(--msg5-height) - var(--msg-spacing) - var(--msg6-height));
		left: 0;
	}
}

@keyframes msg5 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg5-height));
		right: 0;
		opacity: 0;
		transform: scale(0);
	}
	70% {
		top: calc(var(--msg-content-height) - var(--msg5-height));
		right: 0;
		opacity: 0;
		transform: scale(0);
	}
	75% {
		top: calc(var(--msg-content-height) - var(--msg5-height));
		right: 0;
		transform: scale(1);
		opacity: 1;
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg5-height));
	}
	100% {
		top: calc(var(--msg-content-height) - var(--msg5-height) - var(--msg-spacing) - var(--msg6-height));
		right: 0;
		transform: scale(1);
		opacity: 1;
	}
}

@keyframes msg6 {
	0% {
		top: calc(var(--msg-content-height) - var(--msg6-height));
		left: 0;
		opacity: 0;
		transform: scale(0);
	}
	95% {
		top: calc(var(--msg-content-height) - var(--msg6-height));
		left: 0;
		opacity: 0;
		transform: scale(0);
	}
	100% {
		top: calc(var(--msg-content-height) - var(--msg6-height));
		left: 0;
		opacity: 1;
		transform: scale(1);
	}
}

ルート :root と変数

1行目
:root 疑似要素は、タグと同じことを意味しています。
タグよりも上位(親、外側)の要素です。

2行目〜9行目
–(ハイフン2つ)は変数を表します。
たとえば、–msg-content-height: 510px; は変数名「msg-content-height」は510pxだということを、
変数名「msg-spacing」は30pxだということを表しています。

変数の名称は一般には、好きな名前をつけることができます。

ここでは、rootに対してそれぞれの変数を定義しているので、このWebページ内の任意の場所で、宣言した変数を呼び出すことができるようになりました。

アニメーションの基本設定

12行目〜40行目
*クラス名 msg-send と クラス名 msg-receive の装飾に関する部分は省略します。

animation-iteration-count: 1;
はアニメーションのリピート回数を表し、今回は1回再生したら終了です。

animation-duration: 8s;
は、このアニメーションの一連の流れを8秒かけて実行することを意味します。

animation-fill-mode: forwards;
は、再生中以外(この場合は再生終了後)にアニメーションの最後のフレームを表示することを意味します。
この値を backwards とすると、再生終了後に最初の状態が表示されるようになります。

*msg-container は装飾に関するCSSなので省略します。

変数の呼び出し

52行目
ここでようやくCSS文頭で宣言した変数を呼び出して使います。

height: var(–msg-content-height);
この要素(msg-content)の高さを、変数で指定した値、つまり510pxとします。

こうすることで、CSSでも要素の高さを変えたいときには、変数定義部分の値を変更することでOKです。

アニメーションに名前をつける

56行目〜78行目
キーフレームでアニメーションを作るときには、アニメーションに名前が必要です。
先に名前をつけ、後で名前を呼んであげます。

今回は、メッセージ1から6までの名前をつけます。

アニメーションの名前を呼んで、動作を指定してあげる

80行目〜122行目
@keyframes アニメーション名
で先ほど名付けたアニメーションを呼び、その動作を次の { } 内で指定します。

この0%とか5%、12%などの%は、このアニメーション(今回は8秒間)の中で、どの段階でということを意味します。

計算すると厳密には8秒間のアニメーションでは、5%は開始から0.4秒後、12%は0.96秒後だということが分かりますね。

82行目
コンテンツの上部からの絶対位置を計算で算出しています。
*絶対位置ということは19行目と34行目で position: absolute; を定義しています。

top: calc(var(–msg-content-height) – var(–msg1-height));

calc()を使えば幅や高さを計算できる!

例えば、
width: calc(100% – 200px);
と書けば、この幅は100%から200pxを引いたものだと分かります。

もし画面横幅いっぱいがモニタサイズによって分からない場合でも、200px引いた値にすることができます。

このcalc()のカッコ内では四則演算をすることが可能です。

つまり今回サンプルの場合は、コンテンツの高さ(510px)からmsg1の高さ(50px)を引いた値となります。

本当は変数でなくとも、510pxとか50pxとか、具体的な数字でもいいのですが(むしろその結果の460pxでも)、
そうするとコンテンツの高さを変えたいとなった場合に、CSS上で変更する箇所がいっぱいありますよね。

ここが変数のいいところ!
値に変更があっても宣言の部分だけ変更すればいいので、メンテナンスが簡単というメリットがあります。

メッセージをふわっと出そう

84行目と89行目
ここではページを開いたとき(アニメーション開始時)と、5%経過時(0.4秒後)のメッセージ現れ方を指定しています。

開始時は何もない状態から
transform: scale(0);

5%経過時に100%の拡大率までびよーんと引き伸ばしています。
transform: scale(1);

(0.4秒なので、「ふわっと」って感じでしょうか。)

残りは順に高さを指定

91行目から
ここからは次々とメッセージが表示されるごと、最初のメッセージは上にあがっていくだけです。

こちらもtopの位置をcalcにて計算しますが、コンテンツの大きさからメッセージの高さと、余白を引いているところは同じです。

他のメッセージも同様に

そしてメッセージ2番目の場合は12%の時間まで表示しない(transform: scale(0);)設定にしておきます。

デモページはこちらです

デモページはこちら(http://sample.creativeflake.com/blog/chatui.html)

キーフレームは細かい作業ですが、見ていて心地いいタイミングが見つかると楽しいですよ。
きっと以前フラッシャーだったという方は得意かも。