Skip to content

Commit 173c2ff

Browse files
committed
feat: Optimize draw text feature
1 parent 3b4291f commit 173c2ff

File tree

4 files changed

+76
-36
lines changed

4 files changed

+76
-36
lines changed

apps/collabydraw/canvas-engine/CanvasEngine.ts

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
FillStyle,
3+
FONT_SIZE_MAP,
34
FontFamily,
45
FontSize,
56
FontStyle,
@@ -495,6 +496,7 @@ export class CanvasEngine {
495496
this.drawText(
496497
shape.x,
497498
shape.y,
499+
shape.width,
498500
shape.text,
499501
shape.strokeFill,
500502
shape.fontStyle,
@@ -972,7 +974,6 @@ export class CanvasEngine {
972974
Object.assign(textarea.style, {
973975
position: "absolute",
974976
display: "inline-block",
975-
minHeight: "1em",
976977
backfaceVisibility: "hidden",
977978
margin: "0",
978979
padding: "0",
@@ -984,11 +985,13 @@ export class CanvasEngine {
984985
overflowWrap: "break-word",
985986
boxSizing: "content-box",
986987
wordBreak: "normal",
987-
whiteSpace: "pre",
988+
whiteSpace: "pre-wrap",
988989
transform: `translate(${x * this.scale + this.panX}px, ${y * this.scale + this.panY}px)`,
989990
verticalAlign: "top",
990991
opacity: "1",
991992
filter: "var(--theme-filter)",
993+
width: "auto",
994+
minHeight: "2rem",
992995
});
993996
// console.log(
994997
// `this.fontSize= ${this.fontSize}, this.fontFamily= ${this.fontFamily}, this.textAlign=${this.textAlign}, this.scale=${this.scale}`
@@ -1001,7 +1004,7 @@ export class CanvasEngine {
10011004
textarea.style.color = this.strokeFill;
10021005
const fontString = `${calFont}px/1.2 ${this.fontFamily === "normal" ? "Arial" : this.fontFamily === "hand-drawn" ? "Excalifont, Xiaolai" : "Assistant"}`;
10031006
textarea.style.font = fontString;
1004-
textarea.style.zIndex = "1000";
1007+
textarea.style.zIndex = "1";
10051008

10061009
const collabydrawContainer = document.querySelector(
10071010
".collabydraw-textEditorContainer"
@@ -1015,29 +1018,64 @@ export class CanvasEngine {
10151018
return;
10161019
}
10171020

1018-
// Track if there are pending changes
10191021
let hasUnsavedChanges = false;
1020-
let idleTimer: number | null = null;
10211022

1022-
// Functions to handle saving
1023+
let span: HTMLSpanElement | null = null;
1024+
1025+
const resizeTextarea = () => {
1026+
if (span) {
1027+
document.body.removeChild(span);
1028+
}
1029+
1030+
span = document.createElement("span");
1031+
1032+
Object.assign(span.style, {
1033+
visibility: "hidden",
1034+
position: "absolute",
1035+
whiteSpace: "pre-wrap",
1036+
wordBreak: "break-word",
1037+
font: textarea.style.font,
1038+
width: "auto",
1039+
height: "auto",
1040+
});
1041+
1042+
span.textContent = textarea.value || " ";
1043+
document.body.appendChild(span);
1044+
1045+
requestAnimationFrame(() => {
1046+
textarea.style.width = `${Math.max(span!.offsetWidth + 10, 50)}px`;
1047+
textarea.style.height = `${Math.max(span!.offsetHeight, 20)}px`;
1048+
});
1049+
1050+
console.log("span.offsetWidth= ", span.offsetWidth);
1051+
console.log("textarea.style.width= ", textarea.style.width);
1052+
};
1053+
1054+
textarea.addEventListener("input", () => {
1055+
hasUnsavedChanges = true;
1056+
resizeTextarea();
1057+
});
1058+
textarea.addEventListener("keydown", (e) => {
1059+
if (e.key === "Enter") {
1060+
hasUnsavedChanges = true;
1061+
resizeTextarea();
1062+
}
1063+
});
1064+
10231065
const save = () => {
10241066
const text = textarea.value.trim();
10251067
if (!text) {
10261068
textarea.remove();
1069+
document.body.removeChild(span!);
10271070
return;
10281071
}
10291072

1030-
const rect = textarea.getBoundingClientRect();
1031-
const width = rect.width / this.scale;
1032-
const height = rect.height / this.scale;
1033-
10341073
const newShape: Shape = {
10351074
id: uuidv4(),
10361075
type: "text",
10371076
x: x,
10381077
y: y,
1039-
width,
1040-
height,
1078+
width: span!.offsetWidth,
10411079
text,
10421080
fontSize: this.fontSize,
10431081
fontFamily: this.fontFamily,
@@ -1066,53 +1104,34 @@ export class CanvasEngine {
10661104

10671105
if (collabydrawContainer?.contains(textarea)) {
10681106
collabydrawContainer.removeChild(textarea);
1107+
document.body.removeChild(span!);
10691108
}
10701109

1071-
this.clearCanvas(); // Make sure text appears on canvas immediately
1110+
this.clearCanvas();
10721111
hasUnsavedChanges = false;
10731112
};
10741113

1075-
// Auto-save after user stops typing for 1.5 seconds
1076-
const resetIdleTimer = () => {
1077-
if (idleTimer) {
1078-
clearTimeout(idleTimer);
1079-
}
1080-
1081-
if (hasUnsavedChanges) {
1082-
idleTimer = window.setTimeout(() => {
1083-
save();
1084-
}, 1500);
1085-
}
1086-
};
1087-
1088-
// Track input changes
10891114
textarea.addEventListener("input", () => {
10901115
hasUnsavedChanges = true;
1091-
resetIdleTimer();
10921116
});
10931117

1094-
// Save when user presses Enter+Ctrl/Cmd
10951118
textarea.addEventListener("keydown", (e) => {
10961119
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
10971120
e.preventDefault();
10981121
save();
10991122
}
1100-
resetIdleTimer();
11011123
});
11021124

1103-
// Save when user clicks outside
11041125
const handleClickOutside = (e: MouseEvent) => {
11051126
if (!textarea.contains(e.target as Node)) {
11061127
save();
11071128
}
11081129
};
11091130

1110-
// Delay adding the click listener to prevent immediate trigger
11111131
setTimeout(() => {
11121132
document.addEventListener("mousedown", handleClickOutside);
11131133
}, 100);
11141134

1115-
// Clean up event listener when textarea loses focus
11161135
textarea.addEventListener("blur", () => {
11171136
document.removeEventListener("mousedown", handleClickOutside);
11181137
if (hasUnsavedChanges) {
@@ -1197,6 +1216,22 @@ export class CanvasEngine {
11971216
(point) => Math.hypot(point.x - x, point.y - y) <= tolerance
11981217
);
11991218
}
1219+
1220+
case "text": {
1221+
const startX = shape.x;
1222+
const endX = shape.x + shape.width;
1223+
const startY = shape.y;
1224+
const textHeight = FONT_SIZE_MAP[shape.fontSize];
1225+
const endY = shape.y + textHeight;
1226+
1227+
return (
1228+
x >= startX - tolerance &&
1229+
x <= endX + tolerance &&
1230+
y >= startY - tolerance &&
1231+
y <= endY + tolerance
1232+
);
1233+
}
1234+
12001235
default:
12011236
return false;
12021237
}
@@ -1617,14 +1652,14 @@ export class CanvasEngine {
16171652
drawText(
16181653
x: number,
16191654
y: number,
1655+
width: number,
16201656
text: string,
16211657
fillStyle: string,
16221658
fontStyle: FontStyle,
16231659
fontFamily: FontFamily,
16241660
fontSize: FontSize,
16251661
textAlign: TextAlign
16261662
) {
1627-
const width = 200;
16281663
const calFontSize = getFontSize(fontSize, this.scale);
16291664
const lineHeight = getLineHeight(calFontSize);
16301665

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function CollabydrawTextEditorContainer() {
2+
return (
3+
<div className="collabydraw-textEditorContainer"></div>
4+
)
5+
}

apps/collabydraw/components/canvas/CanvasRoot.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { StyleConfigurator } from "../StyleConfigurator";
1818
import ToolSelector from "../ToolSelector";
1919
import CollaborationToolbar from "../CollaborationToolbar";
2020
import ZoomControl from "../ZoomControl";
21+
import CollabydrawTextEditorContainer from "../Collabydraw-TextEditorContainer";
2122

2223
export default function CanvasRoot() {
2324
const { data: session, status } = useSession();
@@ -347,7 +348,7 @@ export default function CanvasRoot() {
347348

348349
)}
349350

350-
<div className="collabydraw-textEditorContainer"></div>
351+
<CollabydrawTextEditorContainer />
351352

352353
{!matches && (
353354
<MobileCommandBar

apps/collabydraw/types/canvas.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export type Shape =
209209
x: number;
210210
y: number;
211211
width: number;
212-
height: number;
213212
text: string;
214213
fontSize: FontSize;
215214
fontFamily: FontFamily;

0 commit comments

Comments
 (0)