'commit'
This commit is contained in:
531
static/Test/地狱大转盘.html
Normal file
531
static/Test/地狱大转盘.html
Normal file
@@ -0,0 +1,531 @@
|
||||
<!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: #ff4757;
|
||||
--secondary: #2f3542;
|
||||
--bg: #1e272e;
|
||||
--text: #f1f2f6;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 0 0 10px #ff6b6b;
|
||||
font-size: 2.5rem;
|
||||
letter-spacing: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 转盘容器 */
|
||||
.wheel-container {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
filter: drop-shadow(0 0 20px rgba(0,0,0,0.5));
|
||||
}
|
||||
|
||||
canvas#wheelCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 5s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
/* 指针 */
|
||||
.pointer {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40px;
|
||||
height: 60px;
|
||||
background: #ff4757;
|
||||
clip-path: polygon(50% 100%, 0 0, 100% 0);
|
||||
z-index: 10;
|
||||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
button#spinBtn {
|
||||
margin-top: 40px;
|
||||
padding: 15px 50px;
|
||||
font-size: 1.5rem;
|
||||
background: linear-gradient(45deg, #ff4757, #ff6b6b);
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 5px 15px rgba(255, 71, 87, 0.4);
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
outline: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button#spinBtn:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 5px rgba(255, 71, 87, 0.4);
|
||||
}
|
||||
|
||||
button#spinBtn:disabled {
|
||||
background: #57606f;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 结果模态框 - 地狱火烤风格 */
|
||||
#resultModal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
#resultModal.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.lucky-title {
|
||||
font-size: 2rem;
|
||||
color: #ced6e0;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition: all 0.5s 0.5s;
|
||||
}
|
||||
|
||||
#resultModal.active .lucky-title {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.victim-name {
|
||||
font-size: 6rem;
|
||||
font-weight: 900;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-shadow: 0 0 20px #ff4757;
|
||||
animation: shake 0.5s infinite;
|
||||
}
|
||||
|
||||
/* 痛苦的表情 */
|
||||
.face-emoji {
|
||||
font-size: 4rem;
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
right: -60px;
|
||||
animation: bounce 1s infinite alternate;
|
||||
}
|
||||
|
||||
/* 火焰画布 */
|
||||
canvas#fireCanvas {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 60%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 弹幕吐槽 */
|
||||
.danmaku {
|
||||
position: absolute;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
animation: fly linear forwards;
|
||||
font-weight: bold;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
}
|
||||
|
||||
@keyframes fly {
|
||||
from { transform: translateX(100vw); }
|
||||
to { transform: translateX(-100vw); }
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% { transform: translate(1px, 1px) rotate(0deg); }
|
||||
10% { transform: translate(-1px, -2px) rotate(-1deg); }
|
||||
20% { transform: translate(-3px, 0px) rotate(1deg); }
|
||||
30% { transform: translate(3px, 2px) rotate(0deg); }
|
||||
40% { transform: translate(1px, -1px) rotate(1deg); }
|
||||
50% { transform: translate(-1px, 2px) rotate(-1deg); }
|
||||
60% { transform: translate(-3px, 1px) rotate(0deg); }
|
||||
70% { transform: translate(3px, 1px) rotate(-1deg); }
|
||||
80% { transform: translate(-1px, -1px) rotate(1deg); }
|
||||
90% { transform: translate(1px, 2px) rotate(0deg); }
|
||||
100% { transform: translate(1px, -2px) rotate(-1deg); }
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
from { transform: translateY(0); }
|
||||
to { transform: translateY(-20px); }
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
margin-top: 50px;
|
||||
padding: 10px 30px;
|
||||
background: transparent;
|
||||
border: 2px solid #fff;
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
border-radius: 30px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>🔮 命运之轮 🔮</h1>
|
||||
|
||||
<div class="wheel-container">
|
||||
<div class="pointer"></div>
|
||||
<canvas id="wheelCanvas" width="500" height="500"></canvas>
|
||||
</div>
|
||||
|
||||
<button id="spinBtn">开始点名</button>
|
||||
|
||||
<!-- 结果模态框 -->
|
||||
<div id="resultModal">
|
||||
<canvas id="fireCanvas"></canvas>
|
||||
<div class="lucky-title">恭喜这位“幸运”同学...</div>
|
||||
<div class="victim-name" id="victimName">
|
||||
???
|
||||
<div class="face-emoji">😱</div>
|
||||
</div>
|
||||
<button class="close-btn" onclick="closeModal()">放过他/她吧</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ================= 配置数据 =================
|
||||
const students = ["刘美希", "林子琪", "唐纯瑞", "林子皓", "刘若曦", "王艺诺", "邹泓凯", "王梓博", "肖靖泽", "彭馨瑶", "刘丰源", "黄琬乔", "赵敏智"];
|
||||
const colors = [
|
||||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
|
||||
'#F7DC6F', '#BB8FCE', '#F1948A', '#82E0AA', '#D7BDE2',
|
||||
'#F0B27A', '#85C1E9', '#F5B7B1'
|
||||
];
|
||||
|
||||
// 吐槽弹幕库
|
||||
const comments = [
|
||||
"怎么又是我?!", "老师别看我...", "由于太帅被系统选中",
|
||||
"如果能重来...", "此时一位靓仔失去了梦想", "不要啊!!!",
|
||||
"这也太准了吧", "这就是命", "正在假装断网...", "瑟瑟发抖中"
|
||||
];
|
||||
|
||||
// ================= 变量与初始化 =================
|
||||
const canvas = document.getElementById('wheelCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const spinBtn = document.getElementById('spinBtn');
|
||||
const modal = document.getElementById('resultModal');
|
||||
const victimNameEl = document.getElementById('victimName');
|
||||
const fireCanvas = document.getElementById('fireCanvas');
|
||||
const fireCtx = fireCanvas.getContext('2d');
|
||||
|
||||
let startAngle = 0;
|
||||
const arc = Math.PI * 2 / students.length;
|
||||
let spinTimeout = null;
|
||||
let spinAngleStart = 10;
|
||||
let spinTime = 0;
|
||||
let spinTimeTotal = 0;
|
||||
let isSpinning = false;
|
||||
let fireAnimationId;
|
||||
|
||||
// 初始化
|
||||
resizeFireCanvas();
|
||||
drawRouletteWheel();
|
||||
window.addEventListener('resize', resizeFireCanvas);
|
||||
|
||||
// ================= 转盘逻辑 =================
|
||||
function drawRouletteWheel() {
|
||||
const centerX = canvas.width / 2;
|
||||
const centerY = canvas.height / 2;
|
||||
const outsideRadius = 240;
|
||||
const textRadius = 170;
|
||||
const insideRadius = 50;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.font = 'bold 18px Helvetica, Arial';
|
||||
|
||||
for(let i = 0; i < students.length; i++) {
|
||||
const angle = startAngle + i * arc;
|
||||
|
||||
// 绘制扇形
|
||||
ctx.fillStyle = colors[i % colors.length];
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, outsideRadius, angle, angle + arc, false);
|
||||
ctx.arc(centerX, centerY, insideRadius, angle + arc, angle, true);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
|
||||
// 绘制文字
|
||||
ctx.save();
|
||||
ctx.shadowColor = "rgba(0,0,0,0.5)";
|
||||
ctx.shadowBlur = 4;
|
||||
ctx.fillStyle = "white";
|
||||
ctx.translate(centerX + Math.cos(angle + arc / 2) * textRadius,
|
||||
centerY + Math.sin(angle + arc / 2) * textRadius);
|
||||
ctx.rotate(angle + arc / 2 + Math.PI / 2);
|
||||
const text = students[i];
|
||||
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function rotateWheel() {
|
||||
spinTime += 30;
|
||||
if(spinTime >= spinTimeTotal) {
|
||||
stopRotateWheel();
|
||||
return;
|
||||
}
|
||||
|
||||
// 缓动算法
|
||||
const spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
|
||||
startAngle += (spinAngle * Math.PI / 180);
|
||||
drawRouletteWheel();
|
||||
|
||||
// 播放转动音效(滴答声)
|
||||
playTickSound();
|
||||
|
||||
spinTimeout = setTimeout(rotateWheel, 30);
|
||||
}
|
||||
|
||||
function stopRotateWheel() {
|
||||
clearTimeout(spinTimeout);
|
||||
isSpinning = false;
|
||||
spinBtn.disabled = false;
|
||||
spinBtn.innerText = "再来一次";
|
||||
|
||||
const degrees = startAngle * 180 / Math.PI + 90;
|
||||
const arcd = arc * 180 / Math.PI;
|
||||
const index = Math.floor((360 - degrees % 360) / arcd);
|
||||
|
||||
const selectedStudent = students[index];
|
||||
showResult(selectedStudent);
|
||||
}
|
||||
|
||||
function easeOut(t, b, c, d) {
|
||||
const ts = (t/=d)*t;
|
||||
const tc = ts*t;
|
||||
return b+c*(tc + -3*ts + 3*t);
|
||||
}
|
||||
|
||||
spinBtn.addEventListener('click', () => {
|
||||
if(isSpinning) return;
|
||||
isSpinning = true;
|
||||
spinBtn.disabled = true;
|
||||
spinBtn.innerText = "命运抉择中...";
|
||||
|
||||
spinAngleStart = Math.random() * 10 + 10;
|
||||
spinTime = 0;
|
||||
spinTimeTotal = Math.random() * 3000 + 4000; // 4-7秒
|
||||
rotateWheel();
|
||||
});
|
||||
|
||||
// ================= 结果与特效 =================
|
||||
function showResult(name) {
|
||||
victimNameEl.innerHTML = `${name} <div class="face-emoji">😱</div>`;
|
||||
modal.classList.add('active');
|
||||
|
||||
// 播放惨叫/音效
|
||||
playScreamSound();
|
||||
speakName(name);
|
||||
|
||||
// 启动火焰粒子
|
||||
initFire();
|
||||
|
||||
// 发射弹幕
|
||||
launchDanmaku();
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
modal.classList.remove('active');
|
||||
cancelAnimationFrame(fireAnimationId);
|
||||
// 清除弹幕
|
||||
document.querySelectorAll('.danmaku').forEach(el => el.remove());
|
||||
}
|
||||
|
||||
function launchDanmaku() {
|
||||
const container = document.getElementById('resultModal');
|
||||
for(let i=0; i<15; i++) {
|
||||
setTimeout(() => {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'danmaku';
|
||||
el.innerText = comments[Math.floor(Math.random() * comments.length)];
|
||||
el.style.top = Math.random() * 80 + '%';
|
||||
el.style.animationDuration = (Math.random() * 3 + 2) + 's';
|
||||
container.appendChild(el);
|
||||
|
||||
// 动画结束后移除
|
||||
el.addEventListener('animationend', () => el.remove());
|
||||
}, Math.random() * 2000);
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 音效系统 (Web Audio API) =================
|
||||
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
const audioCtx = new AudioContext();
|
||||
|
||||
function playTickSound() {
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
|
||||
osc.type = 'triangle';
|
||||
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.1);
|
||||
|
||||
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
|
||||
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.1);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 0.1);
|
||||
}
|
||||
|
||||
function playScreamSound() {
|
||||
// 模拟一个低沉的“咚”声,表示厄运降临
|
||||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||||
|
||||
const osc = audioCtx.createOscillator();
|
||||
const gain = audioCtx.createGain();
|
||||
|
||||
osc.type = 'sawtooth';
|
||||
osc.frequency.setValueAtTime(100, audioCtx.currentTime);
|
||||
osc.frequency.exponentialRampToValueAtTime(10, audioCtx.currentTime + 1);
|
||||
|
||||
gain.gain.setValueAtTime(0.5, audioCtx.currentTime);
|
||||
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 1);
|
||||
|
||||
osc.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
|
||||
osc.start();
|
||||
osc.stop(audioCtx.currentTime + 1);
|
||||
}
|
||||
|
||||
function speakName(name) {
|
||||
// 使用浏览器语音合成
|
||||
if ('speechSynthesis' in window) {
|
||||
const utterance = new SpeechSynthesisUtterance("恭喜" + name + "同学");
|
||||
utterance.pitch = 0.5; // 低沉的声音,比较吓人
|
||||
utterance.rate = 0.8; // 慢一点
|
||||
utterance.volume = 1;
|
||||
window.speechSynthesis.speak(utterance);
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 火焰粒子特效 (Canvas) =================
|
||||
let particles = [];
|
||||
|
||||
function resizeFireCanvas() {
|
||||
fireCanvas.width = window.innerWidth;
|
||||
fireCanvas.height = window.innerHeight * 0.6;
|
||||
}
|
||||
|
||||
class Particle {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.x = Math.random() * fireCanvas.width;
|
||||
this.y = fireCanvas.height;
|
||||
this.size = Math.random() * 20 + 10;
|
||||
this.speedY = Math.random() * 3 + 2;
|
||||
this.speedX = (Math.random() - 0.5) * 2;
|
||||
this.life = 100; // 生命值
|
||||
this.color = { r: 255, g: Math.floor(Math.random() * 150), b: 0 };
|
||||
}
|
||||
|
||||
update() {
|
||||
this.y -= this.speedY;
|
||||
this.x += this.speedX;
|
||||
this.size *= 0.95; // 逐渐变小
|
||||
this.life -= 2;
|
||||
|
||||
// 颜色从黄变红变黑
|
||||
if (this.life < 80) this.color.g -= 5;
|
||||
if (this.color.g < 0) this.color.g = 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
fireCtx.beginPath();
|
||||
fireCtx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
fireCtx.fillStyle = `rgba(${this.color.r}, ${this.color.g}, ${this.color.b}, ${this.life / 100})`;
|
||||
fireCtx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function initFire() {
|
||||
particles = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
animateFire();
|
||||
}
|
||||
|
||||
function animateFire() {
|
||||
fireCtx.clearRect(0, 0, fireCanvas.width, fireCanvas.height);
|
||||
// 产生新粒子
|
||||
if (particles.length < 300) {
|
||||
particles.push(new Particle());
|
||||
particles.push(new Particle());
|
||||
}
|
||||
|
||||
particles.forEach((p, index) => {
|
||||
p.update();
|
||||
p.draw();
|
||||
if (p.life <= 0 || p.size <= 0.5) {
|
||||
particles.splice(index, 1);
|
||||
particles.push(new Particle()); // 循环利用
|
||||
}
|
||||
});
|
||||
|
||||
fireAnimationId = requestAnimationFrame(animateFire);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user