325 lines
8.6 KiB
HTML
325 lines
8.6 KiB
HTML
<!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>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html, body {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
body {
|
||
background: #f5f5f5;
|
||
font-family: 'Arial', sans-serif;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 示例内容 */
|
||
.content {
|
||
position: relative;
|
||
z-index: 1;
|
||
text-align: center;
|
||
color: #333;
|
||
text-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.content h1 {
|
||
font-size: 3em;
|
||
margin-bottom: 20px;
|
||
animation: fadeIn 1s ease-out;
|
||
}
|
||
|
||
.content p {
|
||
font-size: 1.2em;
|
||
animation: fadeIn 1.5s ease-out;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(-20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
/* 水面容器 */
|
||
.water-container {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 0%;
|
||
pointer-events: none;
|
||
overflow: visible;
|
||
}
|
||
|
||
/* 波浪层 */
|
||
.wave {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 200%;
|
||
height: 100%;
|
||
background-repeat: repeat-x;
|
||
background-position: bottom;
|
||
}
|
||
|
||
.wave1 { z-index: 1; }
|
||
.wave2 { z-index: 2; }
|
||
.wave3 { z-index: 3; }
|
||
|
||
/* 触发按钮 */
|
||
.trigger-btn {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 12px 24px;
|
||
background: rgba(33, 150, 243, 0.8);
|
||
border: 2px solid #2196F3;
|
||
color: white;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
border-radius: 25px;
|
||
z-index: 1000;
|
||
transition: all 0.3s;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.trigger-btn:hover {
|
||
background: rgba(33, 150, 243, 1);
|
||
transform: scale(1.05);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- 页面原本的内容 -->
|
||
<div class="content">
|
||
<h1>🌊 彩蛋特效</h1>
|
||
<p>水面正在缓缓上涨...</p>
|
||
<p style="margin-top: 10px; font-size: 0.9em; opacity: 0.8;">
|
||
每次刷新都有不同的波浪效果
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 水面动画容器 -->
|
||
<div class="water-container" id="waterContainer">
|
||
<div class="wave wave1"></div>
|
||
<div class="wave wave2"></div>
|
||
<div class="wave wave3"></div>
|
||
</div>
|
||
|
||
<!-- 重新触发按钮 -->
|
||
<button class="trigger-btn" onclick="restartAnimation()">🔄 重播动画</button>
|
||
|
||
<script>
|
||
// 随机数工具函数
|
||
function random(min, max) {
|
||
return Math.random() * (max - min) + min;
|
||
}
|
||
|
||
function randomInt(min, max) {
|
||
return Math.floor(random(min, max));
|
||
}
|
||
|
||
function randomChoice(arr) {
|
||
return arr[Math.floor(Math.random() * arr.length)];
|
||
}
|
||
|
||
// 生成随机波浪SVG路径 - 相邻波振幅变化平滑
|
||
function generateWaveSVG(waveIndex) {
|
||
const baseHeight = 180 + waveIndex * 10; // 基准高度
|
||
const numWaves = 4; // 生成4个完整波段
|
||
|
||
let path = `M0,${baseHeight}`;
|
||
let currentX = 0;
|
||
|
||
// 第一个波的振幅随机
|
||
let prevAmplitude = random(40, 80);
|
||
|
||
// 生成多个完整的波段,每个包含波峰和波谷
|
||
for (let i = 0; i < numWaves; i++) {
|
||
// 每个波段的随机参数
|
||
const waveLength = random(700, 900); // 整个周期的长度:700-900
|
||
|
||
// 振幅限制:与前一个波的差值不超过20
|
||
let amplitude;
|
||
if (i === 0) {
|
||
amplitude = prevAmplitude;
|
||
} else {
|
||
const minAmp = Math.max(10, prevAmplitude - 20);
|
||
const maxAmp = Math.min(80, prevAmplitude + 20);
|
||
amplitude = random(minAmp, maxAmp);
|
||
}
|
||
prevAmplitude = amplitude;
|
||
|
||
const halfWave = waveLength / 2;
|
||
|
||
// 前半段:基线 → 波峰 → 基线
|
||
const peak1X = currentX + halfWave / 2;
|
||
const peak1Y = baseHeight - amplitude;
|
||
const mid1X = currentX + halfWave;
|
||
const mid1Y = baseHeight;
|
||
|
||
path += ` Q${peak1X},${peak1Y} ${mid1X},${mid1Y}`;
|
||
|
||
// 后半段:基线 → 波谷 → 基线
|
||
const trough1X = mid1X + halfWave / 2;
|
||
const trough1Y = baseHeight + amplitude;
|
||
const end1X = currentX + waveLength;
|
||
const end1Y = baseHeight;
|
||
|
||
path += ` Q${trough1X},${trough1Y} ${end1X},${end1Y}`;
|
||
|
||
currentX = end1X;
|
||
}
|
||
|
||
// 闭合路径
|
||
path += ` L${currentX},1000 L0,1000 Z`;
|
||
|
||
return {
|
||
path: path,
|
||
width: currentX,
|
||
waveWidth: currentX / numWaves // 平均波长
|
||
};
|
||
}
|
||
|
||
// 初始化水面动画
|
||
function initWaterAnimation() {
|
||
const container = document.getElementById('waterContainer');
|
||
const waves = document.querySelectorAll('.wave');
|
||
|
||
// 波浪颜色配置
|
||
const waveColors = [
|
||
'rgba(135, 206, 250, 0.4)',
|
||
'rgba(100, 181, 246, 0.5)',
|
||
'rgba(33, 150, 243, 0.4)'
|
||
];
|
||
|
||
// 创建动态样式表
|
||
const styleSheet = document.createElement('style');
|
||
styleSheet.id = 'dynamic-waves-style';
|
||
document.head.appendChild(styleSheet);
|
||
|
||
// 1. 随机水面上涨参数
|
||
const riseDuration = random(30, 40); // 8-12秒
|
||
const finalHeight = random(87, 93); // 87-93%
|
||
const riseEasing = randomChoice([
|
||
'ease-in-out',
|
||
'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||
'cubic-bezier(0.42, 0, 0.58, 1)'
|
||
]);
|
||
|
||
// 创建水面上涨动画
|
||
const riseKeyframes = `
|
||
@keyframes waterRise {
|
||
0% { height: 0%; }
|
||
100% { height: ${finalHeight}%; }
|
||
}
|
||
`;
|
||
styleSheet.sheet.insertRule(riseKeyframes, 0);
|
||
container.style.animation = `waterRise ${riseDuration}s ${riseEasing} forwards`;
|
||
|
||
// 随机生成方向组合:两左一右 或 两右一左
|
||
const directions = randomChoice([
|
||
[1, 1, -1], // 两右一左
|
||
[1, -1, -1], // 一右两左
|
||
[-1, 1, 1], // 一左两右
|
||
[-1, -1, 1] // 两左一右
|
||
]);
|
||
|
||
// 2. 为每个波浪生成随机参数
|
||
waves.forEach((wave, index) => {
|
||
// 生成随机波浪形状
|
||
const svgData = generateWaveSVG(index);
|
||
const color = waveColors[index];
|
||
|
||
// 创建SVG
|
||
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${svgData.width} 1000' preserveAspectRatio='none'><path d='${svgData.path}' fill='${color}'/></svg>`;
|
||
const dataUrl = `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
||
|
||
// 应用背景
|
||
wave.style.backgroundImage = `url("${dataUrl}")`;
|
||
wave.style.backgroundSize = `${svgData.waveWidth}px 100%`;
|
||
|
||
// 为每层设置不同的速度范围,确保速度差异明显
|
||
let duration;
|
||
if (index === 0) {
|
||
duration = random(16, 22); // 第一层:慢速
|
||
} else if (index === 1) {
|
||
duration = random(11, 16); // 第二层:中速
|
||
} else {
|
||
duration = random(7, 12); // 第三层:快速
|
||
}
|
||
|
||
const direction = directions[index]; // 使用预设的方向
|
||
const distance = svgData.waveWidth * direction;
|
||
const initialPos = randomInt(-200, 200); // 随机初始位置
|
||
const delay = random(0, 1.5); // 随机延迟 0-1.5秒
|
||
|
||
// 创建波浪移动动画
|
||
const moveKeyframes = `
|
||
@keyframes wave${index + 1}Move {
|
||
0% { background-position-x: ${initialPos}px; }
|
||
100% { background-position-x: ${initialPos + distance}px; }
|
||
}
|
||
`;
|
||
styleSheet.sheet.insertRule(moveKeyframes, 0);
|
||
|
||
// 应用动画
|
||
wave.style.animation = `wave${index + 1}Move ${duration}s linear infinite`;
|
||
wave.style.animationDelay = `${delay}s`;
|
||
wave.style.backgroundPositionX = `${initialPos}px`;
|
||
});
|
||
|
||
console.log(`🌊 水面动画已生成
|
||
- 上涨时长: ${riseDuration.toFixed(1)}秒
|
||
- 最终高度: ${finalHeight.toFixed(1)}%
|
||
- 缓动效果: ${riseEasing}
|
||
- 波浪方向: [${directions.map(d => d > 0 ? '→' : '←').join(', ')}]`);
|
||
}
|
||
|
||
// 重新播放动画
|
||
function restartAnimation() {
|
||
// 移除旧的动态样式
|
||
const oldStyle = document.getElementById('dynamic-waves-style');
|
||
if (oldStyle) {
|
||
oldStyle.remove();
|
||
}
|
||
|
||
// 重置容器
|
||
const container = document.getElementById('waterContainer');
|
||
container.style.animation = 'none';
|
||
container.style.height = '0%';
|
||
|
||
// 重置所有波浪
|
||
const waves = document.querySelectorAll('.wave');
|
||
waves.forEach(wave => {
|
||
wave.style.animation = 'none';
|
||
wave.style.animationDelay = '0s';
|
||
wave.style.backgroundPositionX = '0px';
|
||
});
|
||
|
||
// 强制重绘
|
||
void container.offsetHeight;
|
||
|
||
// 重新初始化
|
||
setTimeout(() => {
|
||
initWaterAnimation();
|
||
}, 50);
|
||
}
|
||
|
||
// 页面加载时初始化
|
||
window.addEventListener('load', initWaterAnimation);
|
||
</script>
|
||
|
||
</body>
|
||
</html> |