Files
dinner.tootaio.com/20250916/sponsor-list/script.js
xiaomai bcbae992a3 feat(sponsor-list): implement new design with animated canvas background
This commit introduces a completely redesigned sponsor list page. The previous two-column layout is replaced with a
modern, single-column auto-scrolling list that unifies all sponsors. A dynamic canvas animation has been added to the
background for a more engaging visual experience. Previous versions of the page have been archived. This update also
includes data corrections for sponsor names and minor fixes to the related PPT assets.
2025-09-16 23:48:44 +08:00

116 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 格式化金额,默认 MYR (RM)
* @param {Number|String} amount 金额
* @param {String} currency 币种ISO 4217默认 MYR
* @param {String} locale 语言地区,默认 "ms-MY"
* @returns {String} 格式化后的金额
*/
function formatCurrency(amount, currency = "MYR", locale = "ms-MY") {
const num = Number(amount);
if (isNaN(num)) return String(amount); // 非数字直接返回原值
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(num);
}
const sponsorListDiv = document.getElementById("sponsorList");
Promise.all([
fetch("../data/sponsors.json").then((res) => res.json()),
fetch("../data/seats.json").then((res) => res.json()),
]).then(([sponsors, seats]) => {
const sponsorList = sponsors.map((item) => ({
name: item.name,
amount: formatCurrency(item.amount), // 使用 Intl API
}));
const seatList = seats.map((item) => ({
name: item.name,
amount: `${item.seat}`,
}));
[...sponsorList, ...seatList].forEach((entry) => {
const card = document.createElement("div");
card.className = "sponsor-item card";
card.innerHTML = `<h2>${entry.name}</h2><p>${entry.amount}</p>`;
sponsorListDiv.appendChild(card);
});
});
// ================= Canvas 背景 =================
const canvas = document.getElementById("background");
const ctx = canvas.getContext("2d");
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
class Point {
constructor() {
this.radius = Math.random() * 4 + 2;
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.xSpeed = (Math.random() - 0.5) * 60;
this.ySpeed = (Math.random() - 0.5) * 60;
this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
this.lastDrawTime = null;
}
draw() {
const now = Date.now();
if (this.lastDrawTime) {
const dt = (now - this.lastDrawTime) / 1000;
this.x += this.xSpeed * dt;
this.y += this.ySpeed * dt;
}
if (this.x < 0 || this.x > canvas.width) this.xSpeed *= -1;
if (this.y < 0 || this.y > canvas.height) this.ySpeed *= -1;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.fill();
this.lastDrawTime = now;
}
}
class Graph {
constructor(pointCount = 70, maxDistance = 120) {
this.points = Array.from({ length: pointCount }, () => new Point());
this.maxDist = maxDistance;
}
draw() {
requestAnimationFrame(() => this.draw());
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < this.points.length; i++) {
const p1 = this.points[i];
p1.draw();
for (let j = i + 1; j < this.points.length; j++) {
const p2 = this.points[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < this.maxDist) {
const alpha = 1 - dist / this.maxDist;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.lineWidth = 2 * alpha;
ctx.stroke();
}
}
}
}
}
new Graph().draw();