月亮照山川
本帖最后由 亚伦影音工作室 于 2025-8-11 20:50 编辑 <br /><br /><style>#bj {
position: relative;
width: 1280px;
height: 720px;
margin-left: -300px;
margin-top: 10px;
overflow: hidden;z-index:12345;
background: ;
}
.intro {margin: 0px0px;z-index:2;
width: 100%;
height:100%;
position: absolute;
background:url(https://pic.3gbizhi.com/uploadmark/20231030/babd94bc259b314d3ee1fb9283557361.webp),repeating-linear-gradient(57deg, rgba(250, 4, 10, 0.55) 0%,rgba(73, 185, 41, 0.45) 28%,rgba(4, 38, 245, 0.34) 50%,rgba(73, 185, 41, 0.45) 72%,rgba(250, 4, 10, 0.55) 100%);
background-size: cover;
background-blend-mode: hard-light;
animation: hue-rotate 3s linear infinite;
}
@keyframes hue-rotate {
from {
filter: hue-rotate(0);
}
to {
filter: hue-rotate(360deg);
}
}
#balloonCanvas{ z-index:3;
pointer-events: none;
position: absolute;overflow: hidden;
left:0;top: 0px;
}
.lyrics{margin: 0;z-index: 20;
top: 89%;
left: 50%;
transform: translate(-50%, -50%);
height: 100px; /* 调整高度,只容纳当前歌词 */
text-align: center;
position: absolute;
}
.lyric-line{
width: 100%;
position: relative;
height: 60px;
overflow: visible;
font: 300 50px '华文隶书', sans-serif;
line-height: 60px;
text-align: left;
white-space: nowrap; /* 禁止换行 */
filter: drop-shadow(#fff 1px 0 0) drop-shadow(#fff 0 1px 0) drop-shadow(#fff -1px 0 0) drop-shadow(#fff 0 -1px 0);
}
.lyric-mask {
position: absolute;
top: 0;
left: 0;
width: 0;
overflow: hidden;
color: #8B4513;
height: 100%;
white-space: nowrap;
}
.lyric-original {
color: #ag0000;
white-space: nowrap;
}
</style>
<divid="bj">
<div class='intro'></div>
<canvas id="balloonCanvas" ></canvas>
<div class="lyrics" >
<div class="lyric-line">
<div class="lyric-mask"></div>
<div class="lyric-original"></div>
</div>
</div>
</div>
<audio id="audio" src="https://s2.cldisk.com/sv-w9/audio/d3/49/5a/c4e08ea6a20c3ce16609673ff5697fc6/audio.mp3" autoplay loop></audio>
<script>
const balloonCanvas = document.getElementById('balloonCanvas');
const balloonCtx = balloonCanvas.getContext('2d');
balloonCtx.imageSmoothingEnabled = true;
let mouseX = 0;
let mouseY = 0;
let isMouseMoving = false;
const balloons = [];
const balloonColors = [
'red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink'
];
const balloonSizeRange = ;
balloonCanvas.width = window.innerWidth;
balloonCanvas.height = window.innerHeight;
document.addEventListener('mousemove', function(event) {
mouseX = event.clientX;
mouseY = event.clientY;
isMouseMoving = true;
const speed = calculateMouseSpeed(event);
const balloonCount = Math.min(Math.floor(speed * 1), 2);
for (let i = 0; i < balloonCount; i++) {
createBalloon();
}
setTimeout(() => {
isMouseMoving = false;
}, 200);
});
document.addEventListener('click', function(event) {
mouseX = event.clientX;
mouseY = event.clientY;
for (let i = 0; i < 3; i++) {
createBalloon(true);
}
});
const images = document.getElementsByTagName('img');
for (let i = 0; i < images.length; i++) {
images.addEventListener('click', function() {
balloons.forEach(balloon => {
const angle = Math.random() * Math.PI * 2;
const speed = (Math.random() * 5 + 3);
balloon.vx = Math.cos(angle) * speed;
balloon.vy = Math.sin(angle) * speed;
});
});
}
let lastX = 0, lastY = 0, lastTime = 0;
function calculateMouseSpeed(event) {
const now = Date.now();
const timeDiff = (now - lastTime) / 1000;
lastTime = now;
if (timeDiff === 0) return 0;
const dx = event.clientX - lastX;
const dy = event.clientY - lastY;
const distance = Math.sqrt(dx * dx + dy * dy);
lastX = event.clientX;
lastY = event.clientY;
return distance / timeDiff;
}
function createBalloon(isClick = false) {
const color = balloonColors;
const sizeMultiplier = isClick ? 1.2 : 1;
const speedMultiplier = isClick ? 1.2 : 1;
const angle = Math.random() * Math.PI * 2;
const speed = (Math.random() * 2 + 1) * speedMultiplier;
const balloon = {
x: mouseX,
y: mouseY,
radiusX: Math.random() * balloonSizeRange + balloonSizeRange * sizeMultiplier,
radiusY: (Math.random() * 0.5 + 1) * (Math.random() * balloonSizeRange + balloonSizeRange * sizeMultiplier), // 闀垮渾褰㈢姸
color: color,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
life: Math.random() * 80 + 50, decay: Math.random() * 0.02 + 0.01,
brightness: 500
};
balloons.push(balloon);
}
function createFallingBalloon() {
const color = balloonColors;
const radiusX = Math.random() * balloonSizeRange + balloonSizeRange;
const radiusY = (Math.random() * 0.5 + 1) * (Math.random() * balloonSizeRange + balloonSizeRange); // 闀垮渾褰㈢姸
const balloon = {
x: Math.random() * balloonCanvas.width,
y: 0,
radiusX: radiusX,
radiusY: radiusY,
color: color,
vx: (Math.random() - 0.5) * 2,
vy: Math.random() * 2 + 1,
life: Math.random() * 80 + 50,
decay: Math.random() * 0.02 + 0.01,
brightness: 1
};
balloons.push(balloon);
}
for (let i = 0; i < 3; i++) {
createFallingBalloon();
}
function update() {
balloonCtx.clearRect(0, 0, balloonCanvas.width, balloonCanvas.height);
if (!isMouseMoving) {
balloons.forEach(balloon => {
const randomFactor = Math.random() * 0.1 + 1;
balloon.vx *= 1.05 * randomFactor;
balloon.vy *= 1.05 * randomFactor;
});
}
for (let i = balloons.length - 1; i >= 0; i--) {
const balloon = balloons;
balloon.x += balloon.vx;
balloon.y += balloon.vy;
balloon.vx *= 0.98;
balloon.vy *= 0.98;
balloon.brightness -= balloon.decay;
balloon.life--;
if (balloon.x - balloon.radiusX < 0) {
balloon.x = balloon.radiusX;
balloon.vx = -balloon.vx * 0.8;
}
if (balloon.x + balloon.radiusX > balloonCanvas.width) {
balloon.x = balloonCanvas.width - balloon.radiusX;
balloon.vx = -balloon.vx * 0.8;
}
if (balloon.y - balloon.radiusY < 0) {
balloon.y = balloon.radiusY;
balloon.vy = -balloon.vy * 0.8;
}
if (balloon.y + balloon.radiusY > balloonCanvas.height) {
balloon.y = balloonCanvas.height - balloon.radiusY;
balloon.vy = -balloon.vy * 0.8;
}
if (balloon.life <= 0 || balloon.brightness <= 0) {
balloons.splice(i, 1);
} else {
drawBalloon(balloon);
}
}
requestAnimationFrame(update);
}
function drawBalloon(balloon) {
balloonCtx.shadowColor = 'rgba(0, 0, 0, 0.3)';
balloonCtx.shadowBlur = 5;
balloonCtx.shadowOffsetX = 2;
balloonCtx.shadowOffsetY = 2;
const gradient = balloonCtx.createRadialGradient(
balloon.x, balloon.y, 0,
balloon.x, balloon.y, Math.max(balloon.radiusX, balloon.radiusY)
);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
gradient.addColorStop(0.2, 'rgba(0, 0, 0, 0)');
gradient.addColorStop(1, balloon.color);
balloonCtx.beginPath();
balloonCtx.ellipse(balloon.x, balloon.y, balloon.radiusX, balloon.radiusY, 0, 0, Math.PI * 2);
balloonCtx.fillStyle = gradient;
balloonCtx.fill();
balloonCtx.beginPath();
balloonCtx.ellipse(balloon.x, balloon.y, balloon.radiusX, balloon.radiusY, 0, 0, Math.PI * 2);
balloonCtx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
balloonCtx.lineWidth = 1;
balloonCtx.stroke();
balloonCtx.shadowColor = 'rgba(0, 0, 0, 0)';
}
update();
window.addEventListener('resize', function() {
balloonCanvas.width = window.innerWidth;
balloonCanvas.height = window.innerHeight;
});
</script>
<script>
// 歌词解析ksc歌词或lrc歌词
const lrc = `月亮照山川
月光它落屋檐 落在远方的山川
群山下的少年 绕着山路十八弯
他的身影浅浅 却是步步勇敢
你看 繁星闪啊闪 闪耀在人间
你看绵延高山
山路一道道弯
河水溅湿他衣衫
看他瘦弱的肩
掌心磨出的茧
不知经历 多少辛酸
破旧的鞋子漏脚尖
衣服缝补了几遍
那斜挎包里 是最简单的饭
汗水滴落试卷 晕开远方的答案
烛光下 是他坚定眉眼
月光它落屋檐 落在远方的山川
群山下的少年 绕着山路十八弯
他的身影浅浅 却是步步勇敢
你看 繁星闪啊闪 闪耀在人间
你看绵延高山
山路一道道弯
河水溅湿他衣衫
看他瘦弱的肩
掌心磨出的茧
不知经历 多少辛酸
破旧的鞋子漏脚尖
衣服缝补了几遍
那斜挎包里 是最简单的饭
汗水滴落试卷 晕开远方的答案
烛光下 是他坚定眉眼
月光它落屋檐 落在远方的山川
群山下的少年 绕着山路十八弯
他的身影浅浅 却是步步勇敢
你看 繁星闪啊闪 闪耀在人间
月光它照人眠 照亮远方的群山
一路奔波向前 破晓光芒多耀眼
寒窗苦读的风寒 不动的信念
熬过岁月的冷眼 前路终璀璨
`;
const audio = document.getElementById('audio');
const lyrics = parseLyrics(lrc);
const lyricMask = document.querySelector('.lyric-mask');
const lyricOriginal = document.querySelector('.lyric-original');
let currentIndex = -1;
let currentLyric = null;
// 解析歌词(支持两种格式)
function parseLyrics(lrcText) {
const lyrics = [];
if (lrcText.includes('karaoke.add')) {
const lineRegex = /karaoke\.add\('([^']+)', '([^']+)', '([^']+)', '([^']+)'\);/g;
let match;
while ((match = lineRegex.exec(lrcText)) !== null) {
const startTime = timeToMs(match);
const endTime = timeToMs(match);
const text = match.replace(/\[|\]/g, '').trim();
const durations = match.split(',').map(Number);
if (text) {
lyrics.push({startTime, endTime, text, durations});
}
}
}
else if (lrcText.includes('[')) {
const lines = lrcText.split('\n').filter(line => line.trim());
lines.forEach((line, index) => {
const timeMatch = line.match(/\[(\d+:\d+\.\d+)\]/);
if (timeMatch) {
const timeStr = timeMatch;
const text = line.replace(/\[.*?\]/, '').trim();
if (text) {
const startTime = timeToMs(timeStr);
const nextLine = lines;
const nextTimeMatch = nextLine ? nextLine.match(/\[(\d+:\d+\.\d+)\]/) : null;
const endTime = nextTimeMatch ? timeToMs(nextTimeMatch) : startTime + 5000;
lyrics.push({
startTime,
endTime,
text,
durations: calculateCharDurations(text, startTime, endTime)
});
}
}
});
}
return lyrics;
}
function calculateCharDurations(text, startTime, endTime) {
const totalDuration = endTime - startTime;
const charCount = text.length;
const baseDur = Math.floor(totalDuration / charCount);
const durations = new Array(charCount).fill(baseDur);
const remainder = totalDuration % charCount;
for (let i = 0; i < remainder; i++) {
durations++;
}
return durations;
}
function timeToMs(timeStr) {
const parts = timeStr.split(':');
const minutes = parseInt(parts, 10);
const secondsAndMs = parts.split('.');
const seconds = parseInt(secondsAndMs, 10);
const ms = parseInt(secondsAndMs || 0, 10);
return minutes * 60 * 1000 + seconds * 1000 + ms;
}
function getCurrentLyricIndex(lyrics, currentTimeMs) {
for (let i = 0; i < lyrics.length; i++) {
if (currentTimeMs >= lyrics.startTime && currentTimeMs <= lyrics.endTime) {
return i;
}
}
return -1;
}
function updateLyricDisplay(index) {
if (index < 0 || index >= lyrics.length) return;
currentIndex = index;
currentLyric = lyrics;
lyricOriginal.textContent = currentLyric.text;
lyricMask.textContent = currentLyric.text;
lyricMask.style.width = '0%';
}
function updateLyricMask(currentTimeMs) {
if (!currentLyric) return;
const lyricStartTime = currentLyric.startTime;
const elapsed = currentTimeMs - lyricStartTime;
const totalDuration = currentLyric.durations.reduce((sum, d) => sum + d, 0);
let charIndex = 0;
let accumulatedTime = 0;
for (let i = 0; i < currentLyric.durations.length; i++) {
accumulatedTime += currentLyric.durations;
if (elapsed <= accumulatedTime) {
charIndex = i + 1;
break;
}
}
if (elapsed >= totalDuration) {
charIndex = currentLyric.text.length;
}
charIndex = Math.min(charIndex, currentLyric.text.length);
const tempSpan = document.createElement('span');
tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute';
tempSpan.style.fontSize = '50px';
tempSpan.style.fontWeight = '800';
document.body.appendChild(tempSpan);
const visibleText = currentLyric.text.substring(0, charIndex);
tempSpan.textContent = visibleText;
const width = tempSpan.offsetWidth;
document.body.removeChild(tempSpan);
lyricMask.style.width = `${width}px`;
}
// 监听更新歌词
audio.addEventListener('timeupdate', () => {
const currentTimeMs = audio.currentTime * 1000;
const index = getCurrentLyricIndex(lyrics, currentTimeMs);
if (index !== currentIndex) {
updateLyricDisplay(index);
}
updateLyricMask(currentTimeMs);
});
updateLyricDisplay(0);
</script>
<script>
bj.onclick = () => audio.paused ? (audio.play(), intro.style.animationPlayState = 'running') : (audio.pause(),intro.style.animationPlayState = 'paused');
const intro= document.querySelector('.intro');
</script>
本帖最后由 亚伦影音工作室 于 2025-8-11 12:28 编辑
https://pic.3gbizhi.com/uploads/ ... 081fdd999e0cecb.mp4
https://pic.3gbizhi.com/uploads/ ... 944775a3758a905.mp4
https://pic.3gbizhi.com/uploads/ ... 23a84810ba34266.mp4
欣赏老师的新作,点赞!
页:
[1]