亚伦影音工作室 发表于 2025-11-16 10:58

同步歌词打字效果

本帖最后由 亚伦影音工作室 于 2025-12-3 16:21 编辑 <br /><br /> <style>
    .player-container { margin: 10px-300px ;
    position: relative;
    width: 1286px;
    height: 700px;

    background:#222 url(https://img-baofun.zhhainiao.com/pcwallpaper_ugc/live/939ab19eefec994600f8818b05380401.jpg) no-repeat center / cover;
    overflow: hidden;
      }
      #mapic {top:35%; left:80%;z-index: 1;
display: block;position:absolute;
    width: 260px; height: 360px;transform: rotateY(180deg);filter: hue-rotate(60deg);
    background: url(https://pic1.imgdb.cn/item/68cea8b1c5157e1a88207bd8.png)no-repeat 0px 0/6240px 360px;
animation: bur steps(24) 4s infinite ;
}

@keyframes bur {
0% {background-position: -10px 0;}
100% {background-position: -6240px 0;}
}
      #mypic {top:45%; left:2%;z-index: 6;
display: block;position: absolute;
    width: 200px; height: 200px;
    background: url(https://pic1.imgdb.cn/item/68bedc4158cb8da5c8893fd7.png)no-repeat 0 0/2600px 200px;
animation: heart-burst steps(13) 2s infinite ;
}
@keyframes heart-burst {
0% {background-position: 0px 0;}
100% {background-position: -2600px 0;}
}
      .lyrics-container {
            width: 480px;
            height: 540px;
            overflow-y: auto;
            background-color: transparent;
            
         
            padding: 15px;
            box-sizing: border-box;
            margin-bottom: 20px;
            
             position:absolute; left:450px; top:93px;
            
      }
      
      
      .lyrics-container::-webkit-scrollbar {
            width: 6px;
      }
      
      .lyrics-container::-webkit-scrollbar-track {
            background: transparent;
      }
      
      .lyrics-container::-webkit-scrollbar-thumb {
            background-color: transparent;
            border-radius: 3px;
      }
      
      .lyrics-line {
            margin-bottom: 5px;
            line-height: 1.7;
            min-height: 1.7em;
         font: 400 30px '华文隶书', sans-serif;
            padding: 3px 0;
            color: #ccc;
            
            transition: all 0.3s;
      }
      
      .lyrics-line.active {
            color: #fff000;
            font: 400 30px '华文隶书', sans-serif;
      }
      
      .typing-cursor {
            display: inline-block;
            width: 2px;
            height: 1em;
            background-color: #fff000;
            animation: blink 0.7s infinite;
            vertical-align: middle;
            margin-left: 2px;
      }
      
      @keyframes blink {
            0%, 100% { opacity: 1; }
            50% { opacity: 0; }
      }
      
      
    .play-btn {
            position: absolute;
            left: 50px;
            top: 50px;
            width: 100px;
            height: 100px;
            cursor: pointer;
            transition: transform 0.3s;
            z-index: 2;
      }
      
      .play-btn.playing {
            animation: rotate 2s linear infinite;
      }
      
      @keyframes rotate {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
      }

      
      
    </style>

<div class="player-container">
      <div id="flower-container"style="display:none;"></div>
