效果

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. 为什么说iPhone无望恢复中国市场?

    直到现在还记得,iPhone 4在国内当时引发的追捧狂潮.彼时iPhone 4绝对是一机难求,上至土豪下至学生都以拥有iPhone 4为荣.发售接近一年后仍然需要加价,价格动辄达到七八千元,真正成为了 ...

  2. 关于angular2跳路由防止页面刷新的做法(Angular2路由重载)

    simpleReuseStrategy.ts // 创建重用策略 import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStr ...

  3. 翻转链表和k个一组翻转以及两两反转

    一.输入一个链表,输出反转后的链表. 非递归实现: # -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.v ...

  4. Java IO: PipedInputStream

    原文链接 作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) PipedInputStream可以从管道中读取字节流数据,代码如下: 01 InputSt ...

  5. chap1-HttpRequest测试类

    # HttpRequest测试类, 封装请求方法 import requests class HttpRequest: def http_request(self, url, method, data ...

  6. 吴裕雄--天生自然HTML学习笔记:HTML 元素

    HTML 文档由 HTML 元素定义. HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href="default. ...

  7. JS做深度学习1——偶然发现与入门

    JS做深度学习1--偶然发现与入门 不久前,我初次涉猎了Node.js,并且使用它开发了毕业设计的WEB模块,然后通过在Node中调用系统命令执行Python文件方式实现了深度学习功能模块的对接,Py ...

  8. Analysis of Hello2 source code

    Hello2 应用程序是一个 Web 模块,它使用 Java Servlet 技术来显示问候语和响应,使用的是 Java Servlet 技术. 该应用程序源代码在 tutorial-examples ...

  9. node新人

    node  使用   http和express创建服务器环境 如 apache  iis等 不需要配置一堆文件  为啥使用node  省事  v8引擎  异步js 不影响浏览者浏览网站 redis   ...

  10. 将js进行到底:node学习9

    node.js数据库篇--Mongoose ODM 介绍mongoose 几乎所有的语言都有原生数据库连接驱动,这个我们上一回已经了解了,比如java的jdbc,mysql-connector,但是实 ...