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
|
||||
/** every piece has landed and `end` was fired */
|
||||
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,
|
||||
lastTick: 0,
|
||||
|
||||
add(emitter: Emitter): void {
|
||||
this.emitters.push(emitter)
|
||||
add(emitter: Emitter): boolean {
|
||||
this._mount()
|
||||
|
||||
if (!this.context) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.emitters.push(emitter)
|
||||
|
||||
if (!this.frame) {
|
||||
this.lastTick = performance.now()
|
||||
this.frame = requestAnimationFrame((now) => this._tick(now))
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
_mount(): void {
|
||||
@@ -162,9 +171,14 @@ const stage = {
|
||||
display: 'block',
|
||||
})
|
||||
|
||||
const context = canvas.getContext('2d')
|
||||
if (!context) {
|
||||
return
|
||||
}
|
||||
|
||||
document.body.append(canvas)
|
||||
this.canvas = canvas
|
||||
this.context = canvas.getContext('2d')
|
||||
this.context = context
|
||||
this._resize()
|
||||
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 element at the viewport size, so the drawing stays sharp and 1:1.
|
||||
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.context.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
},
|
||||
@@ -313,7 +327,10 @@ const stage = {
|
||||
|
||||
this.emitters.splice(i, 1)
|
||||
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,
|
||||
stopped: 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 {
|
||||
@@ -387,6 +408,10 @@ class Confetti extends BaseComponent {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._emitter) {
|
||||
this._emitter.detached = true
|
||||
}
|
||||
|
||||
this.stop()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
@@ -100,6 +100,51 @@ describe('Confetti', () => {
|
||||
expect(canvas()).toBeNull()
|
||||
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', () => {
|
||||
|
||||
Reference in new issue
Block a user