我正在使用 HTML+CSS+JavaScript 做一个番茄计时器。
我想要一个围绕圆圈的进度动画,从圆圈顶部开始顺时针旋转,直到完成整个圆圈。
我已经尝试了很多不同的方法来编写代码,但无论我做什么,我都无法使其正常工作。
这是我的代码:
const bells = new Audio('./sounds/bell.wav');
const startBtn = document.querySelector('.btn-start');
const pauseBtn = document.querySelector('.btn-pause');
const resetBtn = document.querySelector('.btn-reset');
const session = document.querySelector('.minutes');
const sessionInput = document.querySelector('#session-length');
const breakInput = document.querySelector('#break-length');
let myInterval;
let state = true;
let isPaused = false
let totalSeconds;
let initialSeconds;
const updateTimerDisplay = () => {
const minuteDiv = document.querySelector('.minutes');
const secondDiv = document.querySelector('.seconds');
let minutesLeft = Math.floor(totalSeconds / 60);
let secondsLeft = totalSeconds % 60;
secondDiv.textContent = secondsLeft < 10 ? '0' + secondsLeft : secondsLeft;
minuteDiv.textContent = `${minutesLeft}`;
// Update the circle animation
const leftSide = document.querySelector('.left-side');
const rightSide = document.querySelector('.right-side');
const duration = initialSeconds; // Total duration in seconds
const elapsed = initialSeconds - totalSeconds;
const percentage = (elapsed / duration) * 100;
let rotationRight, rotationLeft;
if (percentage <= 50) {
// First half: rotate right side down from top to bottom
rotationRight = (percentage / 50) * 180 - 90;
rotationLeft = -90; // Keep left side at the top
} else {
// Second half: rotate left side up from bottom to top
rotationRight = 90; // Keep right side at the bottom
rotationLeft = ((percentage - 50) / 50) * 180 + 90;
}
rightSide.style.transform = `rotate(${rotationRight}deg)`;
leftSide.style.transform = `rotate(${rotationLeft}deg)`;
};
const appTimer = () => {
const sessionAmount = Number.parseInt(sessionInput.value)
if (isNaN(sessionAmount) || sessionAmount <= 0) {
alert('Please enter a valid session duration.');
return;
}
session.textContent = sessionAmount; // Update the session display
if(state) {
state = false;
totalSeconds = sessionAmount * 60;
initialSeconds = totalSeconds;
myInterval = setInterval(() => {
if (!isPaused) {
totalSeconds--;
updateTimerDisplay();
if (totalSeconds <= 0) {
bells.play();
clearInterval(myInterval);
startBreakTimer();
}
}
}, 1000);
} else {
alert('Session has already started.');
}
};
const pauseTimer = () => {
if (!state) {
isPaused = !isPaused;
pauseBtn.textContent = isPaused ? 'resume' : 'pause';
}
}
const startBreakTimer = () => {
const breakAmount = Number.parseInt(breakInput.value);
if (isNaN(breakAmount) || breakAmount <= 0) {
alert('Please enter a valid break duration.');
return;
}
totalSeconds = breakAmount * 60;
initialSeconds = totalSeconds;
myInterval = setInterval(() => {
if (!isPaused) {
totalSeconds--;
updateTimerDisplay();
if (totalSeconds <= 0) {
bells.play();
clearInterval(myInterval);
state = true;
}
}
}, 1000);
};
const resetTimer = () => {
clearInterval(myInterval);
state = true;
isPaused = false;
pauseBtn.textContent = 'pause';
const minuteDiv = document.querySelector('.minutes');
const secondDiv = document.querySelector('.seconds');
minuteDiv.textContent = sessionInput.value;;
secondDiv.textContent = '00';
// Reset circle animation
const leftSide = document.querySelector('.left-side');
const rightSide = document.querySelector('.right-side');
leftSide.style.transform = 'rotate(-90deg)';
rightSide.style.transform = 'rotate(-90deg)';
}
startBtn.addEventListener('click', appTimer);
pauseBtn.addEventListener('click', pauseTimer);
resetBtn.addEventListener('click', resetTimer);
html {
font-family: 'Fira Sans', sans-serif;
font-size: 20px;
letter-spacing: 0.8px;
min-height: 100vh;
color: #d8e9ef;
background-image: linear-gradient(-20deg, #025159 0%, #733b36 100%);
background-size: cover;
}
h1 {
margin: 0 auto 10px auto;
color: #d8e9ef;
}
p {
margin: 0;
}
.app-message {
height: 20px;
margin: 10px auto 20px auto;
}
.app-container {
width: 250px;
height: 420px;
margin: 40px auto;
text-align: center;
border-radius: 5px;
padding: 20px;
}
/*@keyframes rotate-right-from-top {
0% {
transform: rotate(-90deg);
}
100% {
transform: rotate(90deg);
}
}
@keyframes rotate-left-from-top {
0% {
transform: rotate(-90deg);
}
100% {
transform: rotate(90deg);
}
}*/
.app-circle {
position: relative;
margin: 0 auto;
width: 200px;
height: 200px;
}
.circle-shape {
pointer-events: none;
}
.semi-circle {
position: absolute;
width: 100px;
height: 200px;
box-sizing: border-box;
border: solid 6px;
transform: rotate(-90deg);
transform-origin: 50% 100%; /* Set the transform origin to the bottom center */
}
.left-side {
top: 0;
left: 0;
transform-origin: right center;
/*transform: rotate(0deg);*/
border-top-left-radius: 100px;
border-bottom-left-radius: 100px;
border-right: none;
z-index: 1;
/*animation-name: rotate-left-from-top;*/
}
.right-side {
top: 0;
left: 100px;
transform-origin: left center;
/*transform: rotate(0deg);*/
border-top-right-radius: 100px;
border-bottom-right-radius: 100px;
border-left: none;
/*animation-name: rotate-right-from-top;*/
}
.circle {
border-color: #bf5239;
}
.circle-mask {
border-color: #e85a71;
}
.app-counter-box {
font-family: 'Droid Sans Mono', monospace;
font-size: 250%;
position: relative;
top: 50px;
color: #d8e9ef;
}
button {
position: relative;
top: 50px;
font-size: 80%;
text-transform: uppercase;
letter-spacing: 1px;
border: none;
background: none;
outline: none;
color: #d8e9ef;
margin: 5px;
}
button:hover {
color: #90c0d1;
}
.btn-pause, .btn-reset {
display: inline-block;
}
.settings {
position: relative;
top: 100px;
display: flex;
flex-direction: column;
align-items: center;
}
.settings label {
margin: 5px 0;
color: #d8e9ef;
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=EG+Garamond:wght@400;500;600;700&family=Fira+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./style.css" />
<title>Pomodoro App</title>
</head>
<body>
<div class="app-container">
<h1>pomodoro</h1>
<div class="app-message">press start to begin</div>
<div class="app-circle">
<div class="circle-shape">
<div class="semi-circle right-side circle-mask"></div>
<div class="semi-circle right-side circle"></div>
<div class="semi-circle left-side circle-mask"></div>
<div class="semi-circle left-side circle"></div>
</div>
<div class="app-counter-box">
<p><span class="minutes">25</span>:<span class="seconds">00</span></p>
</div>
<button class="btn-start">start</button>
<button class="btn-pause">pause</button>
<button class="btn-reset">reset</button>
<div class="settings">
<label>Session (minutes): <input type="number" id="session-length" value="25"></label>
<label>Break (minutes): <input type="number" id="break-length" value="5"></label>
</div>
</div>
</div>
</body>
<script src="./app.js"></script>
</html>
现在什么也没有发生,直到计时器达到总时间的一半,然后,它填充了圆圈的上半部分,动画从圆圈左侧的中间开始。
有人可以帮我吗?
谢谢!
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=EG+Garamond:wght@400;500;600;700&family=Fira+Sans:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="./style.css" />
<title>Pomodoro App</title>
</head>
<body>
<div class="app-container">
<h1>pomodoro</h1>
<div class="app-message">press start to begin</div>
<div class="app-circle">
<div class="circle-shape">
<div class="semi-circle right-side circle-mask"></div>
<div class="semi-circle right-side circle"></div>
<div class="semi-circle left-side circle-mask"></div>
<div class="semi-circle left-side circle"></div>
</div>
<div class="app-counter-box">
<p><span class="minutes">25</span>:<span class="seconds">00</span></p>
</div>
</div>
<button class="btn-start">start</button>
<button class="btn-pause">pause</button>
<button class="btn-reset">reset</button>
<div class="settings">
<label>Session (minutes): <input type="number" id="session-length" value="25"></label>
<label>Break (minutes): <input type="number" id="break-length" value="5"></label>
</div>
</div>
<script src="./app.js"></script>
</body>
</html>
html {
font-family: 'Fira Sans', sans-serif;
font-size: 20px;
letter-spacing: 0.8px;
min-height: 100vh;
color: #d8e9ef;
background-image: linear-gradient(-20deg, #025159 0%, #733b36 100%);
background-size: cover;
}
h1 {
margin: 0 auto 10px auto;
color: #d8e9ef;
}
p {
margin: 0;
}
.app-message {
height: 20px;
margin: 10px auto 20px auto;
}
.app-container {
width: 250px;
height: 420px;
margin: 40px auto;
text-align: center;
border-radius: 5px;
padding: 20px;
}
.app-circle {
position: relative;
margin: 0 auto;
width: 200px;
height: 200px;
}
.circle-shape {
pointer-events: none;
}
.semi-circle {
position: absolute;
width: 100px;
height: 200px;
box-sizing: border-box;
border: solid 6px;
transform-origin: 50% 100%;
transition: transform 1s linear;
}
.left-side {
top: 0;
left: 0;
transform-origin: right center;
border-top-left-radius: 100px;
border-bottom-left-radius: 100px;
border-right: none;
z-index: 1;
}
.right-side {
top: 0;
left: 100px;
transform-origin: left center;
border-top-right-radius: 100px;
border-bottom-right-radius: 100px;
border-left: none;
}
.circle {
border-color: #bf5239;
}
.circle-mask {
border-color: #e85a71;
}
.app-counter-box {
font-family: 'Droid Sans Mono', monospace;
font-size: 250%;
position: relative;
top: 50px;
color: #d8e9ef;
}
button {
position: relative;
top: 50px;
font-size: 80%;
text-transform: uppercase;
letter-spacing: 1px;
border: none;
background: none;
outline: none;
color: #d8e9ef;
margin: 5px;
}
button:hover {
color: #90c0d1;
}
.btn-pause, .btn-reset {
display: inline-block;
}
.settings {
position: relative;
top: 100px;
display: flex;
flex-direction: column;
align-items: center;
}
.settings label {
margin: 5px 0;
color: #d8e9ef;
}
const bells = new Audio('./sounds/bell.wav');
const startBtn = document.querySelector('.btn-start');
const pauseBtn = document.querySelector('.btn-pause');
const resetBtn = document.querySelector('.btn-reset');
const session = document.querySelector('.minutes');
const sessionInput = document.querySelector('#session-length');
const breakInput = document.querySelector('#break-length');
let myInterval;
let state = true;
let isPaused = false
let totalSeconds;
let initialSeconds;
const updateTimerDisplay = () => {
const minuteDiv = document.querySelector('.minutes');
const secondDiv = document.querySelector('.seconds');
let minutesLeft = Math.floor(totalSeconds / 60);
let secondsLeft = totalSeconds % 60;
secondDiv.textContent = secondsLeft < 10 ? '0' + secondsLeft : secondsLeft;
minuteDiv.textContent = `${minutesLeft}`;
// Update the circle animation
const leftSide = document.querySelector('.left-side');
const rightSide = document.querySelector('.right-side');
const duration = initialSeconds; // Total duration in seconds
const elapsed = initialSeconds - totalSeconds;
const percentage = (elapsed / duration) * 100;
let rotationRight, rotationLeft;
if (percentage <= 50) {
// First half: rotate right side down from top to bottom
rotationRight = (percentage / 50) * 180 - 90;
rotationLeft = -90; // Keep left side at the top
} else {
// Second half: rotate left side up from bottom to top
rotationRight = 90; // Keep right side at the bottom
rotationLeft = ((percentage - 50) / 50) * 180 - 90;
}
rightSide.style.transform = `rotate(${rotationRight}deg)`;
leftSide.style.transform = `rotate(${rotationLeft}deg)`;
};
const appTimer = () => {
const sessionAmount = Number.parseInt(sessionInput.value);
if (isNaN(sessionAmount) || sessionAmount <= 0) {
alert('Please enter a valid session duration.');
return;
}
session.textContent = sessionAmount; // Update the session display
if(state) {
state = false;
totalSeconds = sessionAmount * 60;
initialSeconds = totalSeconds;
myInterval = setInterval(() => {
if (!isPaused) {
totalSeconds--;
updateTimerDisplay();
if (totalSeconds <= 0) {
bells.play();
clearInterval(myInterval);
startBreakTimer();
}
}
}, 1000);
} else {
alert('Session has already started.');
}
};
const pauseTimer = () => {
if (!state) {
isPaused = !isPaused;
pauseBtn.textContent = isPaused ? 'resume' : 'pause';
}
};
const startBreakTimer = () => {
const breakAmount = Number.parseInt(breakInput.value);
if (isNaN(breakAmount) || breakAmount <= 0) {
alert('Please enter a valid break duration.');
return;
}
totalSeconds = breakAmount * 60;
initialSeconds = totalSeconds;
myInterval = setInterval(() => {
if (!isPaused) {
totalSeconds--;
updateTimerDisplay();
if (totalSeconds <= 0) {
bells.play();
clearInterval(myInterval);
state = true;
}
}
}, 1000);
};
const resetTimer = () => {
clearInterval(myInterval);
state = true;
isPaused = false;
pauseBtn.textContent = 'pause';
const minuteDiv = document.querySelector('.minutes');
const secondDiv = document.querySelector('.seconds');
minuteDiv.textContent = sessionInput.value;
secondDiv.textContent = '00';
// Reset circle animation
const leftSide = document.querySelector('.left-side');
const rightSide = document.querySelector('.right-side');
leftSide.style.transform = 'rotate(-90deg)';
rightSide.style.transform = 'rotate(-90deg)';
};
startBtn.addEventListener('click', appTimer);
pauseBtn.addEventListener('click', pauseTimer);
resetBtn.addEventListener('click', resetTimer);