-
Notifications
You must be signed in to change notification settings - Fork 0
fix: responsive canvas dimensions for static and infinite scroll pages #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7e25976
5cbf15e
e181bdf
e2f41a0
94dc27d
6e54ba0
1851d53
10417fe
4d12f5d
e11a97e
5548349
f2293ba
212ab56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,23 +15,73 @@ import { usePencilPopover } from "@/context/pencil-popover/use-pencil-popover"; | |||||||||||||||||||||||||||||||||
| import { useTextPopover } from "@/context/text-popover/use-text-popover"; | ||||||||||||||||||||||||||||||||||
| import { getCanvasCoordinates, getOS } from "@/lib/helpers"; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function setupCanvas(fc: FabricCanvas) { | ||||||||||||||||||||||||||||||||||
| // Get the full document dimensions | ||||||||||||||||||||||||||||||||||
| const EXPANSION_INCREMENT_IN_PIXELS = 500; | ||||||||||||||||||||||||||||||||||
| const CANVAS_MAX_HEIGHT_IN_PIXELS = 8000; // Set a maximum height to prevent excessive canvas size | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function updateCanvasWidth(fc: FabricCanvas) { | ||||||||||||||||||||||||||||||||||
| const contentWidth = Math.max( | ||||||||||||||||||||||||||||||||||
| document.documentElement.clientWidth, | ||||||||||||||||||||||||||||||||||
| document.body.clientWidth | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fc.setDimensions({ width: contentWidth }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function updateCanvasHeight(fc: FabricCanvas) { | ||||||||||||||||||||||||||||||||||
| const contentHeight = Math.max( | ||||||||||||||||||||||||||||||||||
| document.documentElement.clientHeight, | ||||||||||||||||||||||||||||||||||
| document.body.clientHeight | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fc.setDimensions({ | ||||||||||||||||||||||||||||||||||
| width: contentWidth, | ||||||||||||||||||||||||||||||||||
| height: contentHeight, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| function updateDynamicCanvasHeight(fc: FabricCanvas) { | ||||||||||||||||||||||||||||||||||
| const { scrollY: scrollYAmount, innerHeight: viewportHeight } = window; | ||||||||||||||||||||||||||||||||||
| const currentCanvasHeight = fc.getHeight(); | ||||||||||||||||||||||||||||||||||
| let newCanvasHeight = currentCanvasHeight; | ||||||||||||||||||||||||||||||||||
| const visibleBottomY = viewportHeight + scrollYAmount; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fc.setDimensions({ height: 0 }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const contentScrollHeight = Math.max( | ||||||||||||||||||||||||||||||||||
| document.documentElement.scrollHeight, | ||||||||||||||||||||||||||||||||||
| document.body.scrollHeight | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fc.setDimensions({ height: currentCanvasHeight }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||
| visibleBottomY >= currentCanvasHeight && | ||||||||||||||||||||||||||||||||||
| currentCanvasHeight > CANVAS_MAX_HEIGHT_IN_PIXELS | ||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||
|
anth0nycodes marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||
| alert( | ||||||||||||||||||||||||||||||||||
| "This page is too tall for Tracemark to support. You can still draw on the visible canvas area, but it won't expand further." | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| fc.setDimensions({ | ||||||||||||||||||||||||||||||||||
| height: CANVAS_MAX_HEIGHT_IN_PIXELS, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| // TODO: remove alert and replace with better user-facing error handling | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+56
to
+67
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| while ( | ||||||||||||||||||||||||||||||||||
| newCanvasHeight < visibleBottomY && | ||||||||||||||||||||||||||||||||||
| visibleBottomY <= contentScrollHeight && | ||||||||||||||||||||||||||||||||||
| newCanvasHeight < CANVAS_MAX_HEIGHT_IN_PIXELS | ||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||
| newCanvasHeight += EXPANSION_INCREMENT_IN_PIXELS; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (newCanvasHeight > currentCanvasHeight) { | ||||||||||||||||||||||||||||||||||
| fc.setDimensions({ | ||||||||||||||||||||||||||||||||||
| height: Math.max(newCanvasHeight, viewportHeight), | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+81
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| interface CanvasProps { | ||||||||||||||||||||||||||||||||||
| currentTool: ToolbarStates; | ||||||||||||||||||||||||||||||||||
| setCurrentTool: (currentTool: ToolbarStates) => void; | ||||||||||||||||||||||||||||||||||
|
|
@@ -61,10 +111,26 @@ export function Canvas({ currentTool, setCurrentTool }: CanvasProps) { | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| fcRef.current = fc; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const initCanvasDimensions = () => setupCanvas(fc); | ||||||||||||||||||||||||||||||||||
| initCanvasDimensions(); | ||||||||||||||||||||||||||||||||||
| const handleResize = () => { | ||||||||||||||||||||||||||||||||||
| updateCanvasWidth(fc); | ||||||||||||||||||||||||||||||||||
| updateCanvasHeight(fc); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+116
|
||||||||||||||||||||||||||||||||||
| updateCanvasWidth(fc); | |
| updateCanvasHeight(fc); | |
| // On resize, only adjust the canvas width. Height is managed dynamically | |
| // via updateDynamicCanvasHeight to avoid shrinking an already-expanded canvas. | |
| updateCanvasWidth(fc); |
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleResize currently calls updateCanvasHeight, which sets the canvas height to clientHeight (viewport height). If the canvas has already been expanded via scrolling, any resize/observer trigger can shrink it back down and effectively cut off previously drawable areas. Consider keeping the existing expanded height (e.g., max(currentHeight, viewportHeight)) and only growing on resize, not shrinking.
| updateCanvasHeight(fc); | |
| const viewportHeight = document.documentElement.clientHeight; | |
| const currentHeight = fc.getHeight(); | |
| const desiredHeight = Math.max(currentHeight, viewportHeight); | |
| const newHeight = Math.min(CANVAS_MAX_HEIGHT_IN_PIXELS, desiredHeight); | |
| if (newHeight !== currentHeight) { | |
| fc.setHeight(newHeight); | |
| fc.renderAll(); | |
| } |
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateDynamicCanvasHeight is invoked directly on every scroll event and performs multiple setDimensions calls (including a collapse to height 0). This can be expensive during scrolling. Consider throttling via requestAnimationFrame/debounce and registering the listener as { passive: true } since it doesn't call preventDefault().
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scroll handler runs on every scroll event and updateDynamicCanvasHeight does multiple setDimensions calls plus a scrollHeight read, which can force layout and be expensive during scrolling. Consider adding a requestAnimationFrame-based throttle and registering the listener as { passive: true } to reduce main-thread jank.
Copilot
AI
Mar 11, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resizeTimeout can still fire after unmount/fc.dispose() (e.g., a ResizeObserver callback scheduled right before cleanup), which would call handleResize on a disposed Fabric canvas. Clear the pending timeout in the cleanup function to avoid post-dispose dimension updates/errors.
Copilot
AI
Mar 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resizeTimeout is not cleared in the effect cleanup. If a resize event schedules handleResize and the component unmounts (disposing the Fabric canvas) before the timeout fires, the callback can run against a disposed fc. Clear the pending timeout during cleanup (and consider typing it as ReturnType<typeof setTimeout> | undefined to reflect the runtime behavior).
Uh oh!
There was an error while loading. Please reload this page.