fireworks:
enable: false
hexo.extend.filter.register("theme_inject", function (injects) {
injects.bodyEnd.file("clickHeart", "views/clickHeart.pug", {}, { cache: true });
});
script((type = "text/javascript")).document.addEventListener("DOMContentLoaded", () => {
// 配置設定
const CONFIG = {
// 愛心數量
HEART_COUNT: {
MOBILE: 3, // 手機版愛心數量
DESKTOP: 6, // 桌面版愛心數量
},
// 愛心顏色
HEART_COLORS: ["#ff9999", "#ffb3ba", "#ffc8dd", "#bae1ff", "#a2d2ff"],
// 愛心大小範圍(像素)
HEART_SIZE: {
MIN: 10,
MAX: 20,
},
// 動畫持續時間(毫秒)
ANIMATION_DURATION: 1000,
// 最大愛心數量(用於清理)
MAX_HEARTS: 30,
// 移動範圍(像素)
MOVE_RANGE: {
X: 80,
Y: 80,
},
// 緩動函數類型
EASING: "easeOutQuad",
};
addStyles();
addMetaViewport();
const heartsContainer = document.createElement("div");
heartsContainer.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
`;
document.body.appendChild(heartsContainer);
document.body.addEventListener("click", (event) => {
createFirework(event.clientX, event.clientY);
});
document.body.addEventListener("touchstart", (event) => {
const touch = event.touches[0];
createFirework(touch.clientX, touch.clientY);
});
function addStyles() {
const style = document.createElement("style");
style.textContent = `
.heart {
position: absolute;
pointer-events: none;
will-change: transform, opacity;
}
`;
document.head.appendChild(style);
}
function addMetaViewport() {
if (!document.querySelector('meta[name="viewport"]')) {
const meta = document.createElement("meta");
meta.name = "viewport";
meta.content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no";
document.head.appendChild(meta);
}
}
function createFirework(x, y) {
const fragment = document.createDocumentFragment();
const heartCount =
window.innerWidth < 768 ? CONFIG.HEART_COUNT.MOBILE : CONFIG.HEART_COUNT.DESKTOP;
for (let i = 0; i < heartCount; i++) {
const color = getRandomColor();
const heart = createHeart(x, y, color);
fragment.appendChild(heart);
}
heartsContainer.appendChild(fragment);
clearOldHearts();
}
function getRandomColor() {
return CONFIG.HEART_COLORS[Math.floor(Math.random() * CONFIG.HEART_COLORS.length)];
}
function createHeart(x, y, color) {
const heart = document.createElementNS("http://www.w3.org/2000/svg", "svg");
heart.setAttribute("viewBox", "0 0 512 512");
heart.classList.add("heart");
heart.style.transform = `translate(${x}px, ${y}px)`;
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute(
"d",
"M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z",
);
path.setAttribute("fill", color);
heart.appendChild(path);
const size =
CONFIG.HEART_SIZE.MIN + Math.random() * (CONFIG.HEART_SIZE.MAX - CONFIG.HEART_SIZE.MIN);
heart.style.width = `${size}px`;
heart.style.height = `${size}px`;
animateHeart(heart, x, y);
return heart;
}
function animateHeart(heart, initialX, initialY) {
const startTime = performance.now();
const targetX = initialX + (Math.random() - 0.5) * CONFIG.MOVE_RANGE.X;
const targetY = initialY - Math.random() * CONFIG.MOVE_RANGE.Y;
function update(currentTime) {
const elapsed = currentTime - startTime;
if (elapsed >= CONFIG.ANIMATION_DURATION) {
heart.remove();
return;
}
const progress = elapsed / CONFIG.ANIMATION_DURATION;
const easedProgress = easing[CONFIG.EASING](progress);
const currentX = initialX + (targetX - initialX) * easedProgress;
const currentY = initialY + (targetY - initialY) * easedProgress;
const currentOpacity = 1 - easedProgress;
heart.style.transform = `translate(${currentX}px, ${currentY}px)`;
heart.style.opacity = currentOpacity;
requestAnimationFrame(update);
}
requestAnimationFrame(update);
}
const easing = {
easeOutQuad: (t) => 1 - (1 - t) * (1 - t),
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
easeOutQuart: (t) => 1 - Math.pow(1 - t, 4),
easeOutQuint: (t) => 1 - Math.pow(1 - t, 5),
};
function clearOldHearts() {
const hearts = heartsContainer.getElementsByClassName("heart");
if (hearts.length > CONFIG.MAX_HEARTS) {
for (let i = 0; i < hearts.length - CONFIG.MAX_HEARTS; i++) {
hearts[i].remove();
}
}
}
});