优雅翻页时钟与倒计时项目说明
项目概述
这是一个基于纯前端技术栈开发的优雅翻页时钟与倒计时应用,具有现代化的UI设计和流畅的动画效果,具备屏幕常亮属性。
技术栈
- HTML5
- CSS3
- JavaScript (原生)
- Web Wake Lock API (用于控制屏幕常亮)
主要功能
-
实时时钟显示
- 24小时制时间显示
- 数字翻页动画效果
-
倒计时功能
- 支持多个预设时间选项(10/20/30/45/60/90分钟)
- 倒计时结束自动停止
- 数字翻页动画效果
-
屏幕常亮功能
- 使用Web Wake Lock API
- 状态指示器显示
- 页面切换自动重新请求
关键代码实现
1. 翻页动画效果
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.flip-animation {
animation: pulse 0.5s ease;
}
2. 屏幕常亮实现
async function requestWakeLock() {
try {
wakeLock = await navigator.wakeLock.request('screen');
updateWakeLockStatus(true);
console.log('屏幕常亮已启用');
} catch (err) {
console.error('启用屏幕常亮失败:', err);
updateWakeLockStatus(false);
}
}
3. 倒计时核心逻辑
function updateCountdown() {
if (!endTime) return;
const now = new Date();
const timeDiff = endTime - now;
if (timeDiff <= 0) {
clearInterval(countdownInterval);
endTime = null;
// 更新显示和释放屏幕常亮...
return;
}
const hours = String(Math.floor(timeDiff / (1000 * 60 * 60))).padStart(2, '0');
const minutes = String(Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60))).padStart(2, '0');
const seconds = String(Math.floor((timeDiff % (1000 * 60)) / 1000)).padStart(2, '0');
// 更新显示...
}
UI设计特点
- 毛玻璃效果背景
- 渐变背景色
- 悬浮效果
- 响应式设计
- 优雅的动画过渡
运行截图
实时时钟显示界面
倒计时功能界面
项目亮点
- 纯前端实现,无需后端支持
- 现代化UI设计
- 流畅的动画效果
- 支持屏幕常亮
- 响应式设计,支持移动端
使用说明
- 打开网页即可看到实时时钟显示
- 点击倒计时按钮可启动对应时长的倒计时
- 倒计时开始后屏幕会保持常亮
- 倒计时结束后自动关闭常亮
浏览器兼容性
- Chrome 84+
- Edge 84+
- Safari 14+
- Firefox 79+
注:屏幕常亮功能需要浏览器支持Web Wake Lock API
完整代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>优雅翻页时钟与倒计时</title>
<style>
body {
background: linear-gradient(135deg, #1a1a1a, #2d2d2d);
color: #fff;
font-family: 'Helvetica Neue', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
overflow-x: hidden;
overflow-y: hidden;
}
.clock-container, .countdown-container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
margin: 20px 0;
width: 100%;
max-width: 800px;
}
.countdown-container {
display: flex;
flex-direction: column;
gap: 20px;
}
.flip-clock, .countdown-row {
display: flex;
gap: 15px;
justify-content: center;
}
.flip-unit {
position: relative;
background: rgba(0, 0, 0, 0.3);
border-radius: 12px;
padding: 25px 15px;
min-width: 120px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease;
}
.flip-unit:hover {
transform: translateY(-5px);
}
.flip-card {
font-size: 64px;
font-weight: 300;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
display: flex;
justify-content: center;
align-items: center;
}
.countdown-row .flip-card {
font-size: 48px;
}
.label {
text-align: center;
font-size: 16px;
color: rgba(255, 255, 255, 0.6);
margin-top: 10px;
text-transform: uppercase;
letter-spacing: 2px;
}
.separator {
display: flex;
align-items: center;
font-size: 54px;
padding: 0 5px;
color: rgba(255, 255, 255, 0.3);
}
.countdown-title {
text-align: center;
font-size: 24px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 10px;
}
.countdown-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
.timer-button {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #fff;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
}
.timer-button:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.timer-button.active {
background: rgba(255, 255, 255, 0.3);
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.flip-animation {
animation: pulse 0.5s ease;
}
@media (max-width: 768px) {
.flip-unit {
min-width: 80px;
padding: 15px 10px;
}
.flip-card {
font-size: 40px;
}
.countdown-row .flip-card {
font-size: 32px;
}
.separator {
font-size: 32px;
}
}
/* 添加常亮状态指示器样式 */
.wake-lock-status {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.1);
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.wake-lock-status.active {
background: rgba(76, 175, 80, 0.2);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
}
.wake-lock-status.active .status-dot {
background: #4CAF50;
}
</style>
</head>
<body>
<div class="clock-container">
<div class="flip-clock">
<div class="flip-unit">
<div class="flip-card" id="hours">00</div>
<div class="label">时</div>
</div>
<div class="separator">:</div>
<div class="flip-unit">
<div class="flip-card" id="minutes">00</div>
<div class="label">分</div>
</div>
<div class="separator">:</div>
<div class="flip-unit">
<div class="flip-card" id="seconds">00</div>
<div class="label">秒</div>
</div>
</div>
</div>
<div class="countdown-container">
<div class="countdown-title">倒计时</div>
<div class="countdown-buttons">
<button class="timer-button" data-minutes="10">10分钟</button>
<button class="timer-button" data-minutes="20">20分钟</button>
<button class="timer-button" data-minutes="30">30分钟</button>
<button class="timer-button" data-minutes="45">45分钟</button>
<button class="timer-button" data-minutes="60">60分钟</button>
<button class="timer-button" data-minutes="90">90分钟</button>
</div>
<div class="countdown-row" id="countdown">
<div class="flip-unit">
<div class="flip-card" id="countdown-hours">00</div>
<div class="label">时</div>
</div>
<div class="separator">:</div>
<div class="flip-unit">
<div class="flip-card" id="countdown-minutes">00</div>
<div class="label">分</div>
</div>
<div class="separator">:</div>
<div class="flip-unit">
<div class="flip-card" id="countdown-seconds">00</div>
<div class="label">秒</div>
</div>
</div>
</div>
<div class="wake-lock-status">
<span class="status-dot"></span>
<span class="status-text">常亮已关闭</span>
</div>
<script>
let countdownInterval;
let endTime = null;
let wakeLock = null;
function updateClock() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
updateTimeDisplay('hours', hours);
updateTimeDisplay('minutes', minutes);
updateTimeDisplay('seconds', seconds);
}
function updateCountdown() {
if (!endTime) return;
const now = new Date();
const timeDiff = endTime - now;
if (timeDiff <= 0) {
clearInterval(countdownInterval);
endTime = null;
updateTimeDisplay('countdown-hours', '00');
updateTimeDisplay('countdown-minutes', '00');
updateTimeDisplay('countdown-seconds', '00');
// 释放屏幕常亮
if (wakeLock) {
wakeLock.release();
wakeLock = null;
}
return;
}
const hours = String(Math.floor(timeDiff / (1000 * 60 * 60))).padStart(2, '0');
const minutes = String(Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60))).padStart(2, '0');
const seconds = String(Math.floor((timeDiff % (1000 * 60)) / 1000)).padStart(2, '0');
updateTimeDisplay('countdown-hours', hours);
updateTimeDisplay('countdown-minutes', minutes);
updateTimeDisplay('countdown-seconds', seconds);
}
function updateTimeDisplay(elementId, value) {
const element = document.getElementById(elementId);
if (element.textContent !== value) {
element.classList.remove('flip-animation');
void element.offsetWidth; // 触发重绘
element.classList.add('flip-animation');
element.textContent = value;
}
}
function startCountdown(minutes) {
clearInterval(countdownInterval);
const now = new Date();
endTime = new Date(now.getTime() + minutes * 60000);
updateCountdown();
countdownInterval = setInterval(updateCountdown, 1000);
// 启用屏幕常亮
requestWakeLock();
// 更新按钮状态
document.querySelectorAll('.timer-button').forEach(button => {
button.classList.remove('active');
if (parseInt(button.dataset.minutes) === minutes) {
button.classList.add('active');
}
});
}
// 请求屏幕常亮
async function requestWakeLock() {
try {
wakeLock = await navigator.wakeLock.request('screen');
updateWakeLockStatus(true);
wakeLock.addEventListener('release', () => {
updateWakeLockStatus(false);
});
console.log('屏幕常亮已启用');
} catch (err) {
console.error('启用屏幕常亮失败:', err);
updateWakeLockStatus(false);
}
}
// 更新常亮状态显示
function updateWakeLockStatus(isActive) {
const statusElement = document.querySelector('.wake-lock-status');
const statusText = document.querySelector('.status-text');
if (isActive) {
statusElement.classList.add('active');
statusText.textContent = '常亮已开启';
} else {
statusElement.classList.remove('active');
statusText.textContent = '常亮已关闭';
}
}
// 监听页面可见性变化
document.addEventListener('visibilitychange', async () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
await requestWakeLock();
}
});
// 设置倒计时按钮事件
document.querySelectorAll('.timer-button').forEach(button => {
button.addEventListener('click', () => {
const minutes = parseInt(button.dataset.minutes);
startCountdown(minutes);
});
});
setInterval(updateClock, 1000);
updateClock();
</script>
</body>
</html>