hexo博客添加live动图

演示

支持声音功能,默认静音,可以调整长宽高 鼠标移动到live图上方自动循环播放

Live Photo
LIVE
Live Photo
LIVE
Live Photo
LIVE
Live Photo
LIVE

安装

在主题js和css文件夹添加livephoto.css、livephoto.js、csh.js

livephoto.css样式文件

/* Live Photo 容器样式 */
.live-photo-container {
position: relative;
display: inline-block;
cursor: pointer;
overflow: hidden;
line-height: 0; /* 消除图片下方的间隙 */
border-radius: 8px; /* 容器圆角 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 可选:轻微阴影提升质感 */
}

/* 图片和视频层叠 */
.live-photo-static,
.live-photo-video {
width: 100%;
height: auto;
transition: opacity 0.3s ease;
border-radius: 8px; /* 统一图片和视频的圆角值 */
}

/* 视频默认隐藏 */
.live-photo-video {
position: absolute;
top: 0;
left: 0;
opacity: 0;
object-fit: cover; /* 确保视频覆盖整个容器 */
}

/* 鼠标悬停时隐藏静态图,显示视频 */
.live-photo-container:hover .live-photo-static {
opacity: 0;
}

.live-photo-container:hover .live-photo-video {
opacity: 1;
}

/* Live 标志 */
.live-badge {
position: absolute;
top: 10px;
right: 10px;
z-index: 3;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
pointer-events: none; /* 确保鼠标事件能穿透标志 */
opacity: 0.8;
display: flex;
align-items: center;
}

/* 红色闪烁点 */
.live-badge::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: #ff0000;
border-radius: 50%;
margin-right: 6px;
animation: blink 1.5s infinite;
}

@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.3; }
100% { opacity: 1; }
}

/* 静音按钮样式 - 位置调整 */
.live-photo-mute-btn {
position: absolute;
bottom: 25px; /* 从10px调整为15px,向上移动5px */
right: 10px;
z-index: 3;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
opacity: 0.8;
transition: opacity 0.2s ease;
}

.live-photo-mute-btn:hover {
opacity: 1;
background-color: rgba(0, 0, 0, 0.8);
}

livephoto.js文件

document.addEventListener('DOMContentLoaded', function() {
const livePhotos = document.querySelectorAll('.live-photo-container');

livePhotos.forEach(container => {
const video = container.querySelector('.live-photo-video');
const muteBtn = container.querySelector('.live-photo-mute-btn');

// 初始化视频设置
video.loop = true;
video.muted = true;
video.playsInline = true;

// 更新静音按钮状态
const updateMuteButton = () => {
muteBtn.textContent = video.muted ? '🔇' : '🔊';
};
updateMuteButton();

// 静音按钮事件
muteBtn.addEventListener('click', (e) => {
e.stopPropagation();
video.muted = !video.muted;
updateMuteButton();
});

// 鼠标悬停事件
container.addEventListener('mouseenter', () => {
video.currentTime = 0;
video.play().catch(e => console.error("播放失败:", e));
});

container.addEventListener('mouseleave', () => {
video.pause();
video.currentTime = 0;
});

// 触摸设备支持
container.addEventListener('touchstart', (e) => {
e.preventDefault();
if (video.paused) {
video.play();
} else {
video.pause();
}
}, { passive: false });
});
});

PJAX加载完成初始化csh.js

/**
* PJAX加载完成后自动重新初始化(改进版)
* 解决初始化时机、事件绑定和组件状态问题
*/

// 初始化函数示例(实际使用时替换为您的初始化逻辑)
function initializeComponents() {
console.log("执行初始化...");
// 这里放置您的初始化代码,例如:
// - 绑定事件处理程序
// - 初始化UI组件
// - 设置状态变量
// - 加载动态内容

// 示例:初始化Live Photo组件
initLivePhotos();
}

