1725 功能大改
This commit is contained in:
1 parent
fa52190830
commit
f73bf33bce
10 files changed
+1743
-10
No files matched your search
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Shortcodes JavaScript - 实况照片、语音、音乐卡片
|
||||
* 移植自 Hugo-Theme-Amigo
|
||||
*/
|
||||
|
||||
// ============================================================
|
||||
// Live Photo
|
||||
// ============================================================
|
||||
|
||||
function initLivePhotoShortcodes() {
|
||||
document.querySelectorAll('.live-photo').forEach(function(livePhoto) {
|
||||
if (livePhoto.__liveBound) return;
|
||||
livePhoto.__liveBound = true;
|
||||
|
||||
var video = livePhoto.querySelector('video.live-photo-video') || livePhoto.querySelector('video');
|
||||
var posterImg = livePhoto.querySelector('img.live-photo-poster') || livePhoto.querySelector('img');
|
||||
var toggleBtn = livePhoto.querySelector('.live-photo-toggle-btn');
|
||||
var muteBtn = livePhoto.querySelector('.live-photo-mute-btn');
|
||||
|
||||
if (!video || !toggleBtn || !muteBtn) return;
|
||||
|
||||
var HOVER_DELAY = 500;
|
||||
var hoverTimer = null;
|
||||
var isLoaded = false;
|
||||
|
||||
function syncAspectFromPoster() {
|
||||
if (!posterImg) return;
|
||||
var w = posterImg.naturalWidth || 0;
|
||||
var h = posterImg.naturalHeight || 0;
|
||||
if (!w || !h) return;
|
||||
livePhoto.style.setProperty('--live-photo-aspect', w + ' / ' + h);
|
||||
}
|
||||
|
||||
if (posterImg && posterImg.complete) {
|
||||
syncAspectFromPoster();
|
||||
} else if (posterImg) {
|
||||
posterImg.addEventListener('load', syncAspectFromPoster, { once: true });
|
||||
}
|
||||
|
||||
function ensureLoaded() {
|
||||
if (isLoaded) return;
|
||||
isLoaded = true;
|
||||
var src = video.dataset.src || '';
|
||||
if (src && !video.getAttribute('src')) {
|
||||
video.setAttribute('src', src);
|
||||
video.src = src;
|
||||
}
|
||||
try { video.load(); } catch (e) {}
|
||||
}
|
||||
|
||||
function setMuted(isMuted) {
|
||||
video.muted = !!isMuted;
|
||||
if (isMuted) video.setAttribute('muted', '');
|
||||
else video.removeAttribute('muted');
|
||||
muteBtn.setAttribute('data-muted', isMuted ? 'true' : 'false');
|
||||
}
|
||||
|
||||
setMuted(true);
|
||||
|
||||
function playVideo() {
|
||||
ensureLoaded();
|
||||
livePhoto.classList.add('is-playing');
|
||||
toggleBtn.setAttribute('data-state', 'live');
|
||||
try { video.play(); } catch (e) {}
|
||||
}
|
||||
|
||||
function stopVideo() {
|
||||
livePhoto.classList.remove('is-playing');
|
||||
toggleBtn.setAttribute('data-state', 'static');
|
||||
try { video.pause(); video.currentTime = 0; } catch (e) {}
|
||||
}
|
||||
|
||||
// Toggle button click
|
||||
toggleBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
if (livePhoto.classList.contains('is-playing')) {
|
||||
stopVideo();
|
||||
} else {
|
||||
playVideo();
|
||||
}
|
||||
});
|
||||
|
||||
// Mute button click
|
||||
muteBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
setMuted(!video.muted);
|
||||
});
|
||||
|
||||
// Hover to play
|
||||
livePhoto.addEventListener('mouseenter', function() {
|
||||
hoverTimer = setTimeout(playVideo, HOVER_DELAY);
|
||||
});
|
||||
|
||||
livePhoto.addEventListener('mouseleave', function() {
|
||||
clearTimeout(hoverTimer);
|
||||
if (toggleBtn.getAttribute('data-state') !== 'live') {
|
||||
stopVideo();
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile touch
|
||||
livePhoto.addEventListener('touchstart', function() {
|
||||
if (livePhoto.classList.contains('is-playing')) {
|
||||
stopVideo();
|
||||
} else {
|
||||
playVideo();
|
||||
}
|
||||
});
|
||||
|
||||
// Video ended
|
||||
video.addEventListener('ended', function() {
|
||||
if (toggleBtn.getAttribute('data-state') !== 'live') {
|
||||
stopVideo();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Voice Bubble
|
||||
// ============================================================
|
||||
|
||||
function initVoiceBubbles() {
|
||||
document.querySelectorAll('.amigo-voice-bubble').forEach(function(bubble) {
|
||||
if (bubble.__voiceBound) return;
|
||||
bubble.__voiceBound = true;
|
||||
|
||||
var audioSrc = bubble.dataset.src;
|
||||
var audio = null;
|
||||
var isPlaying = false;
|
||||
var durationSpan = bubble.querySelector('.amigo-voice-duration');
|
||||
|
||||
// Load audio duration
|
||||
if (audioSrc) {
|
||||
audio = new Audio(audioSrc);
|
||||
audio.addEventListener('loadedmetadata', function() {
|
||||
var duration = Math.round(audio.duration);
|
||||
if (durationSpan) {
|
||||
durationSpan.textContent = duration + '"';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bubble.addEventListener('click', function() {
|
||||
if (!audio) return;
|
||||
|
||||
if (isPlaying) {
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
bubble.classList.remove('playing');
|
||||
isPlaying = false;
|
||||
} else {
|
||||
// Stop other playing voices
|
||||
document.querySelectorAll('.amigo-voice-bubble.playing').forEach(function(other) {
|
||||
if (other !== bubble) {
|
||||
other.classList.remove('playing');
|
||||
}
|
||||
});
|
||||
|
||||
audio.play();
|
||||
bubble.classList.add('playing');
|
||||
isPlaying = true;
|
||||
}
|
||||
});
|
||||
|
||||
audio.addEventListener('ended', function() {
|
||||
bubble.classList.remove('playing');
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Music Card
|
||||
// ============================================================
|
||||
|
||||
function initMusicCards() {
|
||||
document.querySelectorAll('.amigo-music-card').forEach(function(card) {
|
||||
if (card.__musicBound) return;
|
||||
card.__musicBound = true;
|
||||
|
||||
var audioSrc = card.dataset.src;
|
||||
var playBtn = card.querySelector('.amigo-music-card__play');
|
||||
var seekInput = card.querySelector('.amigo-music-card__seek');
|
||||
var currentTime = card.querySelector('.amigo-music-card__time--current');
|
||||
var durationTime = card.querySelector('.amigo-music-card__time--duration');
|
||||
var audio = null;
|
||||
var isPlaying = false;
|
||||
|
||||
if (!audioSrc) return;
|
||||
|
||||
audio = new Audio(audioSrc);
|
||||
|
||||
// Load metadata
|
||||
audio.addEventListener('loadedmetadata', function() {
|
||||
var duration = formatTime(audio.duration);
|
||||
if (durationTime) durationTime.textContent = duration;
|
||||
});
|
||||
|
||||
// Update progress
|
||||
audio.addEventListener('timeupdate', function() {
|
||||
if (currentTime) currentTime.textContent = formatTime(audio.currentTime);
|
||||
if (seekInput && audio.duration) {
|
||||
seekInput.value = (audio.currentTime / audio.duration * 1000).toString();
|
||||
}
|
||||
});
|
||||
|
||||
// Play/Pause
|
||||
function togglePlay() {
|
||||
if (isPlaying) {
|
||||
audio.pause();
|
||||
card.classList.remove('playing');
|
||||
isPlaying = false;
|
||||
} else {
|
||||
// Stop other playing music
|
||||
document.querySelectorAll('.amigo-music-card.playing').forEach(function(other) {
|
||||
if (other !== card) {
|
||||
other.classList.remove('playing');
|
||||
}
|
||||
});
|
||||
|
||||
audio.play();
|
||||
card.classList.add('playing');
|
||||
isPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (playBtn) {
|
||||
playBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
togglePlay();
|
||||
});
|
||||
}
|
||||
|
||||
card.addEventListener('click', togglePlay);
|
||||
|
||||
// Seek
|
||||
if (seekInput) {
|
||||
seekInput.addEventListener('input', function() {
|
||||
if (audio.duration) {
|
||||
audio.currentTime = (parseFloat(seekInput.value) / 1000) * audio.duration;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Ended
|
||||
audio.addEventListener('ended', function() {
|
||||
card.classList.remove('playing');
|
||||
isPlaying = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatTime(seconds) {
|
||||
var m = Math.floor(seconds / 60);
|
||||
var s = Math.floor(seconds % 60);
|
||||
return m + ':' + (s < 10 ? '0' : '') + s;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Initialize all shortcodes
|
||||
// ============================================================
|
||||
|
||||
function initAllShortcodes() {
|
||||
initLivePhotoShortcodes();
|
||||
initVoiceBubbles();
|
||||
initMusicCards();
|
||||
}
|
||||
|
||||
// DOMContentLoaded
|
||||
document.addEventListener('DOMContentLoaded', initAllShortcodes);
|
||||
|
||||
// Page load
|
||||
window.addEventListener('load', initAllShortcodes);
|
||||
|
||||
// PJAX support - re-initialize after page transition
|
||||
document.addEventListener('pjax:complete', initAllShortcodes);
|
||||
document.addEventListener('pjax:success', initAllShortcodes);
|
||||
|
||||
// Also listen for custom events that some PJAX libraries fire
|
||||
document.addEventListener('page:loaded', initAllShortcodes);
|
||||
document.addEventListener('page:change', initAllShortcodes);
|
||||
Reference in new issue
Block a user