效果

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)

android 带头像的弹幕,原生Canvas实现带头像的弹幕相关推荐

  1. php网页自定义头像系统,怎样用canvas实现自定义头像功能

    这次给大家带来怎样用canvas实现自定义头像功能,用canvas实现自定义头像功能的注意事项有哪些,下面就是实战案例,一起来看一下. 写在最前: 前两天老大跟我说老虎官网上那个自定义头像的功能是fl ...

  2. Android Canvas绘制带箭头的直线

    先看下效果图: 下面我们直接看代码 我自定义了一个View,代码如下: package com.davis.drawtrangle;import android.content.Context; im ...

  3. 微信小程序踩坑记录 ------- canvas 生成带小程序码的微信朋友圈分享图

    最近做了一个问卷类的小程序,其中的结果页想让用户进行朋友圈分享转发,网上搜索资料,得出解决思路,用 canvas 将页面绘制生成图片,然后保存到手机相册,最终效果如下: 在这里我只写页面里关于 can ...

  4. GitHub上受欢迎的Android UI Library-项目开发实战篇:带各类框架链接地址详细解说及使用方法

    这是我列举的下列所有框架github地址:https : //github.com/opendigg/awesome-github-android-ui 抽屉菜单类的框架 MaterialDrawer ...

  5. 国庆头像小程序源码,带独立版后台同时可添加小程序跳转+流量主,所有改动均可后台添加+带搭建教程

    微信小程序实现国旗头像,国庆个性化头像 国庆头像小程序源码,带独立版后台同时可添加小程序跳转+流量主,所有改动均可后台添加+带搭建教程 快去挑选一个自己喜欢的国庆头像吧,只需简单两步即可制作自己专属国 ...

  6. 原生canvas游戏性能优化

    微信小游戏在 17 年末推出,再次带火了 H5 小游戏的开发,本公众号准备一些 H5 游戏开发的文章,奉献给读者,想做小游戏的可以练练手,没时间做的可以学习下游戏常用的一些方法和概念. 本文来自 li ...

  7. java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题

    文章目录 先看成品 前言 一.项目目录结构 一.海报制作PosterUtil.java工具类 1. 描述 2. 代码 二.测试生成海报 1. 描述 2. 直接上代码 四.其他测试 1. Test1_C ...

  8. Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(Canvas)

    Python之tkinter:动态演示调用python库的tkinter带你进入GUI世界(Canvas) 导读 动态演示调用python库的tkinter带你进入GUI世界(Canvas) 目录 t ...

  9. 带你体验云原生场景下 Serverless 应用编程模型

    简介: 阿里云 Knative 基于 ASK 之上,在完全兼容社区 Knaitve 的同时对 FC.ECI 工作负载进行统一应用编排,支持事件驱动.自动弹性,为您提供统一的 Serverless 应用 ...

最新文章

  1. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(39)-在线人数统计探讨
  2. Java 有关于线程
  3. PHP中三元运算符的用法_php中三元运算符用法_PHP教程
  4. Linux的起源与各发行版的基本知识
  5. Meteor环境安装配置
  6. 【直播 】ASP.NET Core解密底层设计逻辑
  7. [LeetCode] 3. Longest Substring Without Repeating Characters 题解
  8. raspberry pi_您应该选择哪种Raspberry Pi?
  9. 小程序引入的echarts过大如何解决_智慧虎超:为服装行业带来3倍收益?小程序如何解决销售难题?...
  10. archlinux安装gnome-shell主题
  11. python使用pip安装第三方库(工具包)速度慢、超时、失败的解决方案
  12. Python新手学习基础之条件语句——elif语句
  13. SQL null的学问
  14. leetcode.1024. 视频拼接
  15. 获取头条小程序分享二维码
  16. pb一步步开发APP
  17. 灵魂站队:结婚,男的压力大,还是女的压力大?
  18. 《C Primer Plus》读后感
  19. 计算一幅图像的平均亮度
  20. C++ 数据结构之栈stack (henu.hjy)

热门文章

  1. 小熊的人生回忆(四)
  2. nginx下使用SSI
  3. 关于Edge被篡改:打开的主页被改动、收藏夹被改动等问题
  4. Learning Cocos2d-x for WP8(2)——深入刨析Hello World
  5. 2020 Deep Learning for Sensor-based Human ActivityRecognition Overview, Challenges and Opportunities
  6. 记一次面试(被骗)经历
  7. 现代 Web 开发的现状与未来
  8. 北京一日行之十二——植物园、蜜蜂馆、碧云寺、香山
  9. html5 audio音频播放器
  10. [译] JavaScript 性能优化杀手