ニコニコ動画風のコメントをJavaScriptで作ってみよう

はじめに

ニコニコ動画風の流れるコメントをJavaScriptで作ってみようという企画です。
かなり難しいので、作れる箇所から徐々に制作していきます。完成するかは未定。

HTML

HTMLは単純です。
スクリーン(div#screen)内にコメント(span.comment)を追加していきます。
スクリーン内には動画(video#video)も用意します

※コメントタグは動画タグの前に追加していきます
動画タグの後に追加する場合は #video に position:absolute が必要です

スクリーンのCSS

スクリーンのCSSです

#screen{
    background-color: #000;
    width: 640px;
    height: 360px;
    overflow: hidden;
    white-space : nowrap;
    cursor: default;
    -ms-user-select: none;
    -moz-user-select: none;
    -webkit-user-select: none;
    user-select: none;
    position: relative;
}

スクリーンからはみ出た文字を隠すため overflow:hidden を設定
上記を有効にするには position:relative も必要
文字が勝手に折り返らないように white-space:nowrap を指定
あとは文字を選択できないようにしたり、カーソルも変更
スクリーンのサイズは固定しておきます(640×360)。HD風に縦横比は1.777

コメントのCSS

コメントのCSSです

.comment{
    position: absolute;
    font-size: 24px;
    line-height: 1;
    z-index: 2;
    color: #fff;
    text-shadow: -1px -1px #000, 1px -1px #000,	-1px 1px #000, 1px 1px #000;
}

文字を動かすために position:absolute を指定
文字色が背景色と被った時のために text-shadow で縁取りする (白文字に黒の縁取り)
念のために z-index も指定

文字を動かす

文字を動かす方法はいろいろありますが、CSSのtransformを利用するとよさそうです
CSSに下記を追加します

.comment{
    animation-name: lane0;
    animation-timing-function: linear;
    animation-duration: 8s;
    animation-fill-mode: forwards;
}
@keyframes lane0 {
from{ transform: translate(640px, 0); }
to{ transform: translate(-1280px, 0); }
}

CSSアニメーションを利用します。
動作関数は linear を指定するとニコニコ風になります

transform:translate(X, Y) で 要素を移動させることができます
とりあえず 右から左へとコメントが流れるようにします。(※Y軸は後回し)

from{} は アニメーションの開始に関する指定
640pxとはスクリーンの右端地点です(=スクリーンの横幅)。
ここからコメントの左端が動き始めます

to{} は アニメーションの終了に関する指定
どこまでコメントを移動させるべきかですが、スクリーン外まで動かします。
0pxがスクリーン左端地点なので、-1280pxはスクリーン2つ分左までになります
最大文字数を考慮しています。

from から to までは 8秒 かけて移動します。animation-duration:8s

アニメーション終了後は、その場に留まるように指定します。animation-fill-mode:forwards

画面外に流れたコメントを消す

ここまでで文字を流せる所まできましたが、これではコメントタグが大量に増えてしまいます。なのでコメントが完全にスクリーン外に出たらコメントを消します

スクリーン内の要素を監視

videoオブジェクトは動画再生中に timeupdate イベントを 3~4回/秒 発行します。
そのタイミングでスクリーン内のコメント要素を検索して、画面外なら消します

removeChild()で要素を削除できますが、ループは逆順に回す必要がある

for(var i = comments.length-1; i >= 0; i--){}

要素の位置は getBoundingClientRect() で取得できます。 (絶対位置)
「コメントの右端 < スクリーンの左端」なら画面外です

文字の重なり防止

ここまでのコードを動かしてみると、同時にコメントを流すとコメントが重なってしまいます。コメントが重ならないように、何らかの工夫が必要になりますが、難しい所です。

・スクリーンを縦にn分割する。 (スクリーン縦サイズ ÷ フォントサイズ が目安)
・分割したものはレーンと呼び、レーン毎にコメントを流す
・コメントを放出したらレーン[n]を false に
・レーン[n]が false のレーンにはコメントを放出しない
・スクリーン内にある全コメントタグを監視して、コメントの右端がスクリーン右端より外ならば、そのレーン[n]は false に(それ以外はtrueに)

これでコメントが重なることはないと思います。

今回は12レーン用意します。
12レーン×25pxで300px。下60pxは使わないことにします

12レーン分のCSSを用意しましょう。下記のCSSを追加

@keyframes lane1 {
from{ transform: translate(640px, 25px); }
to{ transform: translate(-1280px, 25px); }
}
@keyframes lane2 {
from{ transform: translate(640px, 50px); }
to{ transform: translate(-1280px, 50px); }
}
/* 以下lane11まで省略 */

@keyframes をCSS直書きでなくJavaScriptから追加したいなら次のように記述します

for(var i = 0; i < 12; i++){
    var keyframe = "";
    keyframe += "@keyframes lane" + i + "{\n";
    keyframe += "from{transform:translate(640px," + i*25 + "px);}\n";
    keyframe += "to{transform:translate(-1280px," + i*25 + "px);}\n}\n";
    document.styleSheets[0].insertRule(keyframe, 0);
}

コメントをどのレーンに流したかは コメントタグの data-lane 属性に記録しておく

コメントのフォーマット

コメントはサーバからJSON形式で受け取ることにします
フォーマットはこんな感じで。(投稿時間でソートしておく)

[
    ["コメント1", 動画時間, 投稿時間(UNIXタイム)],
    ["コメント2", 動画時間, 投稿時間(UNIXタイム)]
]

ただ、これでは使いにくい気がするので動画時間毎に整理します
例えば100秒の動画としたら配列の要素数を101にして、秒毎に整理します
配列[動画秒数] でアクセスできるので使いやすい気がする

[
    [//0秒台のコメント
        ["コメント1", 動画時間, 投稿時間(UNIXタイム)],
        ["コメント2", 動画時間, 投稿時間(UNIXタイム)]
    ],
    [//1秒台のコメント
        ["コメント3", 動画時間, 投稿時間(UNIXタイム)],
        ["コメント4", 動画時間, 投稿時間(UNIXタイム)]
    ]
]

コメントを停止する

動画が停止した時にコメントも停止するようにします。
動画が停止した時は pause イベントが発行します
コメントを停止するには、コメントタグのスタイルに animation-play-state: paused;
をセットします。

コメントを再生するには、play イベントが発行した時に running をセットします

コメントをクリアする

シークした時、動画が最後まで到達した時はコメントを全て削除する
seeking と ended イベントです

コメントを流す

流すタイミング

コメントを流すのは1秒毎にします。
現在の動画の再生時間は video.currentTime から取得できます

timeupdate イベントが発行する毎に 現在の時間を調べて、前回の秒と違う時にコメントを流せばよいでしょう

動画が停止中にはコメントを流さないようにします。video.pausedの真偽値を調べる
これをサボると、ポーズした時に稀にコメントが流れてしまいます

※飽きてきたので、説明はここまでとします
詳しくは完成品のソースをご覧ください

コメントをどうぞ~