|
|
<!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> |
|
|
:root { |
|
|
--primary-color: #4CAF50; |
|
|
--secondary-color: #66BB6A; |
|
|
--accent-color: #81C784; |
|
|
--light-color: #E8F5E9; |
|
|
--text-color: #2E7D32; |
|
|
--shadow-color: rgba(76, 175, 80, 0.3); |
|
|
} |
|
|
|
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
|
} |
|
|
|
|
|
body { |
|
|
background: linear-gradient(135deg, var(--light-color) 0%, #C8E6C9 100%); |
|
|
color: var(--text-color); |
|
|
min-height: 100vh; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
padding: 1rem; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1200px; |
|
|
width: 100%; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
gap: 1.5rem; |
|
|
} |
|
|
|
|
|
header { |
|
|
text-align: center; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
font-size: 2.5rem; |
|
|
color: var(--primary-color); |
|
|
text-shadow: 2px 2px 4px var(--shadow-color); |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
font-size: 1.2rem; |
|
|
color: var(--accent-color); |
|
|
font-weight: 300; |
|
|
} |
|
|
|
|
|
|
|
|
.main-content { |
|
|
display: flex; |
|
|
width: 100%; |
|
|
flex-direction: row; |
|
|
gap: 2rem; |
|
|
align-items: flex-start; |
|
|
} |
|
|
|
|
|
.setup-panel { |
|
|
background-color: white; |
|
|
padding: 1.5rem; |
|
|
border-radius: 1rem; |
|
|
box-shadow: 0 10px 30px var(--shadow-color); |
|
|
width: 100%; |
|
|
max-width: 400px; |
|
|
transition: all 0.3s ease; |
|
|
flex-shrink: 0; |
|
|
} |
|
|
|
|
|
.setup-panel.collapsed { |
|
|
padding: 1rem; |
|
|
max-width: 400px; |
|
|
} |
|
|
|
|
|
.setup-panel.collapsed .form-content { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.setup-panel.collapsed .panel-title { |
|
|
margin-bottom: 0; |
|
|
} |
|
|
|
|
|
.panel-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 1.5rem; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.panel-title { |
|
|
color: var(--primary-color); |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.toggle-panel { |
|
|
background: none; |
|
|
border: none; |
|
|
font-size: 1.5rem; |
|
|
color: var(--accent-color); |
|
|
cursor: pointer; |
|
|
transition: transform 0.3s; |
|
|
} |
|
|
|
|
|
.setup-panel.collapsed .toggle-panel { |
|
|
transform: rotate(180deg); |
|
|
} |
|
|
|
|
|
.form-row { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin-bottom: 1.5rem; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
flex: 1; |
|
|
min-width: 150px; |
|
|
} |
|
|
|
|
|
label { |
|
|
display: block; |
|
|
margin-bottom: 0.5rem; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
input, select { |
|
|
width: 100%; |
|
|
padding: 0.8rem; |
|
|
border: 2px solid #A5D6A7; |
|
|
border-radius: 0.5rem; |
|
|
font-size: 1rem; |
|
|
background-color: var(--light-color); |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
input:focus, select:focus { |
|
|
outline: none; |
|
|
border-color: var(--primary-color); |
|
|
box-shadow: 0 0 0 3px var(--shadow-color); |
|
|
} |
|
|
|
|
|
.btn { |
|
|
background-color: var(--primary-color); |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 0.8rem 1.5rem; |
|
|
border-radius: 0.5rem; |
|
|
font-size: 1rem; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s; |
|
|
box-shadow: 0 4px 10px var(--shadow-color); |
|
|
} |
|
|
|
|
|
.btn:hover { |
|
|
background-color: var(--accent-color); |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 6px 15px var(--shadow-color); |
|
|
} |
|
|
|
|
|
.actions { |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
gap: 1rem; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.wheel-container { |
|
|
position: relative; |
|
|
width: 100%; |
|
|
max-width: 500px; |
|
|
aspect-ratio: 1; |
|
|
margin: 0 auto; |
|
|
} |
|
|
|
|
|
.wheel { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
position: relative; |
|
|
border-radius: 50%; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 0 30px var(--shadow-color), 0 0 60px rgba(255, 214, 102, 0.6); |
|
|
transition: box-shadow 0.3s; |
|
|
} |
|
|
|
|
|
.wheel-inner { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
border-radius: 50%; |
|
|
position: relative; |
|
|
transition: transform 5s cubic-bezier(0.1, 0.05, 0.1, 1.0); |
|
|
} |
|
|
|
|
|
.segment { |
|
|
position: absolute; |
|
|
width: 50%; |
|
|
height: 50%; |
|
|
transform-origin: bottom right; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.segment-content { |
|
|
position: absolute; |
|
|
left: 30px; |
|
|
width: 80px; |
|
|
text-align: center; |
|
|
transform: rotate(90deg); |
|
|
transform-origin: left; |
|
|
font-weight: bold; |
|
|
font-size: 0.9rem; |
|
|
color: rgba(255, 255, 255, 0.9); |
|
|
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.5); |
|
|
overflow: hidden; |
|
|
text-overflow: ellipsis; |
|
|
white-space: nowrap; |
|
|
} |
|
|
|
|
|
.wheel-pointer { |
|
|
position: absolute; |
|
|
top: -20px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
width: 40px; |
|
|
height: 60px; |
|
|
background-color: var(--accent-color); |
|
|
clip-path: polygon(50% 0%, 0% 100%, 100% 100%); |
|
|
z-index: 10; |
|
|
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.2)); |
|
|
} |
|
|
|
|
|
.wheel-center { |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
width: 15%; |
|
|
height: 15%; |
|
|
background: radial-gradient(circle, var(--light-color) 0%, var(--primary-color) 100%); |
|
|
border-radius: 50%; |
|
|
z-index: 5; |
|
|
box-shadow: 0 0 15px var(--shadow-color); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
color: white; |
|
|
font-weight: bold; |
|
|
font-size: 1.2rem; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.wheel-center:hover { |
|
|
box-shadow: 0 0 20px var(--accent-color); |
|
|
} |
|
|
|
|
|
.wheel-center:after { |
|
|
content: ""; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
width: 70%; |
|
|
height: 70%; |
|
|
background-color: var(--accent-color); |
|
|
border-radius: 50%; |
|
|
z-index: -1; |
|
|
} |
|
|
|
|
|
.result-display { |
|
|
background-color: white; |
|
|
padding: 1.5rem; |
|
|
border-radius: 1rem; |
|
|
box-shadow: 0 10px 30px var(--shadow-color); |
|
|
text-align: center; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
display: none; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
z-index: 10; |
|
|
overflow: hidden; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.result-display h2 { |
|
|
color: var(--primary-color); |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
.result-number { |
|
|
font-size: min(15vw, 8rem); |
|
|
font-weight: bold; |
|
|
color: var(--accent-color); |
|
|
margin: 0.5rem 0; |
|
|
position: relative; |
|
|
transition: all 0.3s; |
|
|
line-height: 1; |
|
|
} |
|
|
|
|
|
.result-text { |
|
|
font-size: max(1rem, min(3vw, 1.4rem)); |
|
|
margin-bottom: 1.2rem; |
|
|
} |
|
|
|
|
|
.shine { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: -100%; |
|
|
width: 50%; |
|
|
height: 100%; |
|
|
background: linear-gradient( |
|
|
90deg, |
|
|
rgba(255, 255, 255, 0) 0%, |
|
|
rgba(255, 255, 255, 0.8) 50%, |
|
|
rgba(255, 255, 255, 0) 100% |
|
|
); |
|
|
z-index: 10; |
|
|
animation: shine 2s infinite; |
|
|
opacity: 0; |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
.confetti { |
|
|
position: absolute; |
|
|
width: 10px; |
|
|
height: 10px; |
|
|
background-color: var(--primary-color); |
|
|
opacity: 0; |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
@keyframes shine { |
|
|
0% { |
|
|
left: -100%; |
|
|
opacity: 0; |
|
|
} |
|
|
10% { |
|
|
opacity: 1; |
|
|
} |
|
|
50% { |
|
|
left: 100%; |
|
|
opacity: 1; |
|
|
} |
|
|
51% { |
|
|
opacity: 0; |
|
|
} |
|
|
100% { |
|
|
opacity: 0; |
|
|
} |
|
|
} |
|
|
|
|
|
.spinning .wheel { |
|
|
box-shadow: 0 0 50px var(--accent-color), 0 0 100px rgba(255, 214, 102, 0.8); |
|
|
} |
|
|
|
|
|
.mode-description { |
|
|
display: none; |
|
|
margin-top: 0.5rem; |
|
|
font-size: 0.85rem; |
|
|
color: var(--accent-color); |
|
|
font-style: italic; |
|
|
} |
|
|
|
|
|
.special-effects { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
pointer-events: none; |
|
|
z-index: 20; |
|
|
} |
|
|
|
|
|
@keyframes flicker { |
|
|
0% { opacity: 1; } |
|
|
50% { opacity: 0.7; } |
|
|
100% { opacity: 1; } |
|
|
} |
|
|
|
|
|
@keyframes shake { |
|
|
0% { transform: translateX(0); } |
|
|
25% { transform: translateX(-5px); } |
|
|
50% { transform: translateX(0); } |
|
|
75% { transform: translateX(5px); } |
|
|
100% { transform: translateX(0); } |
|
|
} |
|
|
|
|
|
@keyframes jump { |
|
|
0% { transform: scale(1); } |
|
|
50% { transform: scale(1.2); } |
|
|
100% { transform: scale(1); } |
|
|
} |
|
|
|
|
|
@keyframes heartbeat { |
|
|
0% { transform: scale(1); } |
|
|
15% { transform: scale(1.15); } |
|
|
30% { transform: scale(1); } |
|
|
45% { transform: scale(1.15); } |
|
|
60% { transform: scale(1); } |
|
|
100% { transform: scale(1); } |
|
|
} |
|
|
|
|
|
|
|
|
@keyframes fall { |
|
|
0% { |
|
|
transform: translateY(0) rotate(0deg); |
|
|
opacity: 1; |
|
|
} |
|
|
100% { |
|
|
transform: translateY(400px) rotate(360deg); |
|
|
opacity: 0; |
|
|
} |
|
|
} |
|
|
|
|
|
.help-bubble { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
background-color: var(--primary-color); |
|
|
color: white; |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
border-radius: 50%; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-size: 1.5rem; |
|
|
cursor: pointer; |
|
|
box-shadow: 0 3px 10px var(--shadow-color); |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.help-bubble:hover { |
|
|
background-color: var(--accent-color); |
|
|
transform: scale(1.1); |
|
|
} |
|
|
|
|
|
.help-content { |
|
|
position: fixed; |
|
|
bottom: 80px; |
|
|
right: 20px; |
|
|
background-color: white; |
|
|
padding: 1.5rem; |
|
|
border-radius: 1rem; |
|
|
width: 300px; |
|
|
box-shadow: 0 10px 30px var(--shadow-color); |
|
|
display: none; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.help-content h3 { |
|
|
color: var(--primary-color); |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
.help-content p { |
|
|
margin-bottom: 0.8rem; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.close-help { |
|
|
position: absolute; |
|
|
top: 10px; |
|
|
right: 10px; |
|
|
font-size: 1.2rem; |
|
|
cursor: pointer; |
|
|
color: var(--accent-color); |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 992px) { |
|
|
.main-content { |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.setup-panel { |
|
|
max-width: 100%; |
|
|
order: 1; |
|
|
} |
|
|
|
|
|
.wheel-container { |
|
|
order: 0; |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
|
h1 { |
|
|
font-size: 2rem; |
|
|
} |
|
|
|
|
|
.container { |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
.setup-panel, .result-display { |
|
|
padding: 1.5rem; |
|
|
} |
|
|
|
|
|
.result-number { |
|
|
font-size: 3.5rem; |
|
|
} |
|
|
|
|
|
.form-row { |
|
|
flex-direction: column; |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
min-width: 100%; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<header> |
|
|
<h1>命运抉择之轮</h1> |
|
|
<p class="subtitle">掌控命运,抉择未来!</p> |
|
|
</header> |
|
|
|
|
|
<div class="main-content"> |
|
|
<div class="setup-panel"> |
|
|
<div class="panel-header"> |
|
|
<h2 class="panel-title">设置</h2> |
|
|
<button class="toggle-panel">▲</button> |
|
|
</div> |
|
|
|
|
|
<div class="form-content"> |
|
|
<div class="form-row"> |
|
|
<div class="form-group"> |
|
|
<label for="class-size">班级人数:</label> |
|
|
<input type="number" id="class-size" min="1" max="100" value="30"> |
|
|
</div> |
|
|
|
|
|
<div class="form-group"> |
|
|
<label for="start-number">起始编号:</label> |
|
|
<input type="number" id="start-number" min="0" value="1"> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="form-row"> |
|
|
<div class="form-group"> |
|
|
<label for="animation-speed">动画速度:</label> |
|
|
<select id="animation-speed"> |
|
|
<option value="slow">慢速 (更多悬念)</option> |
|
|
<option value="normal" selected>正常</option> |
|
|
<option value="fast">快速</option> |
|
|
<option value="insane">超快 (刺激模式)</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="form-row"> |
|
|
<div class="form-group"> |
|
|
<label for="picker-mode">选择模式:</label> |
|
|
<select id="picker-mode"> |
|
|
<option value="normal">正常模式</option> |
|
|
<option value="fake-out">假动作模式</option> |
|
|
<option value="oscillate">摇摆不定模式</option> |
|
|
<option value="mystery">神秘模式</option> |
|
|
<option value="double">双重选择模式</option> |
|
|
<option value="countdown">倒计时模式</option> |
|
|
<option value="heartbeat">心跳紧张模式</option> |
|
|
</select> |
|
|
<div id="mode-normal" class="mode-description">普通的转盘选择,公平公正。</div> |
|
|
<div id="mode-fake-out" class="mode-description">即将停止时,转盘会突然改变方向!</div> |
|
|
<div id="mode-oscillate" class="mode-description">在两个结果之间反复摇摆,到底会是谁呢?</div> |
|
|
<div id="mode-mystery" class="mode-description">结果会短暂显示然后突然变化,充满惊喜!</div> |
|
|
<div id="mode-double" class="mode-description">同时选出两个幸运儿!</div> |
|
|
<div id="mode-countdown" class="mode-description">紧张的倒计时,增加悬念感!</div> |
|
|
<div id="mode-heartbeat" class="mode-description">伴随心跳,感受命运的紧张抉择!</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="actions"> |
|
|
<button id="reset-btn" class="btn">保存修改/重新开始</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="wheel-container"> |
|
|
<div class="wheel-pointer"></div> |
|
|
<div class="wheel"> |
|
|
<div class="wheel-inner" id="wheel-inner"></div> |
|
|
</div> |
|
|
<div class="wheel-center" id="spin-btn">转!</div> |
|
|
|
|
|
<div class="result-display"> |
|
|
<div class="shine"></div> |
|
|
<h2>点名结果</h2> |
|
|
<div class="result-number" id="result-number">?</div> |
|
|
<p class="result-text" id="result-text">请点击转盘开始</p> |
|
|
<button id="spin-again-btn" class="btn">再来一次</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="help-bubble">?</div> |
|
|
<div class="help-content"> |
|
|
<span class="close-help">×</span> |
|
|
<h3>使用帮助</h3> |
|
|
<p><strong>正常模式:</strong> 普通的点名选择,公平公正。</p> |
|
|
<p><strong>假动作模式:</strong> 转盘减速后会有一次假动作,然后才会显示最终结果。</p> |
|
|
<p><strong>摇摆不定模式:</strong> 在两个选项之间来回摇摆,增加悬念感。</p> |
|
|
<p><strong>神秘模式:</strong> 先显示一个结果,然后突然改变!谁也猜不到最终会是谁。</p> |
|
|
<p><strong>双重选择模式:</strong> 同时选出两名同学,适合小组活动。</p> |
|
|
<p><strong>倒计时模式:</strong> 伴随紧张的倒计时动画,增加刺激感。</p> |
|
|
<p><strong>心跳紧张模式:</strong> 伴随心跳动画,让结果更加扣人心弦。</p> |
|
|
<p><strong>提示:</strong> 尝试不同的动画速度,获得最佳体验!</p> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const setupPanel = document.querySelector('.setup-panel'); |
|
|
const togglePanelBtn = document.querySelector('.toggle-panel'); |
|
|
const classSizeInput = document.getElementById('class-size'); |
|
|
const startNumberInput = document.getElementById('start-number'); |
|
|
const pickerModeSelect = document.getElementById('picker-mode'); |
|
|
const animationSpeedSelect = document.getElementById('animation-speed'); |
|
|
const resetBtn = document.getElementById('reset-btn'); |
|
|
const wheelContainer = document.querySelector('.wheel-container'); |
|
|
const wheelInner = document.getElementById('wheel-inner'); |
|
|
const spinBtn = document.getElementById('spin-btn'); |
|
|
const resultDisplay = document.querySelector('.result-display'); |
|
|
const resultNumber = document.getElementById('result-number'); |
|
|
const resultText = document.getElementById('result-text'); |
|
|
const spinAgainBtn = document.getElementById('spin-again-btn'); |
|
|
const helpBubble = document.querySelector('.help-bubble'); |
|
|
const helpContent = document.querySelector('.help-content'); |
|
|
const closeHelp = document.querySelector('.close-help'); |
|
|
|
|
|
|
|
|
const modeDescriptions = { |
|
|
normal: document.getElementById('mode-normal'), |
|
|
'fake-out': document.getElementById('mode-fake-out'), |
|
|
oscillate: document.getElementById('mode-oscillate'), |
|
|
mystery: document.getElementById('mode-mystery'), |
|
|
double: document.getElementById('mode-double'), |
|
|
countdown: document.getElementById('mode-countdown'), |
|
|
heartbeat: document.getElementById('mode-heartbeat') |
|
|
}; |
|
|
|
|
|
|
|
|
const colors = [ |
|
|
'#4CAF50', '#66BB6A', '#81C784', '#A5D6A7', |
|
|
'#C8E6C9', '#7CB342', '#8BC34A', '#9CCC65', |
|
|
'#AED581', '#C5E1A5', '#DCEDC8', '#43A047', |
|
|
'#388E3C', '#2E7D32', '#689F38', '#558B2F' |
|
|
]; |
|
|
|
|
|
|
|
|
let classSize = 30; |
|
|
let startNumber = 1; |
|
|
let mode = 'normal'; |
|
|
let animationSpeed = 'normal'; |
|
|
let isSpinning = false; |
|
|
let segments = []; |
|
|
let selectedSegment = null; |
|
|
let secondSelectedSegment = null; |
|
|
|
|
|
function updateModeDescription() { |
|
|
const selectedMode = pickerModeSelect.value; |
|
|
|
|
|
|
|
|
Object.values(modeDescriptions).forEach(desc => { |
|
|
if (desc) desc.style.display = 'none'; |
|
|
}); |
|
|
|
|
|
|
|
|
const descElement = document.getElementById(`mode-${selectedMode}`); |
|
|
if (descElement) { |
|
|
descElement.style.display = 'block'; |
|
|
} |
|
|
} |
|
|
|
|
|
function createWheel() { |
|
|
|
|
|
wheelInner.innerHTML = ''; |
|
|
segments = []; |
|
|
|
|
|
|
|
|
const segmentAngle = 360 / classSize; |
|
|
|
|
|
for (let i = 0; i < classSize; i++) { |
|
|
const segmentElement = document.createElement('div'); |
|
|
segmentElement.className = 'segment'; |
|
|
|
|
|
|
|
|
const rotation = i * segmentAngle; |
|
|
segmentElement.style.transform = `rotate(${rotation}deg)`; |
|
|
segmentElement.style.backgroundColor = colors[i % colors.length]; |
|
|
|
|
|
|
|
|
const contentElement = document.createElement('div'); |
|
|
contentElement.className = 'segment-content'; |
|
|
const studentNumber = startNumber + i; |
|
|
contentElement.textContent = studentNumber; |
|
|
|
|
|
segmentElement.appendChild(contentElement); |
|
|
wheelInner.appendChild(segmentElement); |
|
|
|
|
|
|
|
|
segments.push({ |
|
|
element: segmentElement, |
|
|
rotation, |
|
|
value: studentNumber |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
function setupWheel() { |
|
|
classSize = parseInt(classSizeInput.value) || 30; |
|
|
startNumber = parseInt(startNumberInput.value) || 1; |
|
|
mode = pickerModeSelect.value; |
|
|
animationSpeed = animationSpeedSelect.value; |
|
|
|
|
|
if (classSize < 1) { |
|
|
alert('班级人数必须大于0'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
createWheel(); |
|
|
|
|
|
|
|
|
resultDisplay.style.display = 'none'; |
|
|
} |
|
|
|
|
|
function spin() { |
|
|
if (isSpinning) return; |
|
|
|
|
|
isSpinning = true; |
|
|
wheelContainer.classList.add('spinning'); |
|
|
|
|
|
|
|
|
let spinDuration = 5000; |
|
|
|
|
|
switch (animationSpeed) { |
|
|
case 'slow': |
|
|
spinDuration = 8000; |
|
|
break; |
|
|
case 'fast': |
|
|
spinDuration = 3000; |
|
|
break; |
|
|
case 'insane': |
|
|
spinDuration = 1500; |
|
|
break; |
|
|
} |
|
|
|
|
|
|
|
|
wheelInner.style.transition = `transform ${spinDuration/1000}s cubic-bezier(0.1, 0.05, 0.1, 1.0)`; |
|
|
|
|
|
|
|
|
const minRotation = 1800; |
|
|
const maxRotation = 3600; |
|
|
|
|
|
|
|
|
const segmentAngle = 360 / classSize; |
|
|
const randomOffset = Math.floor(Math.random() * classSize); |
|
|
const finalSegmentIndex = randomOffset; |
|
|
|
|
|
|
|
|
let totalRotation = minRotation + (maxRotation - minRotation) * Math.random(); |
|
|
|
|
|
|
|
|
totalRotation += (finalSegmentIndex * segmentAngle); |
|
|
|
|
|
|
|
|
wheelInner.style.transform = `rotate(${-totalRotation}deg)`; |
|
|
|
|
|
|
|
|
selectedSegment = segments[finalSegmentIndex]; |
|
|
|
|
|
|
|
|
if (mode === 'double') { |
|
|
let secondIndex = (finalSegmentIndex + Math.floor(classSize / 2)) % classSize; |
|
|
secondSelectedSegment = segments[secondIndex]; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
processResult(); |
|
|
}, spinDuration); |
|
|
} |
|
|
|
|
|
function processResult() { |
|
|
isSpinning = false; |
|
|
wheelContainer.classList.remove('spinning'); |
|
|
|
|
|
let finalResult = selectedSegment.value; |
|
|
let secondResult = secondSelectedSegment ? secondSelectedSegment.value : null; |
|
|
|
|
|
switch (mode) { |
|
|
case 'fake-out': |
|
|
playFakeOutEffect(finalResult); |
|
|
break; |
|
|
|
|
|
case 'oscillate': |
|
|
playOscillateEffect(finalResult); |
|
|
break; |
|
|
|
|
|
case 'mystery': |
|
|
playMysteryEffect(finalResult); |
|
|
break; |
|
|
|
|
|
case 'double': |
|
|
showDoubleResult(finalResult, secondResult); |
|
|
break; |
|
|
|
|
|
case 'countdown': |
|
|
playCountdownEffect(finalResult); |
|
|
break; |
|
|
|
|
|
case 'heartbeat': |
|
|
playHeartbeatEffect(finalResult); |
|
|
break; |
|
|
|
|
|
default: |
|
|
showResult(finalResult); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
function showResult(result) { |
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
resultNumber.textContent = result; |
|
|
resultText.textContent = `恭喜 ${result} 号同学被选中!`; |
|
|
|
|
|
|
|
|
if (result.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
} |
|
|
function showDoubleResult(result1, result2) { |
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
resultNumber.textContent = `${result1} & ${result2}`; |
|
|
resultText.textContent = `恭喜 ${result1} 号和 ${result2} 号同学被选中!`; |
|
|
|
|
|
|
|
|
if (result1.toString().length + result2.toString().length > 4) { |
|
|
resultNumber.style.fontSize = `min(10vw, 5rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
} |
|
|
function playFakeOutEffect(finalResult) { |
|
|
|
|
|
const fakeResult = ((finalResult - startNumber + Math.floor(classSize / 2)) % classSize) + startNumber; |
|
|
|
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
resultNumber.textContent = fakeResult; |
|
|
resultText.textContent = `恭喜 ${fakeResult} 号同学被选中!`; |
|
|
|
|
|
|
|
|
if (fakeResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'shake 0.5s'; |
|
|
resultText.textContent = "等等,发生了什么..."; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
resultNumber.offsetHeight; |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = finalResult; |
|
|
resultText.textContent = `真正被选中的是 ${finalResult} 号同学!`; |
|
|
|
|
|
|
|
|
if (finalResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
}, 1000); |
|
|
}, 1500); |
|
|
} |
|
|
function playOscillateEffect(finalResult) { |
|
|
|
|
|
const altResult = ((finalResult - startNumber + Math.floor(classSize / 2)) % classSize) + startNumber; |
|
|
|
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
|
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
|
|
|
let count = 0; |
|
|
const maxOscillations = 6; |
|
|
const oscillationInterval = setInterval(() => { |
|
|
count++; |
|
|
const currentResult = count % 2 === 0 ? finalResult : altResult; |
|
|
resultNumber.textContent = currentResult; |
|
|
resultText.textContent = "究竟会是谁呢..."; |
|
|
|
|
|
|
|
|
if (currentResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
if (count >= maxOscillations) { |
|
|
clearInterval(oscillationInterval); |
|
|
|
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = finalResult; |
|
|
resultText.textContent = `恭喜 ${finalResult} 号同学被选中!`; |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
} |
|
|
}, 300); |
|
|
} |
|
|
function playMysteryEffect(finalResult) { |
|
|
|
|
|
const mysterySteps = 3; |
|
|
const stepDelay = 600; |
|
|
|
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
let step = 0; |
|
|
resultText.textContent = "神秘数字即将揭晓..."; |
|
|
|
|
|
|
|
|
resultNumber.style.animation = 'flicker 0.5s infinite'; |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
|
|
|
const mysteryInterval = setInterval(() => { |
|
|
step++; |
|
|
|
|
|
if (step < mysterySteps) { |
|
|
|
|
|
const randomNumber = Math.floor(Math.random() * classSize) + startNumber; |
|
|
resultNumber.textContent = randomNumber; |
|
|
|
|
|
|
|
|
if (randomNumber.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
} else { |
|
|
|
|
|
clearInterval(mysteryInterval); |
|
|
resultNumber.style.animation = 'none'; |
|
|
resultNumber.offsetHeight; |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = finalResult; |
|
|
resultText.textContent = `恭喜 ${finalResult} 号同学被选中!`; |
|
|
|
|
|
|
|
|
if (finalResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
} |
|
|
}, stepDelay); |
|
|
} |
|
|
function playCountdownEffect(finalResult) { |
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
resultNumber.textContent = "3"; |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
resultText.textContent = "倒计时开始..."; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = "2"; |
|
|
setTimeout(() => { resultNumber.style.animation = 'none'; }, 500); |
|
|
}, 1000); |
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = "1"; |
|
|
setTimeout(() => { resultNumber.style.animation = 'none'; }, 500); |
|
|
}, 2000); |
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = finalResult; |
|
|
resultText.textContent = `恭喜 ${finalResult} 号同学被选中!`; |
|
|
|
|
|
|
|
|
if (finalResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
}, 3000); |
|
|
} |
|
|
function playHeartbeatEffect(finalResult) { |
|
|
resultDisplay.style.display = 'flex'; |
|
|
|
|
|
resultNumber.textContent = "?"; |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
resultText.textContent = "命运正在抉择..."; |
|
|
|
|
|
|
|
|
resultNumber.style.animation = 'heartbeat 1s infinite'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
resultNumber.offsetHeight; |
|
|
resultNumber.style.animation = 'jump 0.5s'; |
|
|
resultNumber.textContent = finalResult; |
|
|
resultText.textContent = `命运选择了 ${finalResult} 号同学!`; |
|
|
|
|
|
|
|
|
if (finalResult.toString().length > 2) { |
|
|
resultNumber.style.fontSize = `min(12vw, 6rem)`; |
|
|
} else { |
|
|
resultNumber.style.fontSize = `min(15vw, 8rem)`; |
|
|
} |
|
|
|
|
|
|
|
|
const shine = document.querySelector('.shine'); |
|
|
shine.style.opacity = '1'; |
|
|
shine.style.animation = 'shine 2s 1'; |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
resultNumber.style.animation = 'none'; |
|
|
shine.style.opacity = '0'; |
|
|
shine.style.animation = 'none'; |
|
|
}, 2000); |
|
|
|
|
|
|
|
|
createConfetti(); |
|
|
}, 4000); |
|
|
} |
|
|
function createConfetti() { |
|
|
|
|
|
const existingEffects = document.querySelector('.special-effects'); |
|
|
if (existingEffects) { |
|
|
existingEffects.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
const specialEffects = document.createElement('div'); |
|
|
specialEffects.className = 'special-effects'; |
|
|
resultDisplay.appendChild(specialEffects); |
|
|
|
|
|
|
|
|
for (let i = 0; i < 50; i++) { |
|
|
const confetti = document.createElement('div'); |
|
|
confetti.className = 'confetti'; |
|
|
|
|
|
|
|
|
const left = Math.random() * 100 + '%'; |
|
|
const top = -20 + 'px'; |
|
|
|
|
|
|
|
|
const size = Math.random() * 8 + 5; |
|
|
|
|
|
|
|
|
const color = colors[Math.floor(Math.random() * colors.length)]; |
|
|
|
|
|
|
|
|
const shapes = ['circle', 'square', 'triangle']; |
|
|
const shape = shapes[Math.floor(Math.random() * shapes.length)]; |
|
|
|
|
|
|
|
|
confetti.style.left = left; |
|
|
confetti.style.top = top; |
|
|
confetti.style.width = size + 'px'; |
|
|
confetti.style.height = size + 'px'; |
|
|
confetti.style.backgroundColor = color; |
|
|
confetti.style.opacity = '1'; |
|
|
confetti.style.position = 'absolute'; |
|
|
|
|
|
|
|
|
if (shape === 'circle') { |
|
|
confetti.style.borderRadius = '50%'; |
|
|
} else if (shape === 'triangle') { |
|
|
confetti.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)'; |
|
|
} |
|
|
|
|
|
|
|
|
specialEffects.appendChild(confetti); |
|
|
|
|
|
|
|
|
const duration = Math.random() * 3 + 2; |
|
|
const delay = Math.random() * 1.5; |
|
|
|
|
|
confetti.style.animation = `fall ${duration}s ease-in ${delay}s forwards`; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
if (specialEffects && specialEffects.parentNode) { |
|
|
specialEffects.parentNode.removeChild(specialEffects); |
|
|
} |
|
|
}, 5000); |
|
|
} |
|
|
|
|
|
function reset() { |
|
|
|
|
|
setupPanel.classList.remove('collapsed'); |
|
|
resultDisplay.style.display = 'none'; |
|
|
|
|
|
|
|
|
wheelInner.style.transform = 'rotate(0deg)'; |
|
|
|
|
|
|
|
|
isSpinning = false; |
|
|
selectedSegment = null; |
|
|
secondSelectedSegment = null; |
|
|
|
|
|
|
|
|
setupWheel(); |
|
|
} |
|
|
|
|
|
|
|
|
function init() { |
|
|
|
|
|
setupWheel(); |
|
|
|
|
|
|
|
|
togglePanelBtn.addEventListener('click', () => { |
|
|
setupPanel.classList.toggle('collapsed'); |
|
|
togglePanelBtn.textContent = setupPanel.classList.contains('collapsed') ? '▼' : '▲'; |
|
|
}); |
|
|
|
|
|
resetBtn.addEventListener('click', reset); |
|
|
spinBtn.addEventListener('click', spin); |
|
|
spinAgainBtn.addEventListener('click', () => { |
|
|
resultDisplay.style.display = 'none'; |
|
|
}); |
|
|
|
|
|
pickerModeSelect.addEventListener('change', updateModeDescription); |
|
|
|
|
|
helpBubble.addEventListener('click', () => { |
|
|
helpContent.style.display = 'block'; |
|
|
}); |
|
|
|
|
|
closeHelp.addEventListener('click', () => { |
|
|
helpContent.style.display = 'none'; |
|
|
}); |
|
|
|
|
|
|
|
|
updateModeDescription(); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', init); |
|
|
</script> |
|
|
</body> |
|
|
</html> |