diff --git a/dist/birb.js b/dist/birb.js index 113f6e4..e5ddd7a 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -454,6 +454,9 @@ * @param {number} canvasPixelSize */ draw(ctx, direction, canvasPixelSize, species) { + // Clear the canvas before drawing the new frame + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + const pixels = this.getPixels(species?.tags[0]); for (let y = 0; y < pixels.length; y++) { const row = pixels[y]; @@ -474,12 +477,49 @@ this.frames = frames; this.durations = durations; this.loop = loop; + this.lastFrameIndex = -1; + this.lastDirection = null; + this.lastTimeStart = null; } getAnimationDuration() { return this.durations.reduce((a, b) => a + b, 0); } + /** + * Get the current frame index based on elapsed time + * @param {number} time The elapsed time since animation start + * @returns {number} The index of the current frame + */ + getCurrentFrameIndex(time) { + let totalDuration = 0; + for (let i = 0; i < this.durations.length; i++) { + totalDuration += this.durations[i]; + if (time < totalDuration) { + return i; + } + } + return this.frames.length - 1; + } + + /** + * Clear the cached frame state + */ + #clearCache() { + this.lastFrameIndex = -1; + this.lastDirection = null; + } + + /** + * Check if the frame needs to be redrawn + * @param {number} frameIndex The current frame index + * @param {number} direction The current direction + * @returns {boolean} Whether the frame needs to be redrawn + */ + #shouldRedraw(frameIndex, direction) { + return frameIndex !== this.lastFrameIndex || direction !== this.lastDirection; + } + /** * @param {CanvasRenderingContext2D} ctx * @param {number} direction @@ -489,22 +529,29 @@ * @returns {boolean} Whether the animation is complete */ draw(ctx, direction, timeStart, canvasPixelSize, species) { + // Reset cache if animation was restarted + if (this.lastTimeStart !== timeStart) { + this.#clearCache(); + this.lastTimeStart = timeStart; + } + let time = Date.now() - timeStart; const duration = this.getAnimationDuration(); + if (this.loop) { time %= duration; } - let totalDuration = 0; - for (let i = 0; i < this.durations.length; i++) { - totalDuration += this.durations[i]; - if (time < totalDuration) { - this.frames[i].draw(ctx, direction, canvasPixelSize, species); - return false; - } + + const currentFrameIndex = this.getCurrentFrameIndex(time); + + if (this.#shouldRedraw(currentFrameIndex, direction)) { + this.frames[currentFrameIndex].draw(ctx, direction, canvasPixelSize, species); + this.lastFrameIndex = currentFrameIndex; + this.lastDirection = direction; } - // Draw the last frame if the animation is complete - this.frames[this.frames.length - 1].draw(ctx, direction, canvasPixelSize, species); - return true; + + // Return whether animation is complete (for non-looping animations) + return !this.loop && time >= duration; } } @@ -629,7 +676,6 @@ * @returns {boolean} Whether the animation has completed (for non-looping animations) */ draw(species) { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const anim = this.animations[this.currentAnimation]; return anim.draw(this.ctx, this.direction, this.animStart, this.canvasPixelSize, species); } @@ -1606,7 +1652,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.10.28.47", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.47"); }, false), + new MenuItem("2025.10.28.80", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.80"); }, false), ]; const styleElement = document.createElement("style"); diff --git a/dist/birb.user.js b/dist/birb.user.js index 2686fa5..0331853 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Pocket Bird // @namespace https://idreesinc.com -// @version 2025.10.28.47 +// @version 2025.10.28.80 // @description birb // @author Idrees // @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/birb.user.js @@ -468,6 +468,9 @@ * @param {number} canvasPixelSize */ draw(ctx, direction, canvasPixelSize, species) { + // Clear the canvas before drawing the new frame + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + const pixels = this.getPixels(species?.tags[0]); for (let y = 0; y < pixels.length; y++) { const row = pixels[y]; @@ -488,12 +491,49 @@ this.frames = frames; this.durations = durations; this.loop = loop; + this.lastFrameIndex = -1; + this.lastDirection = null; + this.lastTimeStart = null; } getAnimationDuration() { return this.durations.reduce((a, b) => a + b, 0); } + /** + * Get the current frame index based on elapsed time + * @param {number} time The elapsed time since animation start + * @returns {number} The index of the current frame + */ + getCurrentFrameIndex(time) { + let totalDuration = 0; + for (let i = 0; i < this.durations.length; i++) { + totalDuration += this.durations[i]; + if (time < totalDuration) { + return i; + } + } + return this.frames.length - 1; + } + + /** + * Clear the cached frame state + */ + #clearCache() { + this.lastFrameIndex = -1; + this.lastDirection = null; + } + + /** + * Check if the frame needs to be redrawn + * @param {number} frameIndex The current frame index + * @param {number} direction The current direction + * @returns {boolean} Whether the frame needs to be redrawn + */ + #shouldRedraw(frameIndex, direction) { + return frameIndex !== this.lastFrameIndex || direction !== this.lastDirection; + } + /** * @param {CanvasRenderingContext2D} ctx * @param {number} direction @@ -503,22 +543,29 @@ * @returns {boolean} Whether the animation is complete */ draw(ctx, direction, timeStart, canvasPixelSize, species) { + // Reset cache if animation was restarted + if (this.lastTimeStart !== timeStart) { + this.#clearCache(); + this.lastTimeStart = timeStart; + } + let time = Date.now() - timeStart; const duration = this.getAnimationDuration(); + if (this.loop) { time %= duration; } - let totalDuration = 0; - for (let i = 0; i < this.durations.length; i++) { - totalDuration += this.durations[i]; - if (time < totalDuration) { - this.frames[i].draw(ctx, direction, canvasPixelSize, species); - return false; - } + + const currentFrameIndex = this.getCurrentFrameIndex(time); + + if (this.#shouldRedraw(currentFrameIndex, direction)) { + this.frames[currentFrameIndex].draw(ctx, direction, canvasPixelSize, species); + this.lastFrameIndex = currentFrameIndex; + this.lastDirection = direction; } - // Draw the last frame if the animation is complete - this.frames[this.frames.length - 1].draw(ctx, direction, canvasPixelSize, species); - return true; + + // Return whether animation is complete (for non-looping animations) + return !this.loop && time >= duration; } } @@ -643,7 +690,6 @@ * @returns {boolean} Whether the animation has completed (for non-looping animations) */ draw(species) { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const anim = this.animations[this.currentAnimation]; return anim.draw(this.ctx, this.direction, this.animStart, this.canvasPixelSize, species); } @@ -1620,7 +1666,7 @@ insertModal(`${birdBirb()} Mode`, message); }), new Separator(), - new MenuItem("2025.10.28.47", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.47"); }, false), + new MenuItem("2025.10.28.80", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.10.28.80"); }, false), ]; const styleElement = document.createElement("style"); diff --git a/manifest.json b/manifest.json index 4543ce0..2725ff6 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Pocket Bird", "description": "It's a bird, in your browser. What more could you want?", - "version": "2025.10.28.47", + "version": "2025.10.28.80", "homepage_url": "https://idreesinc.com", "icons": { "48": "images/icons/transparent/48x48x1.png", diff --git a/src/Frame.js b/src/Frame.js index 58d08f1..f8c6ece 100644 --- a/src/Frame.js +++ b/src/Frame.js @@ -58,6 +58,9 @@ class Frame { * @param {number} canvasPixelSize */ draw(ctx, direction, canvasPixelSize, species) { + // Clear the canvas before drawing the new frame + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + const pixels = this.getPixels(species?.tags[0]); for (let y = 0; y < pixels.length; y++) { const row = pixels[y]; diff --git a/src/anim.js b/src/anim.js index 7294edb..2663bdb 100644 --- a/src/anim.js +++ b/src/anim.js @@ -11,12 +11,49 @@ class Anim { this.frames = frames; this.durations = durations; this.loop = loop; + this.lastFrameIndex = -1; + this.lastDirection = null; + this.lastTimeStart = null; } getAnimationDuration() { return this.durations.reduce((a, b) => a + b, 0); } + /** + * Get the current frame index based on elapsed time + * @param {number} time The elapsed time since animation start + * @returns {number} The index of the current frame + */ + getCurrentFrameIndex(time) { + let totalDuration = 0; + for (let i = 0; i < this.durations.length; i++) { + totalDuration += this.durations[i]; + if (time < totalDuration) { + return i; + } + } + return this.frames.length - 1; + } + + /** + * Clear the cached frame state + */ + #clearCache() { + this.lastFrameIndex = -1; + this.lastDirection = null; + } + + /** + * Check if the frame needs to be redrawn + * @param {number} frameIndex The current frame index + * @param {number} direction The current direction + * @returns {boolean} Whether the frame needs to be redrawn + */ + #shouldRedraw(frameIndex, direction) { + return frameIndex !== this.lastFrameIndex || direction !== this.lastDirection; + } + /** * @param {CanvasRenderingContext2D} ctx * @param {number} direction @@ -26,22 +63,29 @@ class Anim { * @returns {boolean} Whether the animation is complete */ draw(ctx, direction, timeStart, canvasPixelSize, species) { + // Reset cache if animation was restarted + if (this.lastTimeStart !== timeStart) { + this.#clearCache(); + this.lastTimeStart = timeStart; + } + let time = Date.now() - timeStart; const duration = this.getAnimationDuration(); + if (this.loop) { time %= duration; } - let totalDuration = 0; - for (let i = 0; i < this.durations.length; i++) { - totalDuration += this.durations[i]; - if (time < totalDuration) { - this.frames[i].draw(ctx, direction, canvasPixelSize, species); - return false; - } + + const currentFrameIndex = this.getCurrentFrameIndex(time); + + if (this.#shouldRedraw(currentFrameIndex, direction)) { + this.frames[currentFrameIndex].draw(ctx, direction, canvasPixelSize, species); + this.lastFrameIndex = currentFrameIndex; + this.lastDirection = direction; } - // Draw the last frame if the animation is complete - this.frames[this.frames.length - 1].draw(ctx, direction, canvasPixelSize, species); - return true; + + // Return whether animation is complete (for non-looping animations) + return !this.loop && time >= duration; } } diff --git a/src/birb.js b/src/birb.js index 96c8adf..afc86f8 100644 --- a/src/birb.js +++ b/src/birb.js @@ -125,7 +125,6 @@ export class Birb { * @returns {boolean} Whether the animation has completed (for non-looping animations) */ draw(species) { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); const anim = this.animations[this.currentAnimation]; return anim.draw(this.ctx, this.direction, this.animStart, this.canvasPixelSize, species); } diff --git a/src/frame.js b/src/frame.js index 58d08f1..f8c6ece 100644 --- a/src/frame.js +++ b/src/frame.js @@ -58,6 +58,9 @@ class Frame { * @param {number} canvasPixelSize */ draw(ctx, direction, canvasPixelSize, species) { + // Clear the canvas before drawing the new frame + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + const pixels = this.getPixels(species?.tags[0]); for (let y = 0; y < pixels.length; y++) { const row = pixels[y];