Skip to content

Commit

Permalink
✨ feat: banner 添加弹幕动画插件
Browse files Browse the repository at this point in the history
  • Loading branch information
白云苍狗 committed Mar 20, 2024
1 parent ebada36 commit 8bada69
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 6 deletions.
25 changes: 25 additions & 0 deletions packages/hexo-theme-async-ts/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ declare interface Window {
start_time?: string;
prefix?: string;
};
danmu: DanMuOptions;
};

PAGE_CONFIG: {
Expand All @@ -98,10 +99,34 @@ declare interface Window {

changeGiscusTheme: () => void;
show_date_time: () => void;
danMu: (fun: DanMuFun) => void;

// 三方插件
Fancybox: any;
Swiper: any;
Swup: any;
fjGallery: any;
}

declare type DanMuData = {
id: string | number;
text: string;
url?: string;
avatar?: string;
};

declare type DanMuMode = 'half' | 'top' | 'full';

declare type DanMuOptions = {
el: string;
speed?: number;
gapWidth?: number;
gapHeight?: number;
avatar?: boolean;
height?: number;
delayRange?: number;
mode?: DanMuMode;
align?: string;
};

declare type DanMuFun = () => Promise<DanMuData | DanMuData[]>;
4 changes: 2 additions & 2 deletions packages/hexo-theme-async-ts/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,12 +627,12 @@ export function ShowDateTime() {
}
}

window.asyncFun = globalFun;

