mirror of
https://github.com/tabler/tabler.git
synced 2026-10-12 05:06:55 +04:00
Fix Confetti end event after dispose, stalled frame without a 2D context and canvas width (#3228)
This commit is contained in:
1 parent
0fdf1b71bb
commit
ee3843e469
3 files changed
+81
-6
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@tabler/core": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixed `Confetti` firing `end` after `dispose()`, stalling without a 2D context and sizing its canvas with the scrollbar width.
|
||||||
+31
-6
@@ -59,6 +59,8 @@ type Emitter = {
|
|||||||
stopped: boolean
|
stopped: boolean
|
||||||
/** every piece has landed and `end` was fired */
|
/** every piece has landed and `end` was fired */
|
||||||
done: boolean
|
done: boolean
|
||||||
|
/** the instance was disposed: the pieces still land, but `end` is not fired */
|
||||||
|
detached: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,14 +136,21 @@ const stage = {
|
|||||||
frame: 0,
|
frame: 0,
|
||||||
lastTick: 0,
|
lastTick: 0,
|
||||||
|
|
||||||
add(emitter: Emitter): void {
|
add(emitter: Emitter): boolean {
|
||||||
this.emitters.push(emitter)
|
|
||||||
this._mount()
|
this._mount()
|
||||||
|
|
||||||
|
if (!this.context) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emitters.push(emitter)
|
||||||
|
|
||||||
if (!this.frame) {
|
if (!this.frame) {
|
||||||
this.lastTick = performance.now()
|
this.lastTick = performance.now()
|
||||||
this.frame = requestAnimationFrame((now) => this._tick(now))
|
this.frame = requestAnimationFrame((now) => this._tick(now))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
_mount(): void {
|
_mount(): void {
|
||||||
@@ -162,9 +171,14 @@ const stage = {
|
|||||||
display: 'block',
|
display: 'block',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const context = canvas.getContext('2d')
|
||||||
|
if (!context) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
document.body.append(canvas)
|
document.body.append(canvas)
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
this.context = canvas.getContext('2d')
|
this.context = context
|
||||||
this._resize()
|
this._resize()
|
||||||
window.addEventListener('resize', this._onResize)
|
window.addEventListener('resize', this._onResize)
|
||||||
},
|
},
|
||||||
@@ -192,7 +206,7 @@ const stage = {
|
|||||||
// The backing store is scaled for HiDPI screens; the CSS size above keeps
|
// The backing store is scaled for HiDPI screens; the CSS size above keeps
|
||||||
// the element at the viewport size, so the drawing stays sharp and 1:1.
|
// the element at the viewport size, so the drawing stays sharp and 1:1.
|
||||||
const dpr = Math.min(window.devicePixelRatio || 1, MAX_DPR)
|
const dpr = Math.min(window.devicePixelRatio || 1, MAX_DPR)
|
||||||
this.canvas.width = window.innerWidth * dpr
|
this.canvas.width = document.documentElement.clientWidth * dpr
|
||||||
this.canvas.height = window.innerHeight * dpr
|
this.canvas.height = window.innerHeight * dpr
|
||||||
this.context.setTransform(dpr, 0, 0, dpr, 0, 0)
|
this.context.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||||
},
|
},
|
||||||
@@ -313,7 +327,10 @@ const stage = {
|
|||||||
|
|
||||||
this.emitters.splice(i, 1)
|
this.emitters.splice(i, 1)
|
||||||
emitter.done = true
|
emitter.done = true
|
||||||
EventHandler.trigger(emitter.element, EVENT_END)
|
|
||||||
|
if (!emitter.detached) {
|
||||||
|
EventHandler.trigger(emitter.element, EVENT_END)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -373,9 +390,13 @@ class Confetti extends BaseComponent {
|
|||||||
emitted: 0,
|
emitted: 0,
|
||||||
stopped: false,
|
stopped: false,
|
||||||
done: false,
|
done: false,
|
||||||
|
detached: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
stage.add(this._emitter)
|
if (!stage.add(this._emitter)) {
|
||||||
|
this._emitter = null
|
||||||
|
EventHandler.trigger(this._element, EVENT_END)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(): void {
|
stop(): void {
|
||||||
@@ -387,6 +408,10 @@ class Confetti extends BaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
|
if (this._emitter) {
|
||||||
|
this._emitter.detached = true
|
||||||
|
}
|
||||||
|
|
||||||
this.stop()
|
this.stop()
|
||||||
super.dispose()
|
super.dispose()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,51 @@ describe('Confetti', () => {
|
|||||||
expect(canvas()).toBeNull()
|
expect(canvas()).toBeNull()
|
||||||
expect(end).toHaveBeenCalledTimes(1)
|
expect(end).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should end at once without a canvas when there is no 2D context', () => {
|
||||||
|
fixtureEl.innerHTML = '<button type="button"></button>'
|
||||||
|
reduceMotion(false)
|
||||||
|
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(null)
|
||||||
|
|
||||||
|
const end = vi.fn()
|
||||||
|
button().addEventListener('end.bs.confetti', end)
|
||||||
|
new Confetti(button(), QUICK).burst()
|
||||||
|
|
||||||
|
expect(canvas()).toBeNull()
|
||||||
|
expect(end).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should size the backing store to the viewport without the scrollbar', () => {
|
||||||
|
fixtureEl.innerHTML = '<button type="button"></button>'
|
||||||
|
reduceMotion(false)
|
||||||
|
document.documentElement.style.overflowY = 'scroll'
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Confetti(button(), QUICK).burst()
|
||||||
|
|
||||||
|
const dpr = Math.min(window.devicePixelRatio || 1, 2)
|
||||||
|
expect(canvas()!.width).toBe(document.documentElement.clientWidth * dpr)
|
||||||
|
} finally {
|
||||||
|
document.documentElement.style.overflowY = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('dispose', () => {
|
||||||
|
it('should not fire end once the instance is disposed', async () => {
|
||||||
|
fixtureEl.innerHTML = '<button type="button"></button>'
|
||||||
|
reduceMotion(false)
|
||||||
|
|
||||||
|
const end = vi.fn()
|
||||||
|
button().addEventListener('end.bs.confetti', end)
|
||||||
|
|
||||||
|
const instance = new Confetti(button(), QUICK)
|
||||||
|
instance.burst()
|
||||||
|
instance.dispose()
|
||||||
|
|
||||||
|
await vi.waitFor(() => expect(canvas()).toBeNull(), { timeout: 3000 })
|
||||||
|
expect(end).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('stop', () => {
|
describe('stop', () => {
|
||||||
|
|||||||
Reference in new issue
Block a user