Skip to content

Instantly share code, notes, and snippets.

@stevecyj
Created July 16, 2025 02:58
Show Gist options
  • Select an option

  • Save stevecyj/30da2cf40804d2520036325bbbbc729c to your computer and use it in GitHub Desktop.

Select an option

Save stevecyj/30da2cf40804d2520036325bbbbc729c to your computer and use it in GitHub Desktop.
<template>
<div class="winning-animation">
<canvas ref="canvas" width="512" height="512"></canvas>
<div v-if="loading" class="loading">載入中...</div>
<div v-if="error" class="error">錯誤: {{ error }}</div>
</div>
</template>
<script>
export default {
name: "WinningAnimation",
props: {
autoPlay: {
type: Boolean,
default: true,
},
loop: {
type: Boolean,
default: true,
},
},
data() {
return {
animation: null,
stage: null,
loading: false,
error: null,
isLoaded: false,
loopCheckInterval: null,
}
},
async mounted() {
await this.initAnimation()
},
beforeDestroy() {
this.cleanup()
},
methods: {
async initAnimation() {
try {
this.loading = true
this.error = null
// 載入 CreateJS
await this.loadCreateJS()
// 載入動畫腳本
await this.loadAnimationScript()
// 設定動畫
this.setupAnimation()
this.isLoaded = true
this.loading = false
if (this.autoPlay) {
this.play()
}
} catch (error) {
this.error = error.message
this.loading = false
console.error("動畫載入失敗:", error)
}
},
loadCreateJS() {
return new Promise((resolve, reject) => {
if (window.createjs) {
resolve()
return
}
const script = document.createElement("script")
script.src = "https://code.createjs.com/1.0.0/createjs.min.js"
script.onload = resolve
script.onerror = () => reject(new Error("CreateJS 載入失敗"))
document.head.appendChild(script)
})
},
loadAnimationScript() {
return new Promise((resolve, reject) => {
if (window.AdobeAn && window.AdobeAn.getComposition) {
resolve()
return
}
const script = document.createElement("script")
script.src = "/animate/Winning/Winning.js"
script.onload = resolve
script.onerror = () => reject(new Error("動畫腳本載入失敗"))
document.head.appendChild(script)
})
},
setupAnimation() {
const canvas = this.$refs.canvas
const comp = window.AdobeAn.getComposition(
"4E951434865B49A08935ABFBCEBAFC1E"
)
if (!comp) {
throw new Error("找不到動畫組合")
}
const lib = comp.getLibrary()
// 修復資源路徑
const manifest = lib.properties.manifest.map((item) => ({
...item,
src: `/animate/Winning/${item.src}`,
}))
const loader = new window.createjs.LoadQueue(false)
loader.addEventListener("fileload", (evt) => {
const images = comp.getImages()
if (evt && evt.item.type === "image") {
images[evt.item.id] = evt.result
}
})
loader.addEventListener("complete", (evt) => {
const ss = comp.getSpriteSheet()
const queue = evt.target
const ssMetadata = lib.ssMetadata
// 建立 sprite sheets
for (let i = 0; i < ssMetadata.length; i++) {
ss[ssMetadata[i].name] = new window.createjs.SpriteSheet({
images: [queue.getResult(ssMetadata[i].name)],
frames: ssMetadata[i].frames,
})
}
// 建立動畫和舞台
this.animation = new lib.Winning()
this.stage = new lib.Stage(canvas)
// 設定動畫屬性
this.animation.loop = this.loop
this.stage.addChild(this.animation)
// 設定基本縮放和位置
const scale = 1 // 512x512 不需要額外縮放
this.stage.scaleX = this.stage.scaleY = scale
this.stage.x = (canvas.width - 512 * scale) / 2
this.stage.y = (canvas.height - 512 * scale) / 2
// 添加循環播放邏輯
this.setupAnimationLoop()
// 啟動 ticker
window.createjs.Ticker.framerate = 24
window.createjs.Ticker.addEventListener("tick", this.stage)
window.AdobeAn.compositionLoaded(lib.properties.id)
})
loader.addEventListener("error", (evt) => {
throw new Error(`資源載入失敗: ${evt.item?.src || "未知"}`)
})
loader.loadManifest(manifest)
},
setupAnimationLoop() {
if (!this.animation || !this.loop) return
// 使用 Ticker 監聽動畫狀態,實現循環播放
const checkAnimationLoop = () => {
if (this.animation && this.loop) {
// 檢查動畫是否播放完成 (需要根據實際幀數調整)
if (this.animation.currentFrame >= this.animation.totalFrames - 1) {
// 重新播放動畫
this.animation.gotoAndPlay(0)
}
}
}
// 每隔一定時間檢查動畫狀態
this.loopCheckInterval = setInterval(checkAnimationLoop, 100)
},
play() {
if (this.stage && this.animation) {
this.stage.tickEnabled = true
this.animation.gotoAndPlay(0)
// 確保循環播放邏輯正在運行
if (this.loop && !this.loopCheckInterval) {
this.setupAnimationLoop()
}
}
},
pause() {
if (this.stage) {
this.stage.tickEnabled = false
}
// 暫停時清理循環檢查
if (this.loopCheckInterval) {
clearInterval(this.loopCheckInterval)
this.loopCheckInterval = null
}
},
stop() {
if (this.stage && this.animation) {
this.stage.tickEnabled = false
this.animation.gotoAndStop(0)
}
// 停止時清理循環檢查
if (this.loopCheckInterval) {
clearInterval(this.loopCheckInterval)
this.loopCheckInterval = null
}
},
cleanup() {
// 清理循環檢查 interval
if (this.loopCheckInterval) {
clearInterval(this.loopCheckInterval)
this.loopCheckInterval = null
}
if (this.stage && window.createjs) {
window.createjs.Ticker.removeEventListener("tick", this.stage)
this.stage.removeAllChildren()
}
this.stage = null
this.animation = null
},
},
}
</script>
<style scoped>
.winning-animation {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 512px;
height: 512px;
}
canvas {
display: block;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #666;
font-size: 16px;
background: rgba(255, 255, 255, 0.8);
padding: 10px 20px;
border-radius: 4px;
}
.error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #c62828;
font-size: 14px;
text-align: center;
padding: 10px;
background: rgba(255, 255, 255, 0.9);
border-radius: 4px;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment