Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/components/maskeditor/BrushCursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,39 @@ describe('BrushCursor', () => {
expect(style).toContain('left: 150px')
expect(style).toContain('top: 220px')
})

it('should read the container rect once per position, not once per axis', () => {
mockStore.cursorPoint = { x: 200, y: 300 }
mockStore.panOffset = { x: 0, y: 0 }
mockStore.brushSettings.size = 20
mockStore.brushSettings.hardness = 1
mockStore.zoomRatio = 1

const container = document.createElement('div')
const readRect = vi
.spyOn(container, 'getBoundingClientRect')
.mockReturnValue({
left: 30,
top: 60,
right: 0,
bottom: 0,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => ({})
} as DOMRect)

renderCursor(container)

// `getBoundingClientRect` forces a synchronous layout and the cursor
// moves on every mousemove, so reading it per axis doubled the cost for
// one rect.
expect(
readRect,
'left and top come from the same rect; reading it twice is two forced layouts per mousemove'
).toHaveBeenCalledTimes(1)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})

describe('gradient preview', () => {
Expand Down
45 changes: 27 additions & 18 deletions src/components/maskeditor/BrushCursor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,36 @@ const brushSize = computed(() => {
return brushRadius.value * 2
})

const brushLeft = computed(() => {
/**
* One rect read per move, not one per axis. `cursorPoint` changes on every
* mousemove and `getBoundingClientRect` forces layout, so reading it separately
* in `brushLeft` and `brushTop` cost two synchronous layouts per event for a
* single rect.
*
* The read is deliberately still inside a computed over `cursorPoint`: the rect
* is not reactive, and re-reading it as the cursor moves is what keeps the
* offset correct while the dialog is dragged or resized. This halves the reads
* without changing when they happen.
*/
const brushPosition = computed(() => {
const dialogRect = containerRef?.getBoundingClientRect()
const dialogOffsetLeft = dialogRect?.left || 0
return (
store.cursorPoint.x +
store.panOffset.x -
brushRadius.value -
dialogOffsetLeft
)
return {
left:
store.cursorPoint.x +
store.panOffset.x -
brushRadius.value -
(dialogRect?.left || 0),
top:
store.cursorPoint.y +
store.panOffset.y -
brushRadius.value -
(dialogRect?.top || 0)
}
})

const brushTop = computed(() => {
const dialogRect = containerRef?.getBoundingClientRect()
const dialogOffsetTop = dialogRect?.top || 0
return (
store.cursorPoint.y +
store.panOffset.y -
brushRadius.value -
dialogOffsetTop
)
})
const brushLeft = computed(() => brushPosition.value.left)

const brushTop = computed(() => brushPosition.value.top)

const borderRadius = computed(() => {
return store.brushSettings.type === BrushShape.Rect ? '0%' : '50%'
Expand Down
Loading