// 初始化Live Photo组件(示例)
function initLivePhotos() {
const livePhotos = document.querySelectorAll('.live-photo-container');

livePhotos.forEach(container => {
// 确保组件只初始化一次
if (container.dataset.initialized) return;
container.dataset.initialized = "true";

const video = container.querySelector('.live-photo-video');
const muteBtn = container.querySelector('.live-photo-mute-btn');

// 确保视频循环播放且默认静音
video.loop = true;
video.muted = true;
video.playsInline = true;

// 静音按钮状态
let isMuted = true;

// 更新静音按钮图标
function updateMuteButton() {
muteBtn.innerHTML = isMuted ? '🔇' : '🔊';
}
updateMuteButton();

// 静音按钮点击事件
muteBtn.addEventListener('click', function(e) {
e.stopPropagation();
isMuted = !isMuted;
video.muted = isMuted;
updateMuteButton();
});

// 鼠标悬停播放视频
container.addEventListener('mouseenter', function() {
video.currentTime = 0;
video.play().catch(e => console.error("播放失败:", e));
});

// 鼠标离开暂停视频
container.addEventListener('mouseleave', function() {
video.pause();
video.currentTime = 0;
});
});
}

// 清理函数(在PJAX加载前执行)
function cleanupComponents() {
console.log("执行清理...");
// 这里放置清理代码,例如:
// - 移除事件监听器
// - 重置组件状态
// - 清理全局变量

// 示例:清理Live Photo组件的事件监听器
const livePhotos = document.querySelectorAll('.live-photo-container');
livePhotos.forEach(container => {
// 移除所有自定义事件监听器
const newContainer = container.cloneNode(true);
container.parentNode.replaceChild(newContainer, container);

// 重置初始化标记
delete container.dataset.initialized;
});
}

// 监听pjax:send事件(PJAX开始加载时)
document.addEventListener('pjax:send', function() {
console.log("PJAX开始加载,执行清理");
cleanupComponents();
});

// 监听pjax:success事件
document.addEventListener('pjax:success', function(event) {
console.log("PJAX加载完成,等待DOM更新");

// 使用MutationObserver确保DOM完全更新
const observer = new MutationObserver(function(mutations) {
observer.disconnect();
console.log("DOM更新完成,开始重新初始化");
initializeComponents();
});

// 观察整个文档的变化
observer.observe(document, {
childList: true,
subtree: true
});
});

// 页面首次加载时执行初始化
document.addEventListener('DOMContentLoaded', function() {
console.log("页面首次加载,执行初始化");
initializeComponents();
});

// 监听pjax:error事件
document.addEventListener('pjax:error', function(event) {
console.error("PJAX加载失败", event);
// 错误处理后可尝试重新初始化
initializeComponents();
});

配置

在 Hexo 的主题配置文件 _config.yml 中添加以下选项:

inject:
head:
- <link rel="stylesheet" href="/css/livephoto.css">
bottom:
- <script src="/js/livephoto.js"></script>
- <script src="/js/csh.js"></script>

添加示例

<!-- 在Hexo文章中使用时,用raw标签包裹 -->
{% raw %}
<div class="live-photo-container" style="width: 100%; max-width: 800px; height: auto;">
<!-- 静态图片 -->
<img src="https://cftcr2.20010501.xyz/PicHoro/1757173197761.webp"
class="live-photo-static"
alt="Live Photo"
data-photo-src="https://cftcr2.20010501.xyz/PicHoro/1757173197761.webp">

<!-- 动态视频 -->
<video class="live-photo-video"
preload="metadata"
muted
data-video-src="https://cftcr2.20010501.xyz/PicHoro/1757173192196.mp4">
<source src="https://cftcr2.20010501.xyz/PicHoro/1757173192196.mp4" type="video/mp4">
</video>

<!-- Live 标志 -->
<div class="live-badge">LIVE</div>

<!-- 静音切换按钮 -->
<button class="live-photo-mute-btn" aria-label="切换静音"></button>
</div>
{% endraw %}