-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimlrc.js
139 lines (137 loc) · 5.22 KB
/
simlrc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class SimLRC {
constructor(lrc, container = null) {
// 解析歌词
const lrcSpilitted = lrc.split("\n");
this.lrcParsed = {};
for (let lineNum in lrcSpilitted) {
const line = lrcSpilitted[lineNum];
const regex = /\[\d+\:\d+\.\d+\]/g;
const tags = (line.match(regex) || []).map(match => match.slice(1, -1));
const text = line.replace(regex, "").trim();
if (!tags || !text) continue;
tags.forEach(tag => {
const [minutes, seconds] = tag.split(':').map(Number);
const msTime = Math.round(minutes * 60000 + seconds * 1000);
if (msTime || msTime === 0) {
if (!this.lrcParsed[msTime]) this.lrcParsed[msTime] = [];
this.lrcParsed[msTime].push(text);
}
});
}
if (!Object.keys(this.lrcParsed).length) this.lrcParsed = {0: ["暂无歌词"]};
}
render(container, audio, options = {}) {
if (!container || !audio) return;
// 初始化配置项
const defaultOptions = {
blurStep: 1,
blurMin: 2,
blurMax: 5,
normalColor: "#00000088",
activeColor: "#000000",
clickUpdate: true,
multiLangSupport: true,
align: "center",
inactiveZoom: .8,
lineSpace: .8,
scrollTimeout: 3000,
};
options = Object.assign(defaultOptions, options);
// 渲染歌词HTML
container.innerHTML = "";
for (let timestamp in this.lrcParsed) {
const currentLrc = this.lrcParsed[timestamp];
if (options.multiLangSupport) {
// 启用多语言支持,则同时间戳不同歌词在同一个div渲染
const lrcDiv = document.createElement("div");
lrcDiv.dataset.stamp = timestamp;
currentLrc.forEach((text, index) => {
const textElement = document.createElement(index ? "small" : "span");
textElement.innerText = text;
lrcDiv.appendChild(textElement);
});
container.appendChild(lrcDiv);
} else {
// 禁用多语言支持,则同时间戳不同歌词分开渲染
currentLrc.forEach(text => {
const lrcDiv = document.createElement("div");
lrcDiv.dataset.stamp = timestamp;
lrcDiv.innerText = text;
container.appendChild(lrcDiv);
});
}
}
// 设置样式
container.classList.add("SimLRC");
container.style.setProperty("--align", options.align);
container.style.setProperty("--normalColor", options.normalColor);
container.style.setProperty("--activeColor", options.activeColor);
container.style.setProperty("--hoverColor", options.clickUpdate ? options.activeColor : options.normalColor);
container.style.setProperty("--inactiveZoom", options.inactiveZoom);
container.style.setProperty("--lineSpace", options.lineSpace + "em");
// 监听事件
const refreshLrcProgress = forceScroll => {
const currentTime = audio.currentTime * 1000;
let lrcEles = Array.from(container.getElementsByTagName("div"));
for (let index in lrcEles) {
let div = lrcEles[index];
if (div.dataset.stamp <= currentTime && (!div.nextElementSibling || div.nextElementSibling.dataset.stamp > currentTime)) {
// 执行回调
if (!div.classList.contains("active") && options.callback) options.callback(div.innerText);
if (!div.classList.contains("active") || forceScroll) {
// 取消用户滚动模式
if (forceScroll) {
container.classList.remove("scrolling");
clearTimeout(this.scrollTimeoutId);
}
// 设置为当前歌词并滚动
div.classList.add("active");
if (!container.classList.contains("scrolling")) div.scrollIntoView({ behavior: "smooth", block: "center" });
// 渲染歌词模糊效果
if (options.blurStep && options.blurMax) {
div.style.filter = "none";
const prevSiblings = [];
let prev = div.previousElementSibling;
while (prev) {
prevSiblings.push(prev);
prev = prev.previousElementSibling;
}
let next = div.nextElementSibling;
const nextSiblings = [];
while (next) {
nextSiblings.push(next);
next = next.nextElementSibling;
}
for (let index = 0; index <= Math.max(prevSiblings.length, nextSiblings.length); index++) {
const blurPixel = Math.min(options.blurMin + options.blurStep * index, options.blurMax);
if (prevSiblings[index]) prevSiblings[index].style.filter = `blur(${blurPixel}px)`;
if (nextSiblings[index]) nextSiblings[index].style.filter = `blur(${blurPixel}px)`;
}
}
}
} else div.classList.remove("active");
}
};
audio.addEventListener("timeupdate", () => { refreshLrcProgress(); });
if (options.clickUpdate) {
Array.from(container.getElementsByTagName("div")).forEach(div => {
div.onclick = () => { audio.currentTime = div.dataset.stamp / 1000; refreshLrcProgress(true); };
});
}
refreshLrcProgress();
// 处理用户滚动
const handleUserScroll = () => {
clearTimeout(this.scrollTimeoutId);
this.scrollTimeoutId = setTimeout(() => {
container.classList.remove("scrolling");
refreshLrcProgress(true);
}, options.scrollTimeout);
container.classList.add("scrolling");
}
container.onwheel = handleUserScroll;
container.onpointerdown = () => { this.pointerDown = true; };
container.onpointerup = () => { this.pointerDown = false; };
container.onpointerout = () => { this.pointerDown = false; };
container.onpointermove = () => { if (this.pointerDown) handleUserScroll(); };
}
}