效果

gif有些糊,可以 在线预览

实现关键点

  • requestAnimationFrame 循环帧;
  • 绘制单条弹幕,画框子 -> 画头像 -> 写黑色的字 -> 写红色的字, measureText获取文字宽度;
  • 防止弹幕重叠,分行且记录当前行是否可插入,弹幕随机行插入;
  • 弹幕滚出屏幕外时,移除此条弹幕;
  • 循环发射弹幕的实现。

代码

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>弹幕(头像,文字)</title>
</head> <body>
<canvas id="canvas" style="background: #333;"></canvas>
</body>
<script>
// 圆角矩形
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) </script> </html>

原生Canvas循环滚动弹幕(现金红包活动带头像弹幕)的更多相关文章

  1. marquee 实现首尾相连循环滚动效果

    <marquee></marquee>可以实现多种滚动效果,无需js控制.使用marquee标签不仅可以滚动文字,也可以滚动图片,表格等  marquee标签不是HTML3.2 ...

  2. Android实现真正的ViewPager【平滑过渡】+【循环滚动】!!!顺带还有【末页跳转】。

    实现真正的ViewPager[平滑过渡]+[循环滚动]!!!顺带还有[末页跳转]. 首先呢, 我要对网上常见的3种ViewPager的循环滚动方法做个概述.急需看真正实现方法的同志请选择性忽略下面这一 ...

  3. 基于html5可拖拽图片循环滚动切换

    分享一款基于html5可拖拽图片循环滚动切换.这是一款支持手机端拖拽切换的网站图片循环滚动特效.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div id="s ...

  4. 【转】Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址

    Android循环滚动广告条的完美实现,封装方便,平滑过渡,从网络加载图片,点击广告进入对应网址 关注finddreams,一起分享,一起进步: http://blog.csdn.net/finddr ...

  5. jQuery 实现列表自动滚动循环滚动显示新闻通知

    需求 页面中一个小区域循环滚动展示通知(公告.新闻.活动.图片等),并且鼠标hover时停止滚动并提示,鼠标离开后,继续滚动. 效果图 https://www.iguopin.com/index.ph ...

  6. IOS实现自动循环滚动广告--ScrollView的优化和封装

    一.问题分析 在许多App中,我们都会见到循环滚动的视图,比如广告,其实想实现这个功能并不难,用ScrollView就可以轻松完成,但是在制作的过程中还存在几个小问题,如果能够正确的处理好这些小问题, ...

  7. Jquery制作--循环滚动列表

    自己模仿JQ插件的写法写了一个循环滚动列表插件,支持自定义上.下.左.右四个方向,支持平滑滚动或者间断滚动两种方式,都是通过参数设置.JQ里面有些重复的地方,暂时没想到更好的方法去精简.不过效果还是可 ...

  8. Expression Blend4经验分享:文字公告无缝循环滚动效果

    这次分享一个类似新闻公告板的无缝循环滚动效果,相信很多项目都会应用到这个效果.之前我也百度了一下,网上的一些Silverlight的文字或图片滚动效果,都是一次性滚动的,如果要做到无缝循环滚动,多数要 ...

  9. ul 仿 table 循环滚动

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

随机推荐

  1. TOMCAT7不兼容spring boot 2.0

    找不到这个类,找不到那个类... Spring Boot 2.0 (and Spring Framework 5 upon which it builds) requires a Servlet 3. ...

  2. Nginx笔记总结十:Nginx日志切割

    1.Nginx日志切割 logrotate日志文件管理工具,通过cron程序定期执行,默认在cron默认程序的dayli目录下 [root@joker logrotate.d]# cat /etc/c ...

  3. idea 内存溢出解决方法

    在Run/Debug configuration 的 vm options里面输入 -server -XX:PermSize=128M -XX:MaxPermSize=256m 具体如下图:

  4. PHP常用接口数据过滤的方法

    <?php /** * global.func.php 公共函数库 */ /** * 返回经addslashes处理过的字符串或数组 * @param $string 需要处理的字符串或数组 * ...

  5. mysql-5.7.14-winx64解压版配置

    1.下载最新的MySQL文件并且解压 我的位置是 F:\mysql-5.7.14-winx64 2.F:\mysql-5.7.14-winx64\bin; 添加到环境变量-系统变量-PATH下 3.复 ...

  6. Docker学习笔记_08使用Rancher pipeline搭建基于容器的CICD

    CICD概述 CI-持续集成(Continuous Integration):频繁地将代码集成到主干的一种开发实践,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错 ...

  7. Luogu_2878_[USACO07JAN]保护花朵Protecting the Flowers

    题目描述 Farmer John went to cut some wood and left N (2 ≤ N ≤ 100,000) cows eating the grass, as usual. ...

  8. bzoj1432_[ZJOI2009]Function

    题目描述 有n 个连续函数fi (x),其中1 ≤ i ≤ n.对于任何两个函数fi (x) 和fj (x),(i != j),恰好存在一个x 使得fi (x) = fj (x),并且存在无穷多的x ...

  9. 吴裕雄--天生自然KITTEN编程:拾金币

  10. Flume 实战练习

    前期准备 了解Flume 架构及核心组件 Flume 架构及核心组件 Source : 收集(指定数据源从哪里获取) Channel : 聚集 Sink : 输出(把数据写到哪里去) 学习使用 Flu ...