JS 的平凡之路–學習人氣眼中的效果(中)

descire發表於2019-03-01

這一節簡單的模仿一下人氣眼中的無重疊彈幕效果,也不賣關子了,下一節模仿頭部的標籤切換效果

一、簡介

  一談到彈幕相信大家多不陌生,平時看直播,那彈幕可是看的很歡啊。

  人氣眼中的彈幕可能數量比較少的原因,是一種不重疊的彈幕。先看一下實現的效果:

無重疊彈幕效果
無重疊彈幕效果

  當你決定看下文之前,你需要注意幾點:

  • 最好這時你已經有了一定的canvas基礎;
  • 如果你壓根聽過canvas,那真是太好了,哇!你應該要入坑了。

二、繪製圓角矩形

  在canvas的API中並沒有圓角矩形的繪製方法,所以我們得自己寫一個繪製圓角矩形的方法,一說到圓角矩形,對於我們前端可以說是小菜一碟了,border-radius早就用爛了:

矩形的圓角
矩形的圓角

  由上面這幅圖,想必繪製圓角矩形的思路已經有了。這裡我們可以通過canvas中的弧線和直線組合成圓角矩形(這裡我就不過多的設定各個角的半徑了):

// =====================
// canvas 圓角矩形的繪製
// =====================
// @param { Number } x 左上角的x座標
// @param { Number } y 左上角的y座標
// @param { Number } w 寬度
// @param { Number } h 高度
// @param { Number } r 圓角半徑
// @param { String } bg 背景顏色
Barrage.prototype.drawRoundRect = function(options) {
    let {x,y,w,h,r,bg = "rgb(246,185,206)"} = options;
    const context = this.context;
    context.beginPath();
    context.fillStyle = bg;
    //左上角
    context.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
    context.lineTo(x + w - r, y);
    //右上角
    context.arc(x + w - r, y + r, r, Math.PI * 3 / 2, 0);
    context.lineTo(x + w, y + h - r);
    //右下角
    context.arc(x + w - r, y + h - r, r, 0, Math.PI / 2);
    context.lineTo(x + r, y + h);
    //左下角
    context.arc(x + r, y + h - r, r, Math.PI / 2, Math.PI);
    context.lineTo(x, y + r);

    context.fill();
}複製程式碼

  這裡需要注意的是beginPath方法,這通常是新手的一個坑:

  • 在Canvas中只有一條路徑存在,稱之為當前路徑;
  • 在當前路徑中我們可以新增很多子路徑;
  • 而beginPath方法就是清除當前路徑中的所有子路徑;
  • 如果不適當使用beginPath方法,則會導致你會重複繪製很多路徑。

  這裡你可以實驗一下下面的例子:

    context.strokeStyle = "#fff";
    context.rect(10,10,100,100);
    context.stroke();

    context.beginPath(); //去掉這段程式碼 會導致重複繪製上面生成的四個子路徑
    context.strokeStyle = "#000";
    context.rect(200, 200, 100, 100);
    context.stroke();複製程式碼

三、獲取文字寬度

  很巧的是,在Canvas中提供計算文字寬度的方法,需要注意的是:

  • 計算文字的寬度需要考慮到文字樣式的影響。
// ===================
// 計算文字寬度
// ===================
Barrage.prototype.getTextWidth = function(font, text) {
    const context = this.context;
    context.font = font + "px Arial";
    const textWidth = context.measureText(text).width;
    return textWidth;
}複製程式碼

  很遺憾的是,這裡並沒有獲取高度的屬性。。。。

四、繪製每一條標籤

  上面我們已經知道了繪製圓角矩形和測量文字的寬度了,這裡我們只需要繪製文字即可,而你需要注意的是合理的利用textAlign和textBaseline將文字調整到合適的位置,這裡我將文字居中於圓角矩形內部:

    context.textBaseline = "middle";
    context.textAlign = "center";
    context.fillText(text, x + width / 2, y + h / 2);複製程式碼

五、無重疊彈幕

  對於這一條,我們一步步來:

1、初始化資料的基本資訊

  初始化每個資料的字型、位置、速度等基本資訊,接下來繪製多需要這些引數,這些資料存放在data陣列中。

2、劃分車道

  無重疊彈幕和道路上行駛的車輛類似,我們需要劃分車道,讓它們有序的運作在各自的車道中:

    const pathHeight = 10 + vp * 2 + size,
          pathNumber = Math.floor(h / pathHeight);複製程式碼

3、彈幕的狀態區分

  彈幕我們可以區分為待顯示的彈幕、即將顯示的彈幕、未完全顯示的彈幕、完全顯示的彈幕和顯示完成的彈幕。這裡我們還需要宣告activeArray儲存未完全顯示的彈幕和完全實現的彈幕,nextBarrage儲存即將顯示的彈幕。

4、待顯示彈幕

  我們的彈幕是從左邊發射的,所以當一個彈幕完全顯示出來,這個“車道”應該就要新增新的彈幕了:

    // 部分程式碼
    if (item.x + item.w < width && item.status === 0) {
        next.push(item.y);
        item.status = 1;
        break;
    }複製程式碼

5、新增待顯示彈幕

  動畫執行的每一幀我們需要將待顯示彈幕加入到activeArray陣列當中:

    if (next.length > 0 && data.length > 0) {
        const temp = data.shift();
        temp.y = next.shift();
        temp.x = width + 200;
        activeArray.push(temp);
    }複製程式碼

  這裡的200並不是一個隨意的數,因為我們在設定速度時,是設定1~1.25的隨機數,所以你建立一個方程也就解出兩個彈幕之間的最小安全距離了。

6、動畫的暫停和恢復

  暫停和恢復的根本就是要儲存暫停的繪製狀態,這裡可以通過Canvas的getImageData和putImageData實現:

// ==============
// 動畫暫停與恢復
// ==============
Barrage.prototype.pause = function () {
    const context = this.context;
    if (!this.animationLock) {
        this.drawSurface = context.getImageData(0,0,this.width, this.height);
        this.animationLock = true;
    } else {
        context.putImageData(this.drawSurface, 0, 0);
        this.animationLock = false;
        this.animate();
    }
}複製程式碼

  到這裡,簡單的彈幕功能就完成了。

參考書籍: 《HTML5 Canvas核心技術》
原始碼

相關文章