/**
* 初始化
*/
export function ready() {
window.asyncFun = globalFun;

PrintCopyright();

/* loading animate */
Expand Down
265 changes: 265 additions & 0 deletions packages/hexo-theme-async-ts/src/plugins/danmu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/**
* 弹幕滚动
*/
class DanMuKu {
box = null; // 弹幕容器
boxSize = {
width: 0, // 容器宽度
height: 0, // 容器高度
};

rows = 0; // 行数
dataList: Array<Array<DanMuData>> = []; // 弹幕数据 二维数组
indexs: number[] = []; // 最新弹幕下标
idMap = {}; // 记录已出现 id

avatar = false; // 是否显示头像
speed = 20; // 弹幕每秒滚动距离
height = 36; // 弹幕高度
gapWidth = 20; // 弹幕前后间隔
gapHeight = 20; // 弹幕上下间隔
delayRange = 5000; // 延时范围
align = 'center'; // 轨道纵轴对齐方式

animates = new Map(); // 动画执行元素
stageAnimates = new Map();

constructor(options: DanMuOptions) {
this.update(options);
}

_divisor(mode: DanMuMode) {
let divisor = 0.5;
switch (mode) {
case 'half':
divisor = 0.5;
break;
case 'top':
divisor = 1 / 3;
break;
case 'full':
divisor = 1;
break;
default:
break;
}
return divisor;
}

/**
* 初始化生成弹道
*/
_initBarrageList() {
if (!this.box.querySelector(`.barrage-list`)) {
const barrage = document.createElement('div');
barrage.className = 'barrage-list';
barrage.setAttribute('style', `gap:${this.gapHeight}px;justify-content: ${this.align};display: flex;height: 100%;flex-direction: column;overflow: hidden;`);

for (let i = 0; i < this.rows; i++) {
const item = document.createElement('div');
item.className = `barrage-list-${i}`;
item.setAttribute('style', `height:${this.height}px;position:relative;`);
barrage.appendChild(item);
this.dataList[i] = [];
}
this.box.appendChild(barrage);
}
}

_pushOne(data: DanMuData) {
const lens = this.dataList.map(item => item.length);
const min = Math.min(...lens);
const index = lens.findIndex(i => i === min);
this.dataList[index].push(data);
}

_pushList(data: DanMuData[]) {
const list = this._sliceRowList(this.rows, data);
list.forEach((item, index) => {
if (this.dataList[index]) {
this.dataList[index] = this.dataList[index].concat(...item);
} else {
this.dataList[index] = item;
}
});
}

_sliceRowList(rows: number, list: DanMuData[]) {
const sliceList = [];
const perNum = Math.round(list.length / rows);
for (let i = 0; i < rows; i++) {
let arr = [];
if (i === rows - 1) {
arr = list.slice(i * perNum);
} else {
i === 0 ? (arr = list.slice(0, perNum)) : (arr = list.slice(i * perNum, (i + 1) * perNum));
}
sliceList.push(arr);
}
return sliceList;
}

/**
* 创建弹幕,并执行动画
* @param {*} item 弹幕数据
* @param {*} barrageIndex 弹幕轨道下标
* @param {*} dataIndex 弹幕数据下标
* @returns
*/
_dispatchItem(item: DanMuData, barrageIndex: number, dataIndex: number) {
if (!item || this.idMap[item.id]) {
return;
}

this.idMap[item.id] = item.id;

const parent = this.box.querySelector(`.barrage-list-${barrageIndex}`);
let danmuEl = document.createElement('div');
danmuEl.className = 'danmu-item';
danmuEl.setAttribute(
'style',
`
height:${this.height}px;
display:inline-flex;
position: absolute;
right: 0;
background-color: var(--trm-danmu-bg-color,#fff);
color: var(--trm-danmu-color,#000);
border-radius: 32px;
padding: ${this.avatar ? `4px 8px 4px 4px` : '4px 8px'};
align-items: center; `,
);
danmuEl.innerHTML = `
${this.avatar ? `<img class="danmu-avatar" style="display: inline-block;width:${this.height - 8}px;height:${this.height - 8}px;border-radius:50%;margin-right:6px;" src="${item.avatar}">` : ''}
<div class="danmu-text" style="text-wrap:nowrap;overflow:hidden;text-overflow:ellipsis;">${item.text}</div>`;
parent.appendChild(danmuEl);

const { width } = danmuEl.getBoundingClientRect();
danmuEl.style.width = `${width}px`;
const allTime = ((this.boxSize.width + width) / this.speed) * 1000;
const pastTime = ((width + this.gapWidth) / (this.boxSize.width + width)) * allTime;

if (dataIndex < this.dataList[barrageIndex].length) {
const animate = danmuEl.animate({ transform: ['translateX(100%)', `translateX(-${this.gapWidth}px)`] }, { duration: pastTime, fill: 'forwards' });

this.stageAnimates.set(animate, animate);

animate.onfinish = () => {
this.stageAnimates.delete(animate);
this.indexs[barrageIndex] = dataIndex + 1;
this._dispatchItem(this.dataList[barrageIndex][dataIndex + 1], barrageIndex, dataIndex + 1);
};
}

const animate = danmuEl.animate({ transform: ['translateX(100%)', `translateX(-${this.boxSize.width}px)`] }, { duration: allTime, fill: 'forwards' });

animate.onfinish = () => {
this.animates.delete(animate);
danmuEl.remove();
danmuEl = null;
};

this.animates.set(animate, animate);
}

/**
* 开始滚动弹幕
*/
_run() {
const len = this.dataList.length;
for (let barrageIndex = 0; barrageIndex < len; barrageIndex++) {
const row = this.dataList[barrageIndex];
let dataIndex = this.indexs[barrageIndex];

if (!dataIndex && dataIndex !== 0) {
dataIndex = this.indexs[barrageIndex] = 0;
}

row[dataIndex] && setTimeout(() => this._dispatchItem(row[dataIndex], barrageIndex, dataIndex), Math.random() * this.delayRange);
}
}

/**
* 暂停滚动
*/
pause() {
this.animates.forEach(item => item.pause());
this.stageAnimates.forEach(item => item.pause());
return this;
}

/**
* 开始滚动
*/
play() {
this.animates.forEach(item => item.play());
this.stageAnimates.forEach(item => item.play());
return this;
}

/**
* 清除弹幕
*/
clear() {
this.dataList = [];
this.indexs = [];
this.idMap = {};
this.animates.clear();
this.stageAnimates.clear();
if (this.box.querySelector('.barrage-list')) {
this.box.removeChild(this.box.querySelector('.barrage-list'));
}
return this;
}

update(options: DanMuOptions) {
const box = document.querySelector(options.el);
if (box) {
const size = box.getBoundingClientRect();
this.box = box;
this.boxSize = size;
this.speed = options.speed ?? this.speed;
this.gapWidth = options.gapWidth ?? this.gapWidth;
this.gapHeight = options.gapHeight ?? this.gapHeight;
this.avatar = options.avatar ?? this.avatar;
this.height = options.height ?? this.height;
this.delayRange = options.delayRange ?? this.delayRange;
this.align = options.align ?? this.align;
this.rows = parseInt(((size.height * this._divisor(options.mode ?? 'half')) / (this.height + this.gapHeight)).toString());
this.clear();
} else {
throw new Error(`未找到容器 ${options.el}`);
}
return this;
}

/**
* 添加弹幕数据
* @param {*} data
*/
pushData(data: DanMuData | DanMuData[]) {
this._initBarrageList();
switch (Object.prototype.toString.apply(data)) {
case '[object Object]':
this._pushOne(data as DanMuData);
break;
case '[object Array]':
this._pushList(data as DanMuData[]);
break;
}
this._run();
return this;
}
}

const danmuku = new DanMuKu(window.ASYNC_CONFIG.danmu);

window.danMu = async (fun: DanMuFun) => {
danmuku.update(window.ASYNC_CONFIG.danmu).pushData(await fun());

if (window.ASYNC_CONFIG.swup) {
document.addEventListener('swup:contentReplaced', async function () {
danmuku.update(window.ASYNC_CONFIG.danmu).pushData(await fun());
});
}
};
4 changes: 4 additions & 0 deletions packages/hexo-theme-async/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ assets:
rowcss: css/plugins/bootstrap.row.css
typing: js/plugins/typing.js
search: js/plugins/local_search.js
danmu: js/plugins/danmu.js
main: js/main.js
# Icon configuration
icons:
Expand Down Expand Up @@ -98,6 +99,9 @@ top_bars:
# Banner settings
banner:
use_cover: false
danmu:
enable: false
el: .trm-banner
default:
type: img
bgurl: /img/banner.png
Expand Down
8 changes: 4 additions & 4 deletions packages/hexo-theme-async/layout/_partial/script.ejs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<!-- Plugin -->
<%- partial('../_third-party/plugin',{ loadtype: 'js' }) %>

<!-- CDN -->
<%- partial('../_third-party/cdn',{ cdntype: 'body' }) %>

<% if(hexo_env('cmd') !== 'server'){ %>
<!-- Service Worker -->
<%- partial('../_third-party/sw') %>
<!-- baidu push -->
<%- partial('../_third-party/seo/baidu-push') %>
<% } %>

<script id="async-script" src="<%= url_for(theme.assets.plugin.hexo_theme_async.main) %>?v=<%= theme.version %>"></script>
<script id="async-script" src="<%= url_for(theme.assets.plugin.hexo_theme_async.main) %>?v=<%= theme.version %>"></script>

<!-- CDN -->
<%- partial('../_third-party/cdn',{ cdntype: 'body' }) %>
4 changes: 4 additions & 0 deletions packages/hexo-theme-async/layout/_third-party/plugin.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,8 @@ plugin = theme.assets.plugin;
<% } %>
<% } %>
<% if(theme.banner.danmu.enable) {%>
<script src="<%= url_for(plugin.hexo_theme_async.danmu) %>?v=<%= theme.version %>"></script>
<% } %>
<% } %>
1 change: 1 addition & 0 deletions packages/hexo-theme-async/scripts/helper/async_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ hexo.extend.helper.register('async_config', function () {
start_time: theme.footer?.live_time?.enable ? theme.footer.live_time.start_time : '',
prefix: __(theme.footer.live_time.prefix),
},
danmu: theme.banner.danmu,
};

// 随便封面
Expand Down

0 comments on commit 8bada69

Please sign in to comment.