perf(pdf-viewer): limit drag preview re-render to its page
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 45s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 45s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
แก้ "ลากย้ายเส้น/กล่องข้อความ/ตรายาง/pin ไม่ smooth เป็น step"
(คำขอผู้ใช้ 2026-09-29):
- สาเหตุ: preview ของการลาก (onPreview → setPreviewData) เป็น state
ระดับ root ของ PDFViewer — ทุก pointermove ระหว่างลาก re-render ทั้ง
ต้นไม้ (toolbar + sidebar + ทุกหน้าที่ mounted ตาม virtualization)
ทั้งที่ผู้ใช้ลากอยู่หน้าเดียว และค่าถูกอ่านแค่จุดเดียวคือ substitution
ของหน้าที่ลาก — เทียบกับการวาดเส้นที่ smooth เพราะ live state อยู่ใน
InkLayer เอง + rAF throttle (FIX-8) อยู่แล้ว
- แก้: ย้าย preview state ลงเป็น state ท้องถิ่นของ PdfPage (substitution
ย้ายตามมาอยู่ก่อน derive layers ทั้งหมด) — ระหว่างลาก re-render เฉพาะ
หน้าที่ลาก; root สั่งเคลียร์ผ่าน counter previewClear (useReducer)
แทน setter ตรง ครบทุกจุดเดิม 9 จุด (ปิด popup/Escape/auto-select/
delete/ก่อนคิว update/deselect/goToAnnotation/เลือกรายการ)
- onPreview ของ AnnotationSelectionLayer throttle ด้วย rAF ≤1 ครั้ง/
เฟรม (pattern FIX-8 ของ InkLayer): ทุก pointermove ยังคำนวณ + เก็บ
latestRef เสมอ (ค่า commit ตอน pointerup เป็นค่าล่าสุดสุดเสมอ) เฉพาะ
การ render ที่จัดคิวตามเฟรม + cancel ครบทุกทางออกของ drag
(pointerup/pointercancel/unmount)
- แก้ตาม code review 2026-09-29: preview เก็บเป็น {id, data} เทียบ id
เจ้าของตอน substitute แทน selectedId — กันจอสัมผัสเปลี่ยน selection
กลาง drag (นิ้วหนึ่งลากค้าง อีกนิ้วเลือกอื่น) แล้ว preview ของรายการเก่า
ไปทดแทนรายการใหม่หนึ่งเฟรม (การเคลียร์จาก root เป็น passive effect
หลัง paint)
- sync สถาปัตยกรรมใหม่ใน .claude/rules/pdf-viewer.md (หัวข้อกรอบ
selection F1)
ผ่าน code-reviewer + plan-reviewer (ไม่มี bug ค้าง/ไม่ละเมิด invariant) +
tsc + vitest 207/207 + e2e Playwright: ลาก DRAW FPS≈60.6 (commit
เลื่อนตรงระยะ/undo/redo/reload คงอยู่) + ลาก TEXTBOX FPS≈59-60.9 ไม่มี
เฟรม drop (commit/reload ตรงเป๊ะ)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
0681e056fd
commit
e4ecc026b0
3 changed files with 80 additions and 24 deletions
|
|
@ -183,6 +183,26 @@ export default function AnnotationSelectionLayer({
|
|||
const dragRef = useRef<DragState | null>(null);
|
||||
const movedRef = useRef(false);
|
||||
const latestRef = useRef<AnnotationData | null>(null);
|
||||
// rAF ของ preview ระหว่างลาก — ยิง onPreview ≤1 ครั้ง/เฟรม (pattern เดียวกับ FIX-8
|
||||
// ของ InkLayer): pointermove ยิงถี่กว่าเฟรมได้ ทุก event ยังคำนวณ+เก็บค่าล่าสุดใน
|
||||
// latestRef เสมอ (ค่าตอน commit ต้องล่าสุดสุด) เฉพาะการ render ที่จัดคิวตามเฟรม
|
||||
const previewRafRef = useRef<number | null>(null);
|
||||
|
||||
/** ยกเลิก rAF preview ที่ค้าง — เรียกก่อนเคลียร์ preview ทุกทางออกของ drag (ไม่งั้น
|
||||
* callback ยิงตามหลัง ดัน preview เก่ากลับมาตอน dragRef เคลียร์ไปแล้ว) */
|
||||
function cancelPreviewRaf() {
|
||||
if (previewRafRef.current === null) return;
|
||||
cancelAnimationFrame(previewRafRef.current);
|
||||
previewRafRef.current = null;
|
||||
}
|
||||
|
||||
// เลิกลากกลางคันแล้ว unmount — ยกเลิก rAF ที่ค้างอยู่ด้วย (เหมือน InkLayer)
|
||||
useEffect(
|
||||
() => () => {
|
||||
cancelPreviewRaf();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// อุปกรณ์จอสัมผัส (pointer หลัก coarse) — คำนวณครั้งเดียวตอน mount เหมือน
|
||||
// coarseTouch ของ PDFViewer: .coarse-36 ขยายปุ่ม pin เป็น 36px บนพวกนี้
|
||||
|
|
@ -395,7 +415,14 @@ export default function AnnotationSelectionLayer({
|
|||
}
|
||||
movedRef.current = true;
|
||||
latestRef.current = next;
|
||||
onPreview(next);
|
||||
// render preview ครั้งเดียวต่อ animation frame — callback อ่าน latestRef ตอนเฟรม
|
||||
// ยิงจริง ๆ เพื่อให้ได้ค่าใหม่สุดเสมอแม้ event ถี่ (แก้ลากไม่ smooth 2026-09-29)
|
||||
if (previewRafRef.current === null) {
|
||||
previewRafRef.current = requestAnimationFrame(() => {
|
||||
previewRafRef.current = null;
|
||||
if (dragRef.current && latestRef.current) onPreview(latestRef.current);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function endDrag(e: ReactPointerEvent<HTMLElement>) {
|
||||
|
|
@ -403,6 +430,7 @@ export default function AnnotationSelectionLayer({
|
|||
if (!st) return;
|
||||
e.stopPropagation();
|
||||
dragRef.current = null;
|
||||
cancelPreviewRaf();
|
||||
onPreview(null);
|
||||
if (movedRef.current && latestRef.current) onCommit(annotation.id, latestRef.current);
|
||||
movedRef.current = false;
|
||||
|
|
@ -412,6 +440,7 @@ export default function AnnotationSelectionLayer({
|
|||
function cancelDrag() {
|
||||
if (!dragRef.current) return;
|
||||
dragRef.current = null;
|
||||
cancelPreviewRaf();
|
||||
movedRef.current = false;
|
||||
latestRef.current = null;
|
||||
onPreview(null);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue