diff --git a/src/oj/problem/composables/useFireworks.ts b/src/oj/problem/composables/useFireworks.ts index c036e42..10c6f9c 100644 --- a/src/oj/problem/composables/useFireworks.ts +++ b/src/oj/problem/composables/useFireworks.ts @@ -1,10 +1,72 @@ import confetti from "canvas-confetti" +// 表情图标取自 @iconify 的 streamline-emojis 图标集,与项目内 用法保持一致 +const EMOJI_SVGS = [ + // 星星眼 + '', + // 点赞 + '', + // 火箭 + '', +] + +// 将 emoji SVG 转成 confetti 可用的彩色位图 Shape,缓存后供重复调用复用 +let emojiShapesPromise: Promise | null = null + +function svgToBitmapShape(svg: string): Promise { + return new Promise((resolve, reject) => { + const blob = new Blob([svg], { type: "image/svg+xml" }) + const url = URL.createObjectURL(blob) + const img = new Image() + img.onload = async () => { + const renderSize = 64 + const baseUnit = 10 + const canvas = document.createElement("canvas") + canvas.width = renderSize + canvas.height = renderSize + const ctx = canvas.getContext("2d")! + ctx.drawImage(img, 0, 0, renderSize, renderSize) + URL.revokeObjectURL(url) + + try { + const bitmap = await createImageBitmap(canvas) + const scale = baseUnit / renderSize + resolve({ + type: "bitmap", + bitmap, + matrix: [ + scale, + 0, + 0, + scale, + (-renderSize * scale) / 2, + (-renderSize * scale) / 2, + ], + } as unknown as confetti.Shape) + } catch (e) { + reject(e) + } + } + img.onerror = reject + img.src = url + }) +} + +function loadEmojiShapes(): Promise { + if (!emojiShapesPromise) { + emojiShapesPromise = Promise.all(EMOJI_SVGS.map(svgToBitmapShape)) + } + return emojiShapesPromise +} + /** * 随机烟花效果 Composable - * 提供7种不同风格的烟花庆祝效果 + * 提供10种不同风格的烟花庆祝效果 */ export function useFireworks() { + // 提前预加载表情包位图,避免首次触发效果8时出现解码延迟 + loadEmojiShapes() + /** * 触发随机烟花效果 */ @@ -214,6 +276,57 @@ export function useFireworks() { setTimeout(shoot, 300) setTimeout(shoot, 400) }, + + // 效果8: 表情包烟花 + () => { + loadEmojiShapes().then((emojiShapes) => { + confetti({ + shapes: emojiShapes, + scalar: 6, + particleCount: 18, + spread: 360, + startVelocity: 45, + gravity: 0.7, + ticks: 150, + origin: { y: 0.6 }, + }) + }) + }, + + // 效果9: 风车螺旋 + () => { + const colors = ["#ff9a9e", "#fad0c4", "#fbc2eb", "#a18cd1", "#fad961"] + + for (let angle = 0; angle < 360; angle += 30) { + setTimeout( + () => { + confetti({ + particleCount: 15, + angle, + spread: 20, + startVelocity: 40, + origin: { x: 0.5, y: 0.5 }, + colors, + }) + }, + (angle / 30) * 80, + ) + } + }, + + // 效果10: 礼花瀑布 + () => { + confetti({ + particleCount: 150, + spread: 180, + startVelocity: 0, + gravity: 1.2, + decay: 0.95, + ticks: 300, + origin: { x: 0.5, y: 0 }, + colors: ["#f6d365", "#fda085", "#fbc2eb", "#a6c1ee", "#84fab0"], + }) + }, ] // 随机选择一种效果