<div id="mypic" ></div>
<div id="mapic" ></div>
      <audioid="audio-player" loop autoplay>
            <source src="https://s2.cldisk.com/sv-w9/audio/69/a8/61/9994adf5fb94283fb6d2fa578f140c67/audio.mp3" type="audio/mpeg">
            </audio>
      <divclass="lyrics-container" id="lyrics-container"></div>
      
       <img id="play-pause-btn" class="play-btn" src="https://pic1.imgdb.cn/item/690c41ba3203f7be00db7fed.png" alt="播放/暂停"></div>
   
      </div>

   <script>
   
    // 尽量使用标准LRC格式歌词
    const lrcLyrics = `
做你知己陪你一生
演唱:金钰儿
作词:未子夫
作曲:谢世超
混音:王哲
监制:徐佩佩
统筹:池歌
出品 亚伦影音工作室
演唱:金钰儿
如果那个对的人
出现在错的时分
到底是种幸运还是一种残忍
自从我遇见你的眼神
每过一天 思念就多一分
尽管我无法和你
看每个日出黄昏
依然无怨无悔对你一往情深
我默默心疼 你的心疼
用心治愈 你所有的伤痕
就算只做知己 陪你一生
我也愿意为你 痴痴地等
陪你看风雨 陪你安稳
用我的方式陪伴你的余生
做你红尘知己 陪你一生
哪怕分隔两座 不同的城
不管你我有没有可能
你是心底最温柔的部分
尽管我无法和你
看每个日出黄昏
依然无怨无悔对你一往情深
我默默心疼 你的心疼
用心治愈 你所有的伤痕
就算只做知己 陪你一生
我也愿意为你 痴痴地等
陪你看风雨 陪你安稳
用我的方式陪伴你的余生
做你红尘知己 陪你一生
哪怕分隔两座 不同的城
不管你我有没有可能
你是心底最温柔的部分
你是心底最温柔的部分
`;

    // 解析LRC歌词
    function parseLRC(lrcText) {
      const lines = lrcText.trim().split('\n');
      const result = [];
      
      for (const line of lines) {
            
            const timeMatch = line.match(/\[(\d{2}):(\d{2})\.(\d{2})\]/g);
            if (timeMatch) {
                const text = line.replace(/\[(\d{2}):(\d{2})\.(\d{2})\]/g, '').trim();
               
               
                timeMatch.forEach(tag => {
                  const parts = tag.match(/\[(\d{2}):(\d{2})\.(\d{2})\]/);
                  if (parts) {
                        const minutes = parseFloat(parts);
                        const seconds = parseFloat(parts);
                        const hundredths = parseFloat(parts);
                        const time = minutes * 60 + seconds + hundredths / 100;
                        
                        if (text) {
                            result.push({ time, text });
                        }
                  }
                });
            }
      }
      
      // 按时间排序
      result.sort((a, b) => a.time - b.time);
      return result;
    }

    const lyrics = parseLRC(lrcLyrics);
    let lineElements = [];
    let currentLine = -1;
    let typingInterval;
    let currentChar = 0;
    let hasAutoPlayed = false;

   
    let flowers = [];
    let flowerAnimationActive = false;
    let flowerAnimationId = null;

    // 初始化歌词行
    function initLyrics() {
      const lyricsContainer = document.getElementById('lyrics-container');
      
         
      lyrics.forEach((line, index) => {
            const lineElement = document.createElement('div');
            lineElement.className = 'lyrics-line';
            lineElement.dataset.time = line.time;
            lineElement.dataset.index = index;
            lineElement.textContent = '';
            lyricsContainer.appendChild(lineElement);
      });
      
      
      lineElements = Array.from(document.querySelectorAll('.lyrics-container .lyrics-line'));
    }

    function updateLyrics() {
      const audioPlayer = document.getElementById('audio-player');
      if (!audioPlayer) return;
      
      const currentTime = audioPlayer.currentTime;
      
      
      let newLine = -1;
      for (let i = lyrics.length - 1; i >= 0; i--) {
            if (currentTime >= lyrics.time) {
                newLine = i;
                break;
            }
      }
      
      
      if (newLine !== currentLine) {
            clearInterval(typingInterval);
            
            if (currentLine >= 0 && currentLine < lineElements.length) {
                lineElements.classList.remove('active');
                lineElements.textContent = lyrics.text;
            }
            
            currentLine = newLine;
            currentChar = 0;
            
            // 添加新行的活动状态并开始打字效果
            if (currentLine >= 0 && currentLine < lineElements.length) {
                lineElements.classList.add('active');
                lineElements.textContent = ''; // 清空内容以便打字效果
               
                // 开始打字效果
                typingInterval = setInterval(typeNextChar, 300);
               
                // 检查是否需要滚动
                checkScrollNeeded();
            }
      }
    }

    function typeNextChar() {
      if (currentLine >= 0 && currentChar < lyrics.text.length) {
            lineElements.innerHTML =
                lyrics.text.substring(0, currentChar + 1) +
                '<span class="typing-cursor"></span>';
            currentChar++;
      } else {
            clearInterval(typingInterval);
            if (currentLine >= 0) {
                lineElements.textContent = lyrics.text;
            }
      }
    }

    function checkScrollNeeded() {
      const lyricsContainer = document.getElementById('lyrics-container');
      if (!lyricsContainer || currentLine < 0) return;
      
      const lineElement = lineElements;
      const containerHeight = lyricsContainer.clientHeight;
      const lineHeight = lineElement.offsetHeight;
      const lineTop = lineElement.offsetTop;
      const scrollTop = lyricsContainer.scrollTop;
      
      // 计算当前行在容器中的位置
      const linePosition = lineTop - scrollTop;
      
         
      const threshold = 100;
      if (linePosition > containerHeight - threshold) {
            scrollToCurrentLine();
      }
    }

    function scrollToCurrentLine() {
      const lyricsContainer = document.getElementById('lyrics-container');
      if (!lyricsContainer || currentLine < 0) return;
      
      const lineElement = lineElements;
      const containerHeight = lyricsContainer.clientHeight;
      const lineHeight = lineElement.offsetHeight;
      const lineTop = lineElement.offsetTop;
      
      // 计算目标滚动位置
      const targetScroll = lineTop - containerHeight + (lineHeight * 2);
      
      
      const maxScroll = lyricsContainer.scrollHeight - containerHeight;
      const finalScroll = Math.min(Math.max(0, targetScroll), maxScroll);
      
      lyricsContainer.scrollTo({
            top: finalScroll,
            behavior: 'smooth'
      });
    }

    // 花瓣动画控制函数
    function startFlowerAnimation() {
      if (flowerAnimationActive) return;
      flowerAnimationActive = true;
      
      function animateFlowers() {
            if (!flowerAnimationActive) return;
            
            flowers.forEach(flower => {
                const now = Date.now();
                const elapsed = (now - flower.startTime) / 1000;
               
               
                let progress = elapsed / flower.duration;
               
                if (progress >= 1) {
                  
                  flower.startTime = now;
                  progress = 0;
                }
               
                // 根据动画类型计算当前位置
                let x, y;
                switch(flower.animationType) {
                  case 1:
                        x = 0;
                        y = progress * 500;
                        break;
                  case 2:
                        x = progress * 100;
                        y = progress * 500;
                        break;
                  case 3:
                        x = -progress * 80;
                        y = progress * 500;
                        break;
                  case 4:
                        x = progress < 0.5 ? progress * 300 : 150 - (progress - 0.5) * 400;
                        y = progress * 500;
                        break;
                  case 5:
                        if (progress < 0.3) {
                            x = progress * 400;
                        } else if (progress < 0.7) {
                            x = 120 - (progress - 0.3) * 500;
                        } else {
                            x = -80 + (progress - 0.7) * 400;
                        }
                        y = progress * 500;
                        break;
                }
               
               
                const rotation = progress * 360 * (flower.animationType % 2 === 0 ? 1 : -1);
               
               
                const opacity = flower.opacity * (1 - progress);
               
               
                flower.element.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg)`;
                flower.element.style.opacity = opacity;
            });
            
            flowerAnimationId = requestAnimationFrame(animateFlowers);
      }
      
      animateFlowers();
    }
   
    function stopFlowerAnimation() {
      flowerAnimationActive = false;
      if (flowerAnimationId) {
            cancelAnimationFrame(flowerAnimationId);
            flowerAnimationId = null;
      }
    }
   
    function resetFlowers() {
      flowers.forEach(flower => {
            flower.startTime = Date.now();
            flower.element.style.transform = 'translate(0, 0) rotate(0deg)';
            flower.element.style.opacity = flower.opacity;
      });
    }

    // 配置参数
    const config = {
      flowerCount: 30,            
      area: {                     
            left: 0,               
            right: '100%',         
            top: 0,               
            bottom: '100%'         
      },
      minSize: 10,               
      maxSize: 30,               
      minOpacity: 0.6,            
      maxOpacity: 0.9,            
      minDuration: 8,            
      maxDuration: 20            
    };

   
    const flowerSymbols = ['✿'];

   
    function initFlowers() {
      const container = document.getElementById('flower-container');
      const containerRect = container.getBoundingClientRect();
      
      
      const area = {
            left: parseValue(config.area.left, containerRect.width),
            right: parseValue(config.area.right, containerRect.width),
            top: parseValue(config.area.top, containerRect.height),
            bottom: parseValue(config.area.bottom, containerRect.height)
      };
      
      
      for (let i = 0; i < config.flowerCount; i++) {
            const flower = document.createElement('div');
            const type = Math.floor(Math.random() * 5) + 1;
            const symbol = flowerSymbols;
            
            
            const size = randomBetween(config.minSize, config.maxSize);
            const opacity = randomBetween(config.minOpacity, config.maxOpacity);
            const duration = randomBetween(config.minDuration, config.maxDuration);
            const left = randomBetween(area.left, area.right);
            const animationType = Math.floor(Math.random() * 5) + 1;
            
         
            flower.className = `flower type${type}`;
            flower.innerHTML = symbol;
            flower.style.left = `${left}px`;
            flower.style.top = `-${size * 1.5}px`;   
            flower.style.fontSize = `${size}px`;
            flower.style.opacity = opacity;
            
            container.appendChild(flower);
            
            flowers.push({
                element: flower,
                startTime: Date.now() + randomBetween(0, 15) * 1000, // 随机延迟开始
                duration: duration,
                opacity: opacity,
                animationType: animationType
            });
      }
      
      
      stopFlowerAnimation();
    }

    // 辅助函数
    function parseValue(value, base) {
      if (typeof value === 'string' && value.includes('%')) {
            return (parseFloat(value) / 100) * base;
      }
      return parseFloat(value);
    }

    function randomBetween(min, max) {
      return Math.random() * (max - min) + min;
    }

    // 等待DOM完全加载后再初始化
    document.addEventListener('DOMContentLoaded', function() {
      const audioPlayer = document.getElementById('audio-player');
      const playPauseBtn = document.getElementById('play-pause-btn');
      
      
      if (!audioPlayer || !playPauseBtn) return;

         
      initLyrics();
      
      
      initFlowers();
      
      // 事件监听
      audioPlayer.addEventListener('timeupdate', updateLyrics);

      audioPlayer.addEventListener('canplay', function() {
         
            if (!hasAutoPlayed) {
                const playPromise = audioPlayer.play();
                hasAutoPlayed = true;
               
                if (playPromise !== undefined) {
                  playPromise.catch(error => {
                     
                        playPauseBtn.classList.remove('playing');
                  }).then(() => {
                        playPauseBtn.classList.add('playing');
                  });
                }
            }
      });
mapic.style.animationPlayState = audioPlayer.paused ? 'paused' : 'running';
mypic.style.animationPlayState = audioPlayer.paused ? 'paused' : 'running';
audioPlayer.addEventListener('playing', () =>mapic.style.animationPlayState = 'running');
audioPlayer.addEventListener('playing', () =>mypic.style.animationPlayState = 'running');
audioPlayer.addEventListener('pause', () =>mapic.style.animationPlayState = 'paused');
audioPlayer.addEventListener('pause', () =>mypic.style.animationPlayState = 'paused');

      // 播放/暂停按钮点击事件
      playPauseBtn.addEventListener('click', function() {
            if (audioPlayer.paused) {
                audioPlayer.play().catch(e => {
                  console.log('播放被阻止:', e);
                });
                this.classList.add('playing');
            } else {
                audioPlayer.pause();
                this.classList.remove('playing');
            }
      });
      
      // 音频播放状态变化时更新按钮状态和图片显示
      audioPlayer.addEventListener('play', function() {
            playPauseBtn.classList.add('playing');
            
            animatedGif.classList.add('active');
            staticImage.classList.remove('active');
            
            startFlowerAnimation();
      });
      
      audioPlayer.addEventListener('pause', function() {
            playPauseBtn.classList.remove('playing');
            
            staticImage.classList.add('active');
            animatedGif.classList.remove('active');
            
            stopFlowerAnimation();
      });

      // 初始滚动到顶部
      const lyricsContainer = document.getElementById('lyrics-container');
      if (lyricsContainer) {
            lyricsContainer.scrollTop = 0;
      }

      // 设置歌曲结束禁止自动重新播放(需手动)
      audioPlayer.addEventListener('ended', function() {
            playPauseBtn.classList.remove('playing');
            
            staticImage.classList.add('active');
            animatedGif.classList.remove('active');
            
            stopFlowerAnimation();
      });
   
      // 添加播放开始事件监听
      audioPlayer.addEventListener('play', function() {
            
            if(audioPlayer.currentTime < 0.5) {
               
                currentLine = -1;
               
                // 清除所有打字效果
                clearInterval(typingInterval);
               
               
                lineElements.forEach(line => {
                  line.classList.remove('active');
                  line.textContent = '';
                });
               
               if (lyricsContainer) {
                  lyricsContainer.scrollTo({
                        top: 0,
                        behavior: 'smooth'
                  });
                }
            }
      });
    });
</script>

红影 发表于 2025-11-16 13:34

真神奇,真的是逐字出现的歌词呢。非常漂亮的制作。
欣赏亚伦老师好帖{:4_199:}

红影 发表于 2025-11-16 13:37

原来这两个小动图都是长图的蒙太奇效果,怪不得暂停可以停在任一帧呢。
这样的动图厉害了{:4_199:}

红影 发表于 2025-11-16 13:39

这歌词还能鼠标操作呢,可以让所有歌词都卷到最上去不见,弄到最下的时候新歌词出来还是会自动移动的。
就算暂停时候,整个歌词也是能用鼠标卷动的呢{:4_204:}

老谟深虑 发表于 2025-11-16 18:54

          欣赏老师的新代码,打字效果很不错。
页: [1]
查看完整版本: 同步歌词打字效果