效果

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. 用两个栈实现一个队列(C++)

    分析 栈:后进先出 队列:先进先出 要使用两个栈实现队列(先进先出),主要思路是 1.插入一个元素:直接将元素插入stack1即可. 2.删除一个元素:当stack2不为空时 ,直接弹出栈顶元素,当s ...

  2. 多线程并发 了解 Thread

    通过上一篇 多线程并发 (一) 了解 Java 虚拟机 - JVM 了解了java 虚拟机的构成以及对象的创建等.从Java虚拟机栈我们知道每当我们创建一个线程JVM就会给我们的线程分配一个私有的内存 ...

  3. <JZOJ5912>VanUSee

    挺魔的 反正我考场上想不到233333333333333 #include<cstdio> #include<cstring> #include<iostream> ...

  4. 吴裕雄--天生自然 R语言开发学习:高级数据管理(续三)

    #-----------------------------------# # R in Action (2nd ed): Chapter 5 # # Advanced data management ...

  5. python IO多路复用版FTP

    需求: 实现文件上传及下载功能 支持多连接并发传文件 使用select or selectors

  6. 变身六次失去核心的小米Note还能火吗

    奥特曼变身有时间限制,因此我们总是希望它多变几次身,从而把小怪兽打得嗷嗷叫.但对于科技产品来说,不断推出"变身版",似乎总有江河日下.大势已去之感.三星形形色色的复仇者联盟S6版, ...

  7. Swift 浅谈Struct与Class

    讨论Struct与Class之前,我们先来看一个概念:Value Type(值类型),Reference Type(引用类型): 1. 值类型的变量直接包含他们的数据,对于值类型都有他们自己的数据副本 ...

  8. Serverless 每周小报-20190610

    微软和 Red Hat 开源 KEDA:Fission 发布 1.3.0 博客精选 10 THINGS TO KNOW ABOUT SERVERLESS COMPUTING - BEFORE YOU ...

  9. 关于vue+element-ui项目的分页,返回默认显示第一页的问题解决

    关于vue+element-ui项目的分页,返回默认显示第一页的问题解决     问题描述 当前页面如下: 然后点击页码跳到第3页,然后在第三页点击页面链接跳转到新的页面 然后在新页面点击返回按钮,返 ...

  10. 脚本化处理linux云服务器第二硬盘初始化

    #!/usr/bin/bash # 可以带参数 method=$ size=$ mydir=$ [ $#== ]&&{ echo -e "Missing parameter! ...