android 带头像的弹幕,原生Canvas实现带头像的弹幕

效果

gif有些糊,可以 在线预览

实现关键点

requestAnimationFrame 循环帧;

绘制单条弹幕,画框子 -> 画头像 -> 写黑色的字 -> 写红色的字, measureText获取文字宽度;

防止弹幕重叠,分行且记录当前行是否可插入,弹幕随机行插入;

弹幕滚出屏幕外时,移除此条弹幕;

循环发射弹幕的实现。

代码

弹幕(头像,文字)

// 圆角矩形

CanvasRenderingContext2D.prototype.roundRect = function (left, top, width, height, r) {

const pi = Math.PI;

this.beginPath();

this.arc(left + r, top + r, r, -pi, -pi / 2);

this.arc(left + width - r, top + r, r, -pi / 2, 0);

this.arc(left + width - r, top + height - r, r, 0, pi / 2);

this.arc(left + r, top + height - r, r, pi / 2, pi);

this.closePath();

}

class Barrage {

constructor(id) {

this.scale = 2; // 缩放倍数,1会糊

this.canvas = document.getElementById(id);

this.canvas.width = this.w = document.body.offsetWidth * this.scale;

this.canvas.height = this.h = 220 * this.scale;

this.canvas.style.width = this.w / this.scale + 'px';

this.ctx = this.canvas.getContext('2d');

this.style = { // 弹幕样式

height: 27 * this.scale, // 弹幕高度

fontSize: 14 * this.scale, // 字体大小

marginBottom: 4 * this.scale, // 弹幕 margin-bottom

paddingX: 8 * this.scale, // 弹幕 padding x

avatarWidth: 18 * this.scale, // 头像宽度

}

this.ctx.font = this.style.fontSize + 'px PingFangSC-Regular';

this.barrageList = []; // 弹幕列表

this.rowStatusList = []; // 记录每行是否可插入,防止重叠。 行号为可插入 false为不可插入

let rowLength = Math.floor(this.h / (this.style.height + this.style.marginBottom));

for (var i = 0; i < rowLength; i++) {

this.rowStatusList.push(i)

}

}

shoot(value) {

const { height, avatarWidth, fontSize, marginBottom, paddingX } = this.style;

const { img, t1, t2 } = value;

let row = this.getRow();

let color = this.getColor();

let offset = this.getOffset();

let w_0 = paddingX; // 头像开始位置

let w_1 = w_0 + avatarWidth + 8; // t1文字开始位置

let w_2 = w_1 + Math.ceil(this.ctx.measureText(t1).width) + 8; // t2文字开始位置

let w_3 = w_2 + Math.ceil(this.ctx.measureText(t2).width) + paddingX; // 弹幕总长度

let barrage = {

value,

color,

row,

top: row * (height + marginBottom),

left: this.w,

offset,

width: [w_0, w_1, w_2, w_3],

}

this.barrageList.push(barrage);

}

draw() {

if (!!this.barrageList.length) {

this.ctx.clearRect(0, 0, this.w, this.h);

for (let i = 0, barrage; barrage = this.barrageList[i]; i++) {

// 弹幕滚出屏幕,从数组中移除

if (barrage.left + barrage.width[3] <= 0) {

this.barrageList.splice(i, 1);

i--;

continue;

}

// 弹幕完全滚入屏幕,当前行可插入

if (!barrage.rowFlag) {

if ((barrage.left + barrage.width[3]) < this.w) { //

this.rowStatusList[barrage.row] = barrage.row;

barrage.rowFlag = true;

}

}

barrage.left -= barrage.offset;

this.drawBarrage(barrage);

}

}

requestAnimationFrame(this.draw.bind(this));

}

drawBarrage(barrage) {

const { height, avatarWidth, fontSize, marginBottom, paddingX } = this.style;

const {

value: { img, t1, t2 },

color,

row,

left,

top,

offset,

width,

} = barrage;

// 画框子

this.ctx.roundRect(left, top, width[3], height, height / 2)

this.ctx.fillStyle = 'rgba(255,255,255,0.50)';

this.ctx.fill();

// 画头像

this.ctx.drawImage(img, 0, 0, img.width, img.height, left + width[0], top + (height - avatarWidth) / 2, avatarWidth, avatarWidth);

// 画黑色的字

this.ctx.fillStyle = color;

this.ctx.fillText(t1, left + width[1], top + fontSize + 8);

// 画红色的字

this.ctx.fillStyle = '#F24949';

this.ctx.fillText(t2, left + width[2], top + fontSize + 8);

}

getRow() {

let emptyRowList = this.rowStatusList.filter(d => /\d/.test(d)); // 找出可插入行

let row = emptyRowList[Math.floor(Math.random() * emptyRowList.length)]; // 随机选一行

this.rowStatusList[row] = false;

return row;

}

haveEmptyRow() {

let emptyRowList = this.rowStatusList.filter(d => /\d/.test(d)); // 找出可插入行

return !!emptyRowList.length;

}

getColor() {

return '#000000';

}

getOffset() {

return 1 * this.scale;

}

}

var list = [

{

avatar: 'https://image.duliday.com/living-cost/20200303/2a94df636b91ad15bbbb4408e2f285e4164115?roundPic/radius/66',

t1: '张**三 给 李**四',

t2: '红包',

},

{

avatar: 'https://image.duliday.com/living-cost/20200317/4cd9f827d439f7a1227501f9b09cd1e8622417?roundPic/radius/66',

t1: '王**五 给 赵**六',

t2: '红包',

}

]

// 循环插入发射弹幕

var index = 0;

var shootBarrage = function (list) {

setTimeout(function () {

if (barrage.haveEmptyRow()) {

var data = list[index++] || list[(index = 0) || index++];

var img = new Image();

img.setAttribute("crossOrigin", 'anonymous');

img.onload = function () {

barrage.shoot({

img,

t1: data.t1,

t2: data.t2,

});

}

img.src = data.avatar;

}

shootBarrage(list);

}, 1000)

}

var barrage = new Barrage('canvas');

barrage.draw();

shootBarrage(list)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值