feat: 弹幕性能优化

This commit is contained in:
unanmed 2024-04-25 17:41:37 +08:00
parent 6202dbce62
commit f2fecbb6c6
4 changed files with 65 additions and 52 deletions

View File

@ -23,8 +23,9 @@
[] 苍蓝之殿 1: 红蓝黄门转换 [] 苍蓝之殿 1: 红蓝黄门转换
[] 苍蓝之殿 2: [] 苍蓝之殿 2:
[] 苍蓝之殿 3: [] 苍蓝之殿 3: 传送门
[] 苍蓝之殿 4: [] 苍蓝之殿 4:
[] 苍蓝之殿中: 让我们把这些东西结合起来...
### 成就 ### 成就
@ -96,7 +97,7 @@ dam4.png ---- 存档 59
[] 重构技能树结构 [] 重构技能树结构
[] 技能树允许自动升级 [] 技能树允许自动升级
[] 重构装备系统 [] 重构装备系统
[] 弹幕系统 [x] 弹幕系统
[x] 优化各种 ui [x] 优化各种 ui
[] 怪物脚下加入阴影 [] 怪物脚下加入阴影
[x] 着色器特效 [x] 着色器特效

View File

@ -109,7 +109,7 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
strokeColor: string = 'black'; strokeColor: string = 'black';
private posted: boolean = false; private posted: boolean = false;
private vNode?: VNode; vNode?: VNode;
private posting: boolean = false; private posting: boolean = false;
/** /**
@ -351,6 +351,7 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
* *
*/ */
show() { show() {
if (this.showing) return;
this.showing = true; this.showing = true;
Danmaku.showList.push(this); Danmaku.showList.push(this);
Danmaku.showMap.set(this.id, this); Danmaku.showMap.set(this.id, this);
@ -361,6 +362,7 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
* *
*/ */
showEnd() { showEnd() {
if (!this.showing) return;
this.showing = false; this.showing = false;
deleteWith(Danmaku.showList, this); deleteWith(Danmaku.showList, this);
Danmaku.showMap.delete(this.id); Danmaku.showMap.delete(this.id);

View File

@ -31,19 +31,16 @@ import { mainSetting } from '@/core/main/setting';
import { debounce } from 'lodash-es'; import { debounce } from 'lodash-es';
interface ElementMap { interface ElementMap {
pos: number;
ele: HTMLDivElement; ele: HTMLDivElement;
danmaku: Danmaku; danmaku: Danmaku;
style: CSSStyleDeclaration;
width: number;
hover: boolean; hover: boolean;
top: number; top: number;
style: CSSStyleDeclaration;
} }
const map = Danmaku.showMap; const map = Danmaku.showMap;
const eleMap: Map<number, ElementMap> = new Map(); const eleMap: Map<number, ElementMap> = new Map();
const liked = reactive<Record<number, boolean>>({}); const liked = reactive<Record<number, boolean>>({});
const ticker = new Ticker();
const speed = mainSetting.getValue('ui.danmakuSpeed', 60); const speed = mainSetting.getValue('ui.danmakuSpeed', 60);
@ -53,10 +50,10 @@ const likeFn = (l: boolean, d: Danmaku) => {
watch(Danmaku.showList, list => { watch(Danmaku.showList, list => {
list.forEach(v => { list.forEach(v => {
liked[v.id] = v.liked;
v.on('like', likeFn);
if (!eleMap.has(v.id)) { if (!eleMap.has(v.id)) {
nextTick(() => { nextTick(() => {
liked[v.id] = v.liked;
v.on('like', likeFn);
requestAnimationFrame(() => { requestAnimationFrame(() => {
addElement(v.id); addElement(v.id);
}); });
@ -71,59 +68,39 @@ function addElement(id: number) {
const div = document.getElementById(`danmaku-${id}`); const div = document.getElementById(`danmaku-${id}`);
if (!div) return; if (!div) return;
const style = getComputedStyle(div);
const ele: ElementMap = { const ele: ElementMap = {
danmaku, danmaku,
pos: window.innerWidth + 100,
ele: div as HTMLDivElement, ele: div as HTMLDivElement,
style,
width: parseInt(style.width),
hover: false, hover: false,
top: -1 top: -1,
style: getComputedStyle(div)
}; };
div.style.setProperty('--end', `${-div.scrollWidth}px`);
div.style.setProperty(
'--duration',
`${Math.floor((window.innerWidth + div.scrollWidth) / speed)}s`
);
div.style.setProperty('left', ele.style.left);
div.addEventListener('animationend', () => {
danmaku.showEnd();
eleMap.delete(id);
});
eleMap.set(id, ele); eleMap.set(id, ele);
calTop(id); calTop(id);
} }
let lastTime = 0;
ticker.add(time => {
const dt = (time - lastTime) / 1000;
const toDelete: number[] = [];
eleMap.forEach((value, id) => {
const { danmaku, ele, style, width, hover } = value;
if (!hover) {
const dx = dt * speed;
value.pos -= dx;
ele.style.transform = `translateX(${value.pos.toFixed(2)}px)`;
}
if (value.pos + width < 0) {
toDelete.push(id);
}
});
lastTime = time;
toDelete.forEach(v => {
eleMap.delete(v);
const danmaku = map.get(v);
if (danmaku) {
danmaku.showEnd();
}
map.delete(v);
});
});
function mousein(id: number) { function mousein(id: number) {
const danmaku = eleMap.get(id)!; const danmaku = eleMap.get(id)!;
danmaku.hover = true; danmaku.hover = true;
danmaku.ele.classList.add('danmaku-paused');
} }
function mouseleave(id: number) { function mouseleave(id: number) {
const danmaku = eleMap.get(id)!; const danmaku = eleMap.get(id)!;
danmaku.hover = false; danmaku.hover = false;
danmaku.ele.classList.remove('danmaku-paused');
} }
const touchDebounce = debounce(mouseleave, 3000); const touchDebounce = debounce(mouseleave, 3000);
@ -138,15 +115,19 @@ function calTop(id: number) {
const used: Set<number> = new Set(); const used: Set<number> = new Set();
eleMap.forEach(v => { eleMap.forEach(v => {
const { pos, width } = v; const { ele, style } = v;
const pos = parseInt(style.transform.slice(19, -4));
const width = ele.scrollWidth;
if ( if (
pos <= window.innerWidth + 125 && pos <= window.innerWidth + 200 &&
pos + width >= window.innerWidth + 75 pos + width >= window.innerWidth
) { ) {
used.add(v.top); used.add(v.top);
} }
}); });
let i = -1; let i = -1;
danmaku.top = 0;
danmaku.ele.style.top = `20px`;
while (++i < 20) { while (++i < 20) {
if (!used.has(i)) { if (!used.has(i)) {
danmaku.top = i; danmaku.top = i;
@ -156,9 +137,7 @@ function calTop(id: number) {
} }
} }
onUnmounted(() => { onUnmounted(() => {});
ticker.destroy();
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -176,6 +155,8 @@ onUnmounted(() => {
} }
.danmaku-one { .danmaku-one {
--end: 0;
--duration: 5s;
position: fixed; position: fixed;
left: 0; left: 0;
transform: translateX(100vw); transform: translateX(100vw);
@ -185,11 +166,27 @@ onUnmounted(() => {
padding: 0 5px; padding: 0 5px;
display: flex; display: flex;
align-items: center; align-items: center;
animation: danmaku-roll linear var(--duration) forwards;
animation-play-state: running;
} }
.danmaku-one:hover { .danmaku-one:hover {
background-color: #0006; background-color: #0006;
border-radius: 5px; border-radius: 5px;
animation-play-state: paused;
}
.danmaku-one .danmaku-paused {
animation-play-state: paused;
}
@keyframes danmaku-roll {
0% {
transform: translateX(100vw);
}
100% {
transform: translateX(var(--end));
}
} }
.danmaku-info { .danmaku-info {

View File

@ -27,7 +27,7 @@
id="danmaku-input-input" id="danmaku-input-input"
:max-length="200" :max-length="200"
v-model:value="inputValue" v-model:value="inputValue"
placeholder="请在此输入弹幕" placeholder="请在此输入弹幕,显示中括号请使用\[或\]"
autocomplete="off" autocomplete="off"
@change="input(inputValue)" @change="input(inputValue)"
@pressEnter="inputEnter()" @pressEnter="inputEnter()"
@ -302,7 +302,20 @@ function input(value: string) {
if (size > 200) { if (size > 200) {
tip('warning', '弹幕长度超限!'); tip('warning', '弹幕长度超限!');
} }
danmaku.text = value;
const before = danmaku.text;
const { info, ret } = logger.catch(() => {
danmaku.text = value;
return danmaku.parse();
});
if (info.length > 0) {
if (info[0].code === 4) {
tip('error', '请检查中括号匹配');
danmaku.text = before;
} else {
danmaku.vNode = ret;
}
}
} }
function inputEnter() { function inputEnter() {