perf(sticky): remove creation delay + add dirty-checking to StickySprite
- Replace 50ms setTimeout with queueMicrotask for editor open - Skip updateFromData() entirely when no inputs changed - Cache background redraw inputs, skip Graphics clear+redraw when shape unchanged - Separate text-only, style-only, and bg-only update paths
This commit is contained in:
@@ -10,6 +10,11 @@
|
|||||||
* - width is fixed (from data.w)
|
* - width is fixed (from data.w)
|
||||||
* - height auto-grows to fit wrapped text + padding
|
* - height auto-grows to fit wrapped text + padding
|
||||||
* - parent SceneItem.data.h is updated after layout
|
* - parent SceneItem.data.h is updated after layout
|
||||||
|
*
|
||||||
|
* Performance:
|
||||||
|
* - updateFromData() skips work when inputs haven't changed
|
||||||
|
* - _drawBg() is cached and only redraws when dimensions/color change
|
||||||
|
* - showText() never triggers layout or redraw
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Container, Graphics, Text, TextStyle } from 'pixi.js';
|
import { Container, Graphics, Text, TextStyle } from 'pixi.js';
|
||||||
@@ -32,6 +37,19 @@ export class StickySprite extends Container {
|
|||||||
private _cornerRadius = DEFAULT_CORNER_RADIUS;
|
private _cornerRadius = DEFAULT_CORNER_RADIUS;
|
||||||
private _bgColor = '#ffd43b';
|
private _bgColor = '#ffd43b';
|
||||||
|
|
||||||
|
// Dirty-check cache: last known inputs to avoid redundant work
|
||||||
|
private _lastText = '';
|
||||||
|
private _lastFontSize = 14;
|
||||||
|
private _lastFontFamily = 'Inter, system-ui, sans-serif';
|
||||||
|
private _lastTextColor = '#1a1a1a';
|
||||||
|
private _lastFill = '#ffd43b';
|
||||||
|
private _lastW = 200;
|
||||||
|
private _lastPadding = DEFAULT_PADDING;
|
||||||
|
private _lastCornerRadius = DEFAULT_CORNER_RADIUS;
|
||||||
|
|
||||||
|
// Background redraw cache: skip Graphics.clear()+redraw when shape is unchanged
|
||||||
|
private _bgCacheKey = '';
|
||||||
|
|
||||||
/** After layout, the computed card height. Parent should sync to data.h. */
|
/** After layout, the computed card height. Parent should sync to data.h. */
|
||||||
get computedHeight(): number {
|
get computedHeight(): number {
|
||||||
return this._cardH;
|
return this._cardH;
|
||||||
@@ -55,57 +73,122 @@ export class StickySprite extends Container {
|
|||||||
this.addChild(this._bg);
|
this.addChild(this._bg);
|
||||||
|
|
||||||
// Text style
|
// Text style
|
||||||
|
const fontSize = data.fontSize || 14;
|
||||||
this._style = new TextStyle({
|
this._style = new TextStyle({
|
||||||
fontSize: data.fontSize || 14,
|
fontSize,
|
||||||
fontFamily: data.fontFamily || 'Inter, system-ui, sans-serif',
|
fontFamily: data.fontFamily || 'Inter, system-ui, sans-serif',
|
||||||
fill: data.textColor || '#1a1a1a',
|
fill: data.textColor || '#1a1a1a',
|
||||||
wordWrap: true,
|
wordWrap: true,
|
||||||
wordWrapWidth: Math.max(data.w - this._padding * 2, 1),
|
wordWrapWidth: Math.max(data.w - this._padding * 2, 1),
|
||||||
lineHeight: (data.fontSize || 14) * DEFAULT_LINE_HEIGHT,
|
lineHeight: fontSize * DEFAULT_LINE_HEIGHT,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._text = new Text({ text: data.text || '', style: this._style });
|
this._text = new Text({ text: data.text || '', style: this._style });
|
||||||
this._text.position.set(this._padding, this._padding);
|
this._text.position.set(this._padding, this._padding);
|
||||||
this.addChild(this._text);
|
this.addChild(this._text);
|
||||||
|
|
||||||
|
// Snapshot initial state
|
||||||
|
this._lastText = data.text || '';
|
||||||
|
this._lastFontSize = fontSize;
|
||||||
|
this._lastFontFamily = data.fontFamily || 'Inter, system-ui, sans-serif';
|
||||||
|
this._lastTextColor = data.textColor || '#1a1a1a';
|
||||||
|
this._lastFill = this._bgColor;
|
||||||
|
this._lastW = data.w;
|
||||||
|
this._lastPadding = this._padding;
|
||||||
|
this._lastCornerRadius = this._cornerRadius;
|
||||||
|
|
||||||
this._layout();
|
this._layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update from StickyObject data. Called by SceneManager._updateItem().
|
* Update from StickyObject data. Called by SceneManager._updateItem().
|
||||||
|
* Skips text/style/layout work when nothing actually changed.
|
||||||
*/
|
*/
|
||||||
updateFromData(data: StickyObject): void {
|
updateFromData(data: StickyObject): void {
|
||||||
this._padding = data.padding ?? DEFAULT_PADDING;
|
const padding = data.padding ?? DEFAULT_PADDING;
|
||||||
this._cornerRadius = data.cornerRadius ?? DEFAULT_CORNER_RADIUS;
|
const cornerRadius = data.cornerRadius ?? DEFAULT_CORNER_RADIUS;
|
||||||
this._bgColor = data.fill || '#ffd43b';
|
const bgColor = data.fill || '#ffd43b';
|
||||||
this._cardW = data.w;
|
const w = data.w;
|
||||||
|
const text = data.text || '';
|
||||||
|
const fontSize = data.fontSize || 14;
|
||||||
|
const fontFamily = data.fontFamily || 'Inter, system-ui, sans-serif';
|
||||||
|
const textColor = data.textColor || '#1a1a1a';
|
||||||
|
|
||||||
// Update text content and style
|
// Check if anything actually changed
|
||||||
this._text.text = data.text || '';
|
const textChanged = text !== this._lastText;
|
||||||
this._style.fontSize = data.fontSize || 14;
|
const styleChanged =
|
||||||
this._style.fontFamily = data.fontFamily || 'Inter, system-ui, sans-serif';
|
fontSize !== this._lastFontSize ||
|
||||||
this._style.fill = data.textColor || '#1a1a1a';
|
fontFamily !== this._lastFontFamily ||
|
||||||
this._style.wordWrapWidth = Math.max(data.w - this._padding * 2, 1);
|
textColor !== this._lastTextColor;
|
||||||
this._style.lineHeight = (data.fontSize || 14) * DEFAULT_LINE_HEIGHT;
|
const layoutChanged =
|
||||||
this._text.style = this._style;
|
w !== this._lastW ||
|
||||||
|
padding !== this._lastPadding;
|
||||||
|
const bgChanged =
|
||||||
|
bgColor !== this._lastFill ||
|
||||||
|
cornerRadius !== this._lastCornerRadius;
|
||||||
|
|
||||||
this._text.position.set(this._padding, this._padding);
|
// Nothing changed — skip all work
|
||||||
this._layout();
|
if (!textChanged && !styleChanged && !layoutChanged && !bgChanged) return;
|
||||||
|
|
||||||
|
// Update cached state
|
||||||
|
this._padding = padding;
|
||||||
|
this._cornerRadius = cornerRadius;
|
||||||
|
this._bgColor = bgColor;
|
||||||
|
this._cardW = w;
|
||||||
|
this._lastText = text;
|
||||||
|
this._lastFontSize = fontSize;
|
||||||
|
this._lastFontFamily = fontFamily;
|
||||||
|
this._lastTextColor = textColor;
|
||||||
|
this._lastFill = bgColor;
|
||||||
|
this._lastW = w;
|
||||||
|
this._lastPadding = padding;
|
||||||
|
this._lastCornerRadius = cornerRadius;
|
||||||
|
|
||||||
|
// Only update text if content changed
|
||||||
|
if (textChanged) {
|
||||||
|
this._text.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update style if style/layout props changed
|
||||||
|
if (styleChanged || layoutChanged) {
|
||||||
|
this._style.fontSize = fontSize;
|
||||||
|
this._style.fontFamily = fontFamily;
|
||||||
|
this._style.fill = textColor;
|
||||||
|
this._style.wordWrapWidth = Math.max(w - padding * 2, 1);
|
||||||
|
this._style.lineHeight = fontSize * DEFAULT_LINE_HEIGHT;
|
||||||
|
this._text.style = this._style;
|
||||||
|
this._text.position.set(padding, padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relayout if text, style, or dimensions changed (need remeasure)
|
||||||
|
if (textChanged || styleChanged || layoutChanged) {
|
||||||
|
this._layout();
|
||||||
|
} else if (bgChanged) {
|
||||||
|
// Only bg color/radius changed — redraw background without remeasuring text
|
||||||
|
this._drawBg();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recompute layout: measure text height, resize card, redraw background.
|
* Recompute layout: measure text height, resize card, redraw background.
|
||||||
* Returns the new card height so the caller can sync data.h.
|
|
||||||
*/
|
*/
|
||||||
private _layout(): void {
|
private _layout(): void {
|
||||||
// Measure the PixiJS text after style update
|
|
||||||
const textBounds = this._text.getLocalBounds();
|
const textBounds = this._text.getLocalBounds();
|
||||||
const textH = textBounds.height;
|
const textH = textBounds.height;
|
||||||
|
|
||||||
// Card height: text + top/bottom padding, minimum height
|
|
||||||
this._cardH = Math.max(textH + this._padding * 2, MIN_HEIGHT);
|
this._cardH = Math.max(textH + this._padding * 2, MIN_HEIGHT);
|
||||||
|
this._drawBg();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redraw the rounded-rect background. Skips if shape is unchanged
|
||||||
|
* (same dimensions, color, radius).
|
||||||
|
*/
|
||||||
|
private _drawBg(): void {
|
||||||
|
const cacheKey = `${this._cardW}|${this._cardH}|${this._bgColor}|${this._cornerRadius}`;
|
||||||
|
if (cacheKey === this._bgCacheKey) return;
|
||||||
|
this._bgCacheKey = cacheKey;
|
||||||
|
|
||||||
// Redraw background
|
|
||||||
this._bg.clear();
|
this._bg.clear();
|
||||||
const color = parseInt(this._bgColor.replace('#', ''), 16) || 0xffd43b;
|
const color = parseInt(this._bgColor.replace('#', ''), 16) || 0xffd43b;
|
||||||
this._bg.roundRect(0, 0, this._cardW, this._cardH, this._cornerRadius);
|
this._bg.roundRect(0, 0, this._cardW, this._cardH, this._cornerRadius);
|
||||||
|
|||||||
@@ -348,7 +348,8 @@ export function activateTool(
|
|||||||
const canvasEl = container.querySelector('canvas');
|
const canvasEl = container.querySelector('canvas');
|
||||||
const domContainer = canvasEl?.parentElement ?? container;
|
const domContainer = canvasEl?.parentElement ?? container;
|
||||||
|
|
||||||
setTimeout(() => {
|
// Use microtask instead of 50ms delay — PixiJS only needs one tick
|
||||||
|
queueMicrotask(() => {
|
||||||
ctx.textEditor!.startEditing(item, viewport, domContainer, () => {
|
ctx.textEditor!.startEditing(item, viewport, domContainer, () => {
|
||||||
// Cleanup rule — fires on BOTH save and cancel (stopEditing always calls _onChange):
|
// Cleanup rule — fires on BOTH save and cancel (stopEditing always calls _onChange):
|
||||||
// - new sticky + cancel/blur with empty text => remove (no blank notes left behind)
|
// - new sticky + cancel/blur with empty text => remove (no blank notes left behind)
|
||||||
@@ -362,7 +363,7 @@ export function activateTool(
|
|||||||
ctx.onChange();
|
ctx.onChange();
|
||||||
});
|
});
|
||||||
ctx.textEditor!.clearText();
|
ctx.textEditor!.clearText();
|
||||||
}, 50);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.switchToSelect?.();
|
ctx.switchToSelect?.();
|
||||||
|
|||||||
Reference in New Issue
Block a user