前書き#
もしあなたが B 站 の仕事が面白いかどうか、または私が B 站 に来てから何をしているのかに興味があるなら、この文章はあなたのいくつかの疑問に答えるかもしれません。
B 站 に来てから、HTML5 プレーヤーのいくつかのモジュール、リファクタリング、日常的なメンテナンスを担当する以外に、BAS 高度な弾幕は私が半年間担当して開発した最大のプロジェクトです。
この記事は、今日会社で行われた超極電磁波の共有から整理したもので(私は史上最年少の講師らしいです (=・ω・=))、このプロジェクトの一つの段階的なまとめでもあります。
BAS 弾幕とは#
BAS、正式名称 Bilibili Animation Script は、新世代の bilibili 高度な弾幕スクリプト言語であり、高度な弾幕スタイル、インタラクション、アニメーションを記述するためのテキストです。
BAS 弾幕は BAS で記述された高度な弾幕で、要素とアニメーションで構成され、要素はテキストオブジェクト、インタラクションボタン、path オブジェクトに分かれ、アニメーションは単純アニメーション、連結アニメーション、並列アニメーションに分かれます。
BAS 弾幕は主に字幕君などの高級プレイヤーを対象としており、字幕、特殊効果、インタラクティブアプリケーション、ゲーム、純粋な弾幕作品などのシーンで使用できます。
現在、Web 端はすでにオンラインで、モバイル端も基本的に開発が完了しており、来年のイベントや新年祭で使用できることを期待しています。
私たちは BAS 弾幕を直感的に示すためにいくつかのビデオを作成しました:
最初のビデオは毛酱大佬が作成した弾幕 PV:https://www.bilibili.com/video/av257649/
2 番目は弾幕実現の烂苹果:https://www.bilibili.com/video/av18682336/
3 番目はインタラクションボタンのデモ:https://www.bilibili.com/video/av16558829/index_3.html#page=3 、また、哔哩哔哩ランキング週刊のようなものも非常に良いアプリケーションシーンです。
字幕アプリケーションの面では、字幕ファイルを BAS に変換するツールなどを簡単に実現でき、今後もこのようなことを続けていく予定です。
いくつかの高度な弾幕の比較#
mode7#
弾幕はインターフェースで設定され、コードを書く必要がなく、使用は簡単ですが機能は比較的制限されています。
mode8#
すなわちコード弾幕で、非常に強力ですがコードを書く必要があり、使用は複雑で、安全性が低く、Flash プラットフォームのみをサポートしています。
mode9#
すなわち BAS 弾幕で、mode 7 と mode 8 の折衷案です。
mode 7 と比較すると、mode 9 はスクリプトを書く必要があり、使用はやや複雑ですが、インタラクション、グラフィック、より複雑なアニメーションをサポートし、機能ははるかに強力です。
mode 8 と比較すると、mode 9 は文法を簡素化し、宣言的に変更し、使用はより簡単で、安全性が高く、プレーヤーが解析を実現し、不正なスクリプトは通過せず、プログラムは制御可能で、直接弾幕を操作するのではなく、クロスプラットフォームで使用できます。
BAS 弾幕の使用#
送信権限#
まず、BAS 弾幕の送信権限には厳しい制限があり、設計上一般ユーザーはまずコインを購入し、その後 UP 主の確認を待ってから使用できるようになります。字幕君などの特別な権限を持つユーザーのみが直接使用できますが、現在は字幕君にのみ開放されており、字幕君が使用し改善した後に使用シーンを拡大することを検討します。
コイン購入 | UP 主確認 | |
---|---|---|
一般会員 | √ | √ |
UP 主 | √ | × |
VIP | × | √ |
字幕君 / 管理者 | × | × |
送信入口#
注意、送信権限がない場合、入口は隠されています。この時、試験室で試すことができます。
スクリプトの作成#
ここでは、いくつかの簡単なスクリプトを作成してみましょう。BAS スクリプトは非常にシンプルで、宣言的で記述的なスクリプトであり、文法が使いやすく、オブジェクトブロックと操作ブロックの独立性が保証されています。
BAS 弾幕を試す最も簡単な方法は、ドキュメントの例と試験室を使用することで、ブラウザの新しいタブで開き、例に従っていくつかの基本的な使い方を試すことができます。
テキストオブジェクトの例として、簡単なフェードアウトアニメーションを持つテキストオブジェクトは次のようになります:
def text demo {
content = "BAS"
}
set demo {
alpha = 0
} 5s
これで、フェードアウトする BAS 弾幕を成功裏に作成しました。見た目はとてもシンプルですが、js は背後で大量の作業を行っています。js は最初に BAS スクリプトを js が認識できるオブジェクトに解析し、デフォルトの属性を適用し、それをプレーヤーにレンダリングし、同時にアニメーションを開始します。この時、プレーヤーの左上隅にフェードアウトする白いテキストが表示されます。
位置決め#
位置決めも非常にシンプルで、BAS 弾幕の位置は弾幕のアンカーポイント(anchorX anchorY)とステージ位置(x y)によって決まります。アンカーポイントは弾幕の中心点で、(0, 0) は弾幕の左上隅、(1, 1) は弾幕の右下隅です。
def text tl {
content = "左上"
x = 0
y = 0
anchorX = 0
anchorY = 0
}
def text tr {
content = "右上"
x = 100%
y = 0
anchorX = 1
anchorY = 0
}
def text bl {
content = "左下"
x = 0
y = 100%
anchorX = 0
anchorY = 1
}
def text br {
content = "右下"
x = 100%
y = 100%
anchorX = 1
anchorY = 1
}
def text c {
content = "中心"
x = 50%
y = 50%
anchorX = 0.5
anchorY = 0.5
}
弾幕ステージ#
弾幕ステージは弾幕の描画範囲であり、弾幕ステージはデフォルトで動画の実際の領域です。さらに、テキストオブジェクトは parent 属性を使用して他のテキストオブジェクトを親要素として指定し、親要素を舞台として描画します。親要素は子要素の位置決め、スケーリングなどに影響を与えます。
def text a {
content = "□"
fontSize = 40%
x = 0
y = 0
color = 0xffff00
}
def text b {
parent = "a"
content = "□"
fontSize = 20%
x = 0
y = 0
color = 0xff00ff
}
set a {
x = 50%
y = 0
} 2s
then set a {} 3s
set b {
y = 50%
} 3s
then set b {} 3s
ライフサイクル#
ライフサイクルは BAS のもう一つの重要な概念であり、ライフサイクルは弾幕が生存する時間です。duration 属性が指定されていない場合、要素のライフサイクルはアニメーションの合計時間となり、アニメーションがない場合はデフォルトで 4 秒です。ライフサイクルが終了すると、要素は舞台から削除されます。
def text a {
content = "BAS"
}
def text a {
content = "BAS"
duration = 10s
}
def text a {
content = "BAS"
}
set a {} 10s
自適応#
位置とフォントサイズがパーセンテージ値の場合、ステージサイズに応じて自動的に適応できます。これにより、さまざまなプラットフォームや異なるプレーヤーサイズで弾幕の一貫した効果を実現し、異なる状況で動画に対する弾幕の位置とサイズが固定されます。位置座標は現在のステージの幅と高さ * パーセンテージ値 px、フォントサイズは現在のステージの幅 * パーセンテージ値 px です。
def text c {
content = "BAS"
x = 50%
y = 50%
anchorX = 0.5
anchorY = 0.5
fontSize = 5%
}
この時、プレーヤーのサイズを変更すると、弾幕のサイズもプレーヤーに合わせて変わり、結果として動画に対する位置とサイズが固定されます。
インタラクション#
現在、インタラクションボタンのみがいくつかの簡単なクリック効果をサポートしており、動画の特定の時間点にジャンプしたり、新しいウィンドウで他の動画を開いたりできます。
シークボタン:
def button c {
text = "30分にジャンプ"
x = 35%
y = 45%
fontSize = 5%
textColor = 0xffffff
fillColor = 0x80D8FF
target = seek {
time = 30m
}
}
av ジャンプボタン:
def button c {
text = "av1714157"
x = 35%
y = 45%
fontSize = 5%
textColor = 0xffffff
fillColor = 0x80D8FF
duration = 2s
target = av {
av = 1714157
page = 1
time = 20.5s500ms
}
}
bangumi ジャンプボタン:
def button c {
text = "第22話 春風"
x = 35%
y = 45%
fontSize = 5%
textColor = 0xffffff
fillColor = 0x80D8FF
duration = 2s
target = bangumi {
seasonId = 1699
episodeId = 80041
time = 1m30s
}
}
グラフィック#
path オブジェクトを使用して svg グラフィックを描画でき、d 属性は svg のパスに対応します。
def path p {
d = "M30.828,30.422 18.997,16.260 Z"
viewBox="0 0 32 34"
x = 45%
y = 45%
scale = 3
borderWidth = 1
borderColor = 0xffffff
borderAlpha = 0.8
fillColor = 0x00a1d6
fillAlpha = 0.8
}
アニメーション#
アニメーションは単純アニメーション、連結アニメーション、並列アニメーションに分かれます。
弾幕の属性には可変、不可変、固定のものがあり、可変属性のみが正常なアニメーション効果を持ちます。非可変属性に新しい値を設定すると即座に反映され、不可変属性に値を設定すると無視されます。原則として、特定の属性は 1 つの set 文の中で最大 1 回しか現れず、実装上、複数回現れる場合は最後のものが優先されます。
連結アニメーションは先後の順序で実行されます。
def text a {
content = "BAS"
}
set a {
color = 0x000000
} 1s
then set a {
alpha = 0
} 1s
並列アニメーションは同時に実行され、並列で同じ属性を設定する場合は最後のものが優先され、以前の衝突するアニメーションは無視されます。技術的制限により、x y rotateX rotateY rotateZ scale は同じ属性と見なされます。
def text a {
content = "BAS"
}
set a {
color = 0x000000
} 1s
set a {
alpha = 0
} 1s
BAS 弾幕のフロントエンド実装#
BAS スクリプトからブラウザの DOM 要素にレンダリングされるまでの主なステップは次のとおりです:
- BAS スクリプトを js オブジェクトに解析する(https://github.com/aristotle9/as3cc)
- デフォルト値を適用し、パーセンテージ値を計算する
- ライフサイクルを監視する
- 属性の衝突を解決する
- 要素を描画し、スタイルとアニメーションを適用する
- インタラクションイベントをバインドする
位置決め#
位置は BAS スクリプトのアンカーポイント(anchorX anchorY)と位置(x y)によって決まります。実装上は 2 つのネストされた DOM 要素を使用し、外部要素がステージ位置を決定し、内部要素が弾幕のアンカーポイントを決定します。例えば、中央に配置されたテキストオブジェクト:
def text c {
content = "BAS"
x = 50%
y = 50%
anchorX = 0.5
anchorY = 0.5
}
レンダリングされた DOM 構造はおおよそ次のようになります:
<div style="transform:translate((ステージの幅*50%)px, (ステージの高さ*50%)px);">
<div style="transform:translate(-50%,-50%);">BAS弾幕</div>
</div>
アニメーション#
アニメーションでは、ブラウザの互換性と使いやすさを考慮し、CSS3 の animation が最適な選択です。これに関する属性は次のとおりです:
属性 | 説明 |
---|---|
@keyframes | アニメーションを定義する |
animation-name | 対応する @keyframes アニメーションの名前 |
animation-duration | アニメーションが 1 サイクルを完了するのにかかる時間 |
animation-play-state | アニメーションが実行中または一時停止中であるか |
animation-timing-function | アニメーションの速度曲線 |
単純アニメーション#
上記の例を引き継いで:
def text demo {
content = "BAS"
}
set demo {
alpha = 0
} 5s
このような BAS スクリプトがレンダリングされる DOM 構造はおおよそ次のようになります:
<style>
@keyframes a1 {
100% { opacity:0; }
}</style>
<div style="animation-name:a1;animation-duration:5s;opacity:1;">BAS</div>
keyframes はアニメーションのキーフレームを定義し、アニメーション終了時の透明度はゼロになります;animation-duration はアニメーションの時間 5s に対応します。
並列アニメーション#
複数の keyframes を定義して、複数のアニメーションを同時に実行します。
def text a {
content = "BAS"
}
set a {
color = 0x000000
} 1s
set a {
alpha = 0
} 1s
<style>
@keyframes a1 {
100% { color:#000000; }
}
@keyframes a2 {
100% { opacity:0; }
}
</style>
<div style="animation-name:a1,a2;animation-duration:1s,1s;opacity:1;color:#ffffff;">BAS</div>
連結アニメーション#
animation-delay を使用して異なるアニメーションの開始時間をずらし、連結効果を実現します。
def text a {
content = "BAS"
}
set a {
color = 0x000000
} 1s
then set a {
alpha = 0
} 1s
<style>
@keyframes a1 {
100% { color:#000000; }
}
@keyframes a2 {
0% { color:#000000; }
100% { color:#000000;opacity:0; }
}
</style>
<div style="animation-name:a1,a2;animation-duration:1s,1s;animation-delay:0s,1s;opacity:1;color:#ffffff;">BAS</div>
状態制御#
開始#
animation-play-state: running
一時停止#
animation-play-state: paused
中間状態#
animation-delay を負の値に設定することで、特定の中間状態からアニメーションを開始できます。
例えば、弾幕のライフサイクルが動画の 1 秒から 5 秒に対応する場合、動画が 4 秒にジャンプしたとき、弾幕の animation-delay 属性を -1 秒に設定する必要があります。
終了#
ライフサイクルが終了したら、要素を迅速に削除する必要があります。原理は、animation アニメーションの終了が animationend イベントをトリガーし、そのイベントがトリガーされたときに要素を削除することです。要素にアニメーションがない場合は、空のアニメーションを指定する必要があります。
<style>
@keyframes a1 {
100% { }
}
</style>
終了。