我本身是一个十年的英雄联盟玩家,EDG夺冠当天兴奋不已,好像国足进世界杯那般。同时陈奕迅的《孤勇者》配合上我想吹爆的《双城之战》最近也上映了,燃起了我这网瘾青年久违的激情。
我考虑决定做一个主题弹幕网页,纪念这一次来之不易的冠军,业余时间熬夜做完并把代码开源。
网页入口:http://www.lhqcloud.top/EDG_NB/pages/
Github: https://github.com/doterlin/EDG_NB
兼容性:现代浏览器,暂未适配移动设备,请在PC浏览。
技术选型:前端基本都是原生CSS3 + ES5编写,后端是node + socket.io + mysql
有同学恶意刷弹幕...一天多了十几万条数据,我在11月15日会清空数据。
运行与部署
安装依赖:
npm install
建数据库edg_nb
, 导入service/bullet.sql
建好表。
将pages/index.html
部署到静态目录或者本地直接打开到浏览器运行, 然后运行node服务:
node ./service/app.js
websocket进程比较脆弱,你可以使用pm2
守护:
pm2 start ./service/app.js
前端
前端的动画效果自己设计水平有限,氛围感不是很强,但全是原生写,不依赖第三方库。为了增加更多趣味,结合EDG的BO5英雄技能设计了弹幕的动画效果,在构想效果上就绞尽脑汁,代码编写反倒觉得舒服点。
背景动画
背景动画类的实现如下,需要配合css,样式部分可在static/style.css
找到对应的类查看。
// Bg类,画背景
// @param parentElement 画背景的节点
function Bg(parentElement, randomTextArray) {
this.parentElement = parentElement;
this.randomTextArray = randomTextArray;
this.parentW = parentElement.clientWidth,
this.parentH = parentElement.clientHeight;
this.rowWidth = 40;
this.rowMargin = 50;
this.rowClassName = 'bg-row';
}
// 开始画
Bg.prototype.draw = function () {
this.drawRow();
}
Bg.prototype.drawRow = function () {
// parent节点能画多少列
var rowNumber = Math.ceil(this.parentW / (this.rowWidth + this.rowMargin));
for (let i = 0; i < rowNumber; i++) {
var el = document.createElement('div');
el.setAttribute('class', 'bg-row');
this.parentElement.append(el);
new ScrollText(el, this.randomTextArray);
}
}
Bg.prototype.destroy = function () {
this.parentElement.innerHTML = '';
}
背景文字类:
// 滚动文字类
function ScrollText(rowElement, textArray) {
this.rowElement = rowElement;
this.textArray = textArray;
this.aniTime = 10000; //文字动画滚动时长
this.createRomdomTextEl();
}
ScrollText.prototype.createRomdomTextEl = function () {
var text = this.textArray[Math.floor((Math.random() * this.textArray.length))].toUpperCase(); //随机取出一个
var p = document.createElement('p');
p.setAttribute('class', 'bg-text');
p.innerHTML = text;
var ts = this;
p.className = 'bg-text slideDown';
this.rowElement.append(p);
setTimeout(function () {
ts.rowElement.removeChild(p)
}, this.aniTime);
setTimeout(() => {
ts.createRomdomTextEl();
}, (Math.random() * 1 + 2.2) * 1000);
}
弹幕
弹幕功能由动画效果和websocket连接交互构成。动画核心代码:
// 弹幕类
// @param text 弹幕文字
// @param aniType 弹幕动画类型
function Bullet({ text, aniType, isCustomer }, parentElement) {
this.text = text;
this.aniType = aniType;
this.isCustomer = isCustomer;
this.parentElement = parentElement;
this.el = null;
this.aniTime = aniType === '1' ? 4000 : 10000; //弹幕留存时间
this.create()
}
Bullet.prototype.create = function () {
this.el = document.createElement('div');
var innerHTML = this.text;
if (this.isCustomer) {
innerHTML += '<span class="vip-tag"> 来自尊贵的程序员</span>';
}
if (this.aniType === '6') {
innerHTML = '<img class="img-0-21-0" src="../static/imgs/0-21-0.webp"/><div>' + innerHTML + '</div>'
}
this.el.innerHTML = innerHTML;
this.el.setAttribute('class', 'bullet-text bullet-ani' + this.aniType);
//初始高度随机
this.el.setAttribute('style', 'top: ' + Math.random() * 90 + '%;');
}
Bullet.prototype.go = function () {
var ts = this;
ts.parentElement.append(ts.el);
setTimeout(function () {
ts.parentElement.removeChild(ts.el)
}, this.aniTime)
}
websocker连接与处理:
// websocket
function createSocketIo() {
var socket = io('localhost:3300', { transports: ['websocket'] });
// 监听连接
socket.on('connect', function () {
console.log('%c connect success.', 'color: #690');
});
//监听计数
socket.on('count', function (data) {
countText1.innerHTML = data.C1;
countText2.innerHTML = data.C2;
});
// 监听数据
socket.on('add', function (data) {
new Bullet(data, parentEl).go(true)
});
return socket;
}
后端
后端要实现的功能很简单,一是建立websocket连接和推送消息,二是在数据库记录和查询用户的弹幕数。
主要代码:
const db = require("./db")
let http = require("http");
let fs = require("fs");
let server = http.createServer((req, res) => {
res.end('Nothing!')
})
server.listen(3300, () => {
console.log("http服务已启动");
})
var io = require('socket.io')(server);
// 连接
io.on('connection', function (socket) {
getCount(res => {
socket.emit('count', res)
})
// 监听
socket.on('add', function (data, a2, a3) {
const _data = {
...data,
isCustomer: isCustomer(data.text)
}
socket.emit('add', _data);
addRecord(_data, socket);
getCount(res => {
socket.emit('count', res)
})
});
});
其中db.js
是封装的mysql查询模块。
一个彩蛋
项目中“藏”了一个小彩蛋
给身为召唤师的程序员朋友们,我相信你们很快就能找到这个彩蛋。
如果真需要触发彩蛋的提示,可以在console控制台查看(但我相信大部分看到这篇文章的朋友都不需要提示)。
欢迎大家贡献弹幕,如果你喜欢这个网页欢迎你分享给其他召唤师。后面到一定时间我会出一个统计结果,有问题随时交流。断剑重铸之日,其势归来之时! 加油,召唤师。