一:效果展示

二:特效实现
(1)视觉效果
- 整体风格:粉色渐变背景 + 毛玻璃效果弹窗,营造浪漫氛围
- 初始面板:居中显示,带浮动动画
- 心形弹窗:120个小弹窗沿心形路径依次出现(每个显示3-5秒)
- 主弹窗:35个随机位置的祝福语弹窗(每个显示5秒,祝福语可在代码中任意修改)
- 结束面板:所有弹窗消失后显示
(2)交互流程
- 用户点击「开始显示祝福」按钮 → 隐藏控制面板 → 触发心形弹窗动画
- 心形弹窗全部显示后 → 自动触发主弹窗动画
- 所有弹窗消失 → 显示结束面板 → 可点击「重新开始」重置
三:完整代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>情人节爱心弹窗代码</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
font-family: 'Helvetica', 'Arial', sans-serif;
height: 100vh;
background: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);
}
.popup {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 15px;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.2),
inset 0 0 30px rgba(255, 255, 255, 0.5);
border: 2px solid rgba(255, 255, 255, 0.7);
padding: 15px;
box-sizing: border-box;
opacity: 0;
transform: scale(0.8);
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 1000;
cursor: default;
user-select: none;
max-width: 240px;
text-align: center;
background: linear-gradient(135deg, rgba(255,255,255,0.9), rgba(255,215,215,0.8));
backdrop-filter: blur(5px);
text-shadow: 0 1px 1px rgba(0,0,0,0.1);
font-weight: bold;
letter-spacing: 0.5px;
}
@keyframes fadeIn {
to {
opacity: 0.95;
transform: scale(1);
}
}
.heart-popup {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
box-shadow:
0 4px 15px rgba(255, 105, 180, 0.4),
inset 0 0 20px rgba(255, 255, 255, 0.6);
border: 2px solid rgba(255, 255, 255, 0.8);
padding: 10px 8px;
box-sizing: border-box;
opacity: 0;
transition: all 0.5s ease-out;
z-index: 1001;
cursor: default;
user-select: none;
background: linear-gradient(45deg, #ff9a9e, #fecfef);
font-size: 11px !important;
color: #d23669;
font-weight: bold;
text-shadow: 0 1px 1px rgba(255,255,255,0.5);
transform: scale(0.8);
animation: pulse 2s infinite;
min-width: 90px;
min-height: 50px;
max-width: 100px;
word-wrap: break-word;
white-space: normal;
line-height: 1.3;
text-align: center;
}
@keyframes pulse {
0% {
transform: scale(0.8); }
50% {
transform: scale(0.95); }
100% {
transform: scale(0.8); }
}
.control-panel {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
text-align: center;
width: 70%;
max-width: 380px;
background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(255,182,193,0.3));
border-radius: 25px;
padding: 30px;
box-shadow:
0 0 0 2px rgba(255,105,180,0.4),
0 10px 30px rgba(0, 0, 0, 0.2),
0 0 30px rgba(255,105,180,0.3);
border: 2px solid rgba(255,105,180,0.6);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
opacity: 1;
scale: 1;
animation: float 3s ease-in-out infinite, panelFadeIn 0.5s forwards;
}
@keyframes panelFadeIn {
from {
scale: 0.9; opacity: 0; }
to {
scale: 1; opacity: 1; }
}
.control-title {
font-size: 24px;
font-weight: bold;
color: #FF69B4;
margin-bottom: 20px;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
background: linear-gradient(45deg, #ff6b95, #ff8e53);
-webkit-background-clip: text;
color: transparent;
}
.control-message {
font-size: 16px;
color: #555;
margin-bottom: 25px;
line-height: 1.6;
font-weight: 500;
}
#startButton {
padding: 14px 35px;
font-size: 20px;
background: linear-gradient(45deg, #ff4777, #ff69b4);
box-shadow:
0 6px 20px rgba(255, 71, 119, 0.5),
0 0 15px rgba(255, 105, 180, 0.4),
inset 0 -3px 5px rgba(0,0,0,0.1);
position: relative;
overflow: visible;
}
#startButton::before {
content: '❤️';
position: absolute;
left: -15px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
animation: heartBeat 1.2s infinite;
z-index: 1;
}
#startButton::after {
content: '❤️';
position: absolute;
right: -15px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
animation: heartBeat 1.2s infinite;
z-index: 1;
display: none;
}
@keyframes heartBeat {
0% {
transform: translateY(-50%) scale(1); }
50% {
transform: translateY(-50%) scale(1.2); }
100% {
transform: translateY(-50%) scale(1); }
}
#startButton:hover {
transform: translateY(-4px);
box-shadow:
0 10px 30px rgba(255, 71, 119, 0.7),
0 0 20px rgba(255, 105, 180, 0.5),
inset 0 -3px 5px rgba(0,0,0,0.1);
background: linear-gradient(45deg, #ff1493, #ff4777);
}
.control-button {
padding: 12px 30px;
font-size: 18px;
font-weight: bold;
color: white;
background: linear-gradient(45deg, #FF69B4, #FF82AB);
border: none;
border-radius: 25px;
cursor: pointer;
box-shadow:
0 4px 15px rgba(255, 105, 180, 0.4),
inset 0 -3px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
margin: 10px;
position: relative;
overflow: visible;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
letter-spacing: 1px;
}
.control-button:active {
transform: translateY(1px);
}
.control-button::after {
display: none !important;
}
.hidden {
display: none !important;
}
@keyframes float {
0% {
transform: translate(-50%, -50%) translateY(0px); }
50% {
transform: translate(-50%, -50%) translateY(-10px); }
100% {
transform: translate(-50%, -50%) translateY(0px); }
}
.heart-icon {
color: #ff4777;
font-size: 18px;
margin: 0 2px;
animation: heartbeat 1.5s ease-in-out infinite;
}
@keyframes heartbeat {
0% {
transform: scale(1); }
25% {
transform: scale(1.1); }
50% {
transform: scale(1); }
75% {
transform: scale(1.1); }
100% {
transform: scale(1); }
}
</style>
</head>
<body>
<div id="controlPanel" class="control-panel">
<div class="control-title">温馨祝福</div>
<div class="control-message">
点击下方按钮开始显示祝福语<br>
愿您收获满满的温暖与感动!<br>
<span class="heart-icon">❤</span>
</div>
<button id="startButton" class="control-button">开始显示祝福</button>
</div>
<div id="endPanel" class="control-panel hidden">
<div class="control-title">祝福结束</div>
<div class="control-message">
所有祝福语已显示完毕<br>
愿这些温暖的话语给您带来美好的一天!<br>
<span class="heart-icon">✨</span>
</div>
<button id="restartButton" class="control-button">重新开始</button>
</div>
<script>
const config = {
popupCount: 35,
heartPopupCount: 120,
heartScale: 20,
popupWidth: 220,
popupHeight: 80,
heartPopupWidth: 80,
heartPopupHeight: 10,
baseInterval: 0.2,
minInterval: 0.05,
animationDuration: 300,
heartDelay: 0,
popupDelayAfterHeart: 1000,
popupDisplayTime: 5000
};
const colorThemes = [
['#FFF0F5', '#FF69B4'], ['#F0F8FF', '#4682B4'],
['#F5FFFA', '#3CB371'], ['#FFFACD', '#FFA500'],
['#FAFAD2', '#FF6347'], ['#E6E6FA', '#9370DB'],
['#FFF5EE', '#FF7F50'], ['#F8F8FF', '#5F9EA0'],
['#FDF5E6', '#D2691E'], ['#F0FFF0', '#2E8B57'],
['#FFE4E1', '#DA70D6'], ['#E0FFFF', '#48D1CC'],
['#FFF8DC', '#FF8C00'], ['#E6E0F8', '#9932CC']
];
const tips = [
"每一个清晨,都是新的开始。", "愿你每天都能找到生活中的小确幸。",
"阳光总在风雨后,请相信美好即将来临。", "无论走到哪里,都不要忘记欣赏沿途的风景。",
"有时候,停下脚步是为了更好地前行。", "勇敢地去追寻你的梦想吧,它就在不远处等你。",
"每一步都算数,在成为更好自己的路上。", "内心充满爱的人,能看到生活中更多的美。",
"记住那些让你微笑的理由,它们是你的力量。", "生活是一场旅行,重要的是享受旅程本身。",
"愿你永远保有一颗探索世界的好奇心。", "即使前路漫漫,也不要忘了偶尔停下来赏花。",
"每个不曾起舞的日子,都是对生命的辜负。", "用心感受每一次心跳,那是生命的声音。",
"保持初心,不忘初心,方得始终。", "向着光亮那方,一步一步前进。",
"不管前方有多少未知,勇敢迈向未来。", "在寻找答案的路上,你会发现更多的问题和乐趣。",
"用爱填满心灵,你会发现周围的世界也随之改变。", "每一天都是独一无二的画卷,只待你来描绘。",
"相信自己,你是这世上无可替代的存在。", "让温暖和善意成为你生活的主色调。",
"当你感到迷茫时,记得回头看看走过的路。", "珍惜那些陪伴你走过风雨的朋友。",
"在这瞬息万变的世界里,找到属于自己的不变。", "愿你拥有足够的勇气,去拥抱所有的可能。",
"不论何时何地,都要保持一颗乐观向上的心。", "发现身边的美好,哪怕是微不足道的小事。",
"带着感恩的心面对每一天,你会收获更多惊喜。", "在这个快节奏的世界里,别忘了慢下来倾听内心的声音。",
"把生活过成一首诗,优雅而从容。", "无论遇到什么困难,都请坚持下去。",
"给自己一个大大的微笑,因为你真的很棒。", "用积极的态度迎接挑战,你会发现不一样的自己。",
"与人为善,不仅温暖他人,也照亮了自己的道路。", "保持对生活的热爱,因为它是最好的老师。",
"在平凡的日子里,也能找到非凡的意义。"
];
const availableFonts = [
['Helvetica', '15px', 'bold'], ['Arial', '14px', 'bold'],
['Comic Sans MS', '15px', 'bold'], ['Georgia', '14px', 'bold'],
['Times New Roman', '15px', 'bold'], ['Verdana', '14px', 'bold'],
['Courier New', '14px', 'bold'], ['Palatino', '15px', 'bold'],
['Trebuchet MS', '14px', 'bold'], ['Impact', '16px', 'bold']
];
function heartCurve(t, scale) {
t = t * 2 * Math.PI;
const x = 16 * Math.pow(Math.sin(t), 3);
const y = -(13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t));
return {
x: x * scale,
y: y * scale
};
}
let activePopups = [];
let popupQueue = [];
let isProcessing = false;
let activeHeartPopups = 0;
let heartPopupsCreated = 0;
function createPopup() {
if (popupQueue.length === 0) {
isProcessing = false;
checkAllPopupsGone();
return;
}
const tip = popupQueue.shift();
const popup = document.createElement('div');
popup.className = 'popup';
const x = Math.random() * (window.innerWidth - config.popupWidth - 30) + 15;
const y = Math.random() * (window.innerHeight - config.popupHeight - 30) + 15;
const [bgColor, fgColor] = colorThemes[Math.floor(Math.random() * colorThemes.length)];
const [fontFamily, fontSize, fontWeight] = availableFonts[Math.floor(Math.random() * availableFonts.length)];
popup.style.width = `${
config.popupWidth}px`;
popup.style.minHeight = `${
config.popupHeight}px`;
popup.style.left = `${
x}px`;
popup.style.top = `${
y}px`;
popup.style.backgroundColor = bgColor;
popup.style.color = fgColor;
popup.style.fontFamily = fontFamily;
popup.style.fontSize = fontSize;
popup.style.fontWeight = fontWeight;
const content = document.createElement('div');
content.className = 'popup-content';
content.innerHTML = tip;
popup.appendChild(content);
document.body.appendChild(popup);
activePopups.push(popup);
setTimeout(() => {
popup.style.opacity = '0.95';
popup.style.transform = 'scale(1)';
}, 10);
setTimeout(() => {
if (popup.parentNode) {
popup.style.opacity = '0';
popup.style.transform = 'scale(0.8)';
setTimeout(() => {
if (popup.parentNode) {
popup.parentNode.removeChild(popup);
activePopups = activePopups.filter(p => p !== popup);
checkAllPopupsGone();
}
}, 500);
}
}, config.popupDisplayTime);
setTimeout(createPopup, config.baseInterval * 1000);
}
function checkAllPopupsGone() {
if (activePopups.length === 0 && popupQueue.length === 0 && !isProcessing) {
document.getElementById('controlPanel').classList.add('hidden');
document.getElementById('endPanel').classList.remove('hidden');
}
}
function createHeartPopups(callback) {
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2 - 60;
heartPopupsCreated = 0;
activeHeartPopups = 0;
const heartPopups = [];
function createPoint(i) {
if (i >= config.heartPopupCount) {
const checkInterval = setInterval(() => {
if (activeHeartPopups === 0) {
clearInterval(checkInterval);
preparePopupQueue();
setTimeout(createPopup, config.popupDelayAfterHeart);
}
}, 100);
return;
}
const t = i / config.heartPopupCount;
const point = heartCurve(t, config.heartScale);
const x = centerX + point.x - config.heartPopupWidth / 2;
const y = centerY + point.y - config.heartPopupHeight / 2;
const popup = document.createElement('div');
popup.className = 'heart-popup';
const [bgColor, fgColor] = colorThemes[Math.floor(Math.random() * colorThemes.length)];
popup.style.width = `${
config.heartPopupWidth}px`;
popup.style.height = `${
config.heartPopupHeight}px`;
popup.style.left = `${
x}px`;
popup.style.top = `${
y}px`;
popup.style.backgroundColor = bgColor;
popup.style.color = fgColor;
popup.style.fontFamily = 'Microsoft YaHei, Helvetica';
popup.style.fontSize = '11px';
popup.style.fontWeight = 'bold';
popup.style.lineHeight = '1.3';
popup.style.padding = '10px 8px';
popup.style.wordWrap = 'break-word';
popup.style.whiteSpace = 'normal';
const shortTips = tips.filter(tip => tip.length <= 15);
const selectedTip = shortTips.length > 0
? shortTips[Math.floor(Math.random() * shortTips.length)]
: tips[Math.floor(Math.random() * tips.length)];
const content = document.createElement('div');
content.className = 'popup-content';
content.style.width = '100%';
content.style.height = '100%';
content.style.display = 'flex';
content.style.justifyContent = 'center';
content.style.alignItems = 'center';
content.style.textAlign = 'center';
content.innerHTML = selectedTip;
popup.appendChild(content);
document.body.appendChild(popup);
heartPopups.push(popup);
activeHeartPopups++;
setTimeout(() => {
popup.style.opacity = '1';
popup.style.transform = 'scale(0.9)';
}, 10);
setTimeout(() => {
if (popup.parentNode) {
popup.style.opacity = '0';
popup.style.transform = 'scale(0.7)';
setTimeout(() => {
if (popup.parentNode) {
popup.parentNode.removeChild(popup);
activeHeartPopups--;
}
}, 500);
}
}, 3000 + Math.random() * 2000);
heartPopupsCreated++;
setTimeout(() => createPoint(i + 1), 20);
}
createPoint(0);
}
function preparePopupQueue() {
const shuffledTips = [...tips].sort(() => Math.random() - 0.5);
popupQueue = shuffledTips.slice(0, config.popupCount);
}
window.addEventListener('load', () => {
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const controlPanel = document.getElementById('controlPanel');
const endPanel = document.getElementById('endPanel');
startButton.addEventListener('click', () => {
startButton.style.transform = 'translateY(2px)';
setTimeout(() => {
startButton.style.transform = 'translateY(-4px)';
}, 100);
controlPanel.classList.add('hidden');
createHeartPopups();
});
restartButton.addEventListener('click', () => {
activePopups.forEach(popup => {
if (popup.parentNode) {
popup.style.opacity = '0';
popup.style.transform = 'scale(0.8)';
setTimeout(() => {
if (popup.parentNode) {
popup.parentNode.removeChild(popup);
}
}, 500);
}
});
const heartPopups = document.querySelectorAll('.heart-popup');
heartPopups.forEach(popup => {
if (popup.parentNode) {
popup.style.opacity = '0';
setTimeout(() => {
if (popup.parentNode) {
popup.parentNode.removeChild(popup);
}
}, 500);
}
});
activePopups = [];
popupQueue = [];
isProcessing = false;
endPanel.classList.add('hidden');
controlPanel.classList.remove('hidden');
});
});
document.addEventListener('DOMContentLoaded', () => {
document.body.style.overflow = 'hidden';
});
</script>
</body>
</html>
四:代码分析
(1)配置参数
const config = {
popupCount: 35, // 主弹窗数量
heartPopupCount: 120, // 心形弹窗数量
heartScale: 20, // 心形大小缩放
popupWidth: 220, // 主弹窗宽度
popupHeight: 80, // 主弹窗高度
heartPopupWidth: 80, // 心形弹窗宽度
heartPopupHeight: 10, // 心形弹窗高度
popupDisplayTime: 5000 // 主弹窗显示时间(ms)
};
(2)心形弹窗
function createPoint(i) {
if (i >= config.heartPopupCount) {
// 所有心形弹窗创建完成后,准备主弹窗队列
preparePopupQueue();
setTimeout(createPopup, config.popupDelayAfterHeart);
return;
}
// 计算当前弹窗位置
const t = i / config.heartPopupCount;
const point = heartCurve(t, config.heartScale);
// 创建弹窗DOM元素
const popup = document.createElement('div');
popup.className = 'heart-popup';
// 随机样式
const [bgColor, fgColor] = colorThemes[Math.floor(Math.random() * colorThemes.length)];
// 设置位置和样式
popup.style.left = `${
centerX + point.x - config.heartPopupWidth/2}px`;
popup.style.top = `${
centerY + point.y - config.heartPopupHeight/2}px`;
// 添加到页面
document.body.appendChild(popup);
// 递归创建下一个弹窗
setTimeout(() => createPoint(i + 1), 20);
}
(3)主弹窗
function createPopup() {
if (popupQueue.length === 0) {
checkAllPopupsGone(); // 队列为空时检查是否结束
return;
}
const tip = popupQueue.shift(); // 取出一条祝福语
const popup = document.createElement('div');
popup.className = 'popup';
// 随机位置(避开屏幕边缘)
const x = Math.random() * (window.innerWidth - config.popupWidth - 30) + 15;
const y = Math.random() * (window.innerHeight - config.popupHeight - 30) + 15;
// 设置样式和内容
popup.style.left = `${
x}px`;
popup.style.top = `${
y}px`;
popup.innerHTML = tip;
// 动画入场
setTimeout(() => {
popup.style.opacity = '0.95';
popup.style.transform = 'scale(1)';
}, 10);
// 定时消失
setTimeout(() => {
popup.style.opacity = '0';
popup.style.transform = 'scale(0.8)';
setTimeout(() => popup.remove(), 500);
}, config.popupDisplayTime);
// 继续创建下一个弹窗
setTimeout(createPopup, config.baseInterval * 1000);
}
(4)交互事件处理
/* 开始按钮 */
startButton.addEventListener('click', () => {
// 按钮按下动画
startButton.style.transform = 'translateY(2px)';
setTimeout(() => {
startButton.style.transform = 'translateY(-4px)';
}, 100);
// 隐藏控制面板并触发心形弹窗
controlPanel.classList.add('hidden');
createHeartPopups();
});
/* 重置按钮 */
restartButton.addEventListener('click', () => {
// 清除所有弹窗
activePopups.forEach(popup => popup.remove());
document.querySelectorAll('.heart-popup').forEach(popup => popup.remove());
// 重置状态
activePopups = [];
popupQueue = [];
// 返回初始面板
endPanel.classList.add('hidden');
controlPanel.classList.remove('hidden');
});