寫在前面#
如果你好奇 B 站的工作有不有趣,或者我來 B 站之後都在做什麼,那麼這篇文章或許可以解答你的部分疑問。
來 B 站後,除了負責 HTML5 播放器的一些模塊、重構和日常維護以外,BAS 高級彈幕算是我半年來負責開發的最大一個項目了。
本文整理自今天在公司的超極電磁波分享(據說我還是有史以來年齡最小的講師 (=・ω・=),也算是對這個項目的一个階段性總結。
什麼是 BAS 彈幕#
BAS,全稱 Bilibili Animation Script,是新一代 bilibili 高級彈幕腳本語言,是一種用來描述高級彈幕樣式、交互和動畫的文本。
BAS 彈幕是用 BAS 描述的高級彈幕,由元素和動畫組成,元素分為文本對象、交互按鈕、path 對象,動畫分為簡單動畫、串聯動畫、並聯動畫。
BAS 彈幕主要面向字幕君等高端玩家,可以用於字幕、特效、交互應用、遊戲、純彈幕作品等場景。
目前 Web 端已經上線,移動端也基本開發完成,期望在明年的活動或拜年祭中能夠用到。
我們做了幾個視頻來直觀展示 BAS 彈幕:
第一個是毛醬大佬做的彈幕 PV:https://www.bilibili.com/video/av257649/
第二個是彈幕實現的爛蘋果:https://www.bilibili.com/video/av18682336/
第三個是交互按鈕的演示: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 屬性時,元素生命週期為動畫總時間,沒有動畫時默認為 4s。生命週期結束後元素就會在舞台中被清除。
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%
}
這時候改變播放器大小,彈幕大小也會跟隨播放器改變,結果是它相對於視頻的位置和大小是固定的。
交互#
目前只有交互按鈕支持一些簡單的點擊效果,轉跳到視頻某個時間點、新窗口打開其他視頻等。
seek 按鈕:
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
}
動畫#
動畫分為簡單動畫、串聯動畫、並聯動畫。
彈幕的屬性有可漸變、不可漸變、不可變之分,只有可漸變屬性才有正常的動畫效果,對非漸變屬性設置新值會立即生效,對不可變的屬性設值將被忽略。原則上某一屬性在一個 set 語句中最多只能出現一次,在實現上,如果多次出現,以最後一次為準。
串聯動畫以先後順序運行。
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)共同決定,實現上使用兩個嵌套的 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 | 動畫完成一個週期的時間 |
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 為負值就可以實現從某個中間狀態開始動畫。
比如彈幕的生命週期對應視頻的 1s 到 5s,視頻跳轉到 4s 時,需要設置彈幕 animation-delay 屬性為 -1s。
結束#
生命週期結束需要及時清除元素,原理是 animation 動畫結束會觸發 animationend 事件,該事件觸發時清除掉元素即可。元素沒有動畫時需要指定一個空動畫。
<style>
@keyframes a1 {
100% { }
}
</style>