HumanBreak/packages/legacy-ui/src/ui/danmaku.vue

226 lines
5.3 KiB
Vue

<template>
<div id="danmaku-div">
<div
:id="`danmaku-${one.id}`"
class="danmaku-one"
v-for="one of Danmaku.showList"
:key="one.id"
@mouseenter="mousein(one.id)"
@mouseleave="mouseleave(one.id)"
@touchstart="touchStart(one.id)"
>
<span class="danmaku-info">
<span
class="danmaku-like-icon"
:liked="likedMap[one.id] < 0"
@click="postLike(one)"
>
<like-filled />
</span>
<span class="danmaku-like-num">{{
Math.abs(likedMap[one.id])
}}</span>
</span>
<component :is="one.getVNode()"></component>
</div>
</div>
</template>
<script lang="ts" setup>
import { nextTick, onUnmounted, reactive, watch } from 'vue';
import { Danmaku } from '../danmaku';
import { LikeFilled } from '@ant-design/icons-vue';
import { mainSetting } from '../preset/settingIns';
import { debounce } from 'lodash-es';
interface ElementMap {
ele: HTMLDivElement;
danmaku: Danmaku;
hover: boolean;
top: number;
style: CSSStyleDeclaration;
}
const likedMap: Record<number, number> = reactive({});
const map = Danmaku.showMap;
const eleMap: Map<number, ElementMap> = new Map();
const liked = reactive<Record<number, boolean>>({});
const speed = mainSetting.getValue('ui.danmakuSpeed', 60);
const likeFn = (l: boolean, d: Danmaku) => {
liked[d.id] = l;
};
watch(Danmaku.showList, list => {
list.forEach(v => {
if (!eleMap.has(v.id)) {
nextTick(() => {
liked[v.id] = v.liked;
v.on('like', likeFn);
requestAnimationFrame(() => {
addElement(v.id);
});
});
}
});
});
function addElement(id: number) {
const danmaku = map.get(id);
if (!danmaku) return;
const div = document.getElementById(`danmaku-${id}`);
if (!div) return;
const ele: ElementMap = {
danmaku,
ele: div as HTMLDivElement,
hover: false,
top: -1,
style: getComputedStyle(div)
};
div.style.setProperty('--end', `${-div.scrollWidth}px`);
div.style.setProperty(
'--duration',
`${Math.floor((window.innerWidth + div.scrollWidth + 100) / speed)}s`
);
div.style.setProperty('left', ele.style.left);
div.addEventListener('animationend', () => {
danmaku.showEnd();
eleMap.delete(id);
delete likedMap[id];
});
eleMap.set(id, ele);
likedMap[id] = danmaku.liked ? -danmaku.likedNum : danmaku.likedNum;
calTop(id);
}
function mousein(id: number) {
const danmaku = eleMap.get(id)!;
danmaku.hover = true;
danmaku.ele.classList.add('danmaku-paused');
}
function mouseleave(id: number) {
const danmaku = eleMap.get(id)!;
danmaku.hover = false;
danmaku.ele.classList.remove('danmaku-paused');
}
const touchDebounce = debounce(mouseleave, 3000);
function touchStart(id: number) {
mousein(id);
touchDebounce(id);
}
function calTop(id: number) {
const danmaku = eleMap.get(id)!;
const fontSize = mainSetting.getValue('screen.fontSize', 16) * 1.25 + 15;
const used: Set<number> = new Set();
eleMap.forEach(v => {
const { ele, style } = v;
const pos = parseInt(style.transform.slice(19, -4));
const width = ele.scrollWidth;
if (
pos <= window.innerWidth + 200 &&
pos + width >= window.innerWidth
) {
used.add(v.top);
}
});
let i = -1;
danmaku.top = 0;
danmaku.ele.style.top = `20px`;
while (++i < 20) {
if (!used.has(i)) {
danmaku.top = i;
danmaku.ele.style.top = `${fontSize * i + 20}px`;
break;
}
}
}
async function postLike(danmaku: Danmaku) {
const res = await danmaku.triggerLike();
const liked = res.data.liked;
likedMap[danmaku.id] = liked ? -danmaku.likedNum : danmaku.likedNum;
}
onUnmounted(() => {});
</script>
<style lang="less" scoped>
#danmaku-div {
position: fixed;
width: 0;
height: 0;
left: 0;
top: 0;
overflow: visible;
font-size: 150%;
display: flex;
align-items: center;
z-index: 100;
}
.danmaku-one {
--end: 0;
--duration: 5s;
position: fixed;
left: 0;
transform: translateX(100vw);
width: max-content;
white-space: nowrap;
text-wrap: nowrap;
padding: 0 5px;
display: flex;
align-items: center;
animation: danmaku-roll linear var(--duration) forwards;
animation-play-state: running;
}
.danmaku-one:hover {
background-color: #0006;
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 {
text-shadow: 1px 1px 1px black, 1px -1px 1px black, -1px 1px 1px black,
-1px -1px 1px black;
}
.danmaku-like-num {
font-size: 75%;
}
.danmaku-like-icon {
transition: color 0.1s linear;
color: white;
}
.danmaku-like-icon[liked='true'],
.danmaku-like-icon:hover {
color: aqua;
}
</style>