perf: only update draggable x/y state at end of drag (#1379)
* perf: only update draggable x/y state at end of drag This is more intelligent and more performant than using requestIdleCallback willy-nilly. We can just update the store when the user is actually done dragging the button. * remove console.log * consistent syntax
This commit is contained in:
parent
a5f68aa45c
commit
b2d7fad435
|
@ -31,19 +31,17 @@
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { observe } from 'svelte-extras'
|
import { observe } from 'svelte-extras'
|
||||||
import {
|
import { throttleTimer } from '../_utils/throttleTimer'
|
||||||
throttleRequestAnimationFrame,
|
|
||||||
throttleRequestPostAnimationFrame
|
|
||||||
} from '../_utils/throttleTimers'
|
|
||||||
import { pointerUp, pointerDown, pointerLeave, pointerMove } from '../_utils/pointerEvents'
|
import { pointerUp, pointerDown, pointerLeave, pointerMove } from '../_utils/pointerEvents'
|
||||||
|
import { requestPostAnimationFrame } from '../_utils/requestPostAnimationFrame'
|
||||||
|
|
||||||
// ensure DOM writes only happen once after a rAF
|
// ensure DOM writes only happen once after a rAF
|
||||||
const updateIndicatorStyle = throttleRequestAnimationFrame()
|
const updateIndicatorStyle = throttleTimer(requestAnimationFrame)
|
||||||
const updateIndicatorClass = throttleRequestAnimationFrame()
|
const updateIndicatorClass = throttleTimer(requestAnimationFrame)
|
||||||
const updateDraggableClass = throttleRequestAnimationFrame()
|
const updateDraggableClass = throttleTimer(requestAnimationFrame)
|
||||||
|
|
||||||
// ensure DOM reads only happen once after a rPAF
|
// ensure DOM reads only happen once after a rPAF
|
||||||
const calculateGBCR = throttleRequestPostAnimationFrame()
|
const calculateGBCR = throttleTimer(requestPostAnimationFrame)
|
||||||
|
|
||||||
const clamp = x => Math.max(0, Math.min(1, x))
|
const clamp = x => Math.max(0, Math.min(1, x))
|
||||||
|
|
||||||
|
@ -53,7 +51,9 @@
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
this.fire('dragStart')
|
this.fire('dragStart')
|
||||||
} else {
|
} else {
|
||||||
|
const { x, y } = this.get()
|
||||||
this.fire('dragEnd')
|
this.fire('dragEnd')
|
||||||
|
this.fire('change', { x, y })
|
||||||
}
|
}
|
||||||
}, { init: false })
|
}, { init: false })
|
||||||
this.observe('indicatorStyle', indicatorStyle => {
|
this.observe('indicatorStyle', indicatorStyle => {
|
||||||
|
@ -115,7 +115,6 @@
|
||||||
const x = clamp((e.clientX - rect.left - offsetX) / rect.width)
|
const x = clamp((e.clientX - rect.left - offsetX) / rect.width)
|
||||||
const y = clamp((e.clientY - rect.top - offsetY) / rect.height)
|
const y = clamp((e.clientY - rect.top - offsetY) / rect.height)
|
||||||
this.set({ x, y })
|
this.set({ x, y })
|
||||||
this.fire('change', { x, y })
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
indicatorHeight={52}
|
indicatorHeight={52}
|
||||||
x={indicatorX}
|
x={indicatorX}
|
||||||
y={indicatorY}
|
y={indicatorY}
|
||||||
|
on:dragStart="onDragStart()"
|
||||||
|
on:dragEnd="onDragEnd()"
|
||||||
on:change="onDraggableChange(event)"
|
on:change="onDraggableChange(event)"
|
||||||
on:dragStart="set({dragging: true})"
|
|
||||||
on:dragEnd="set({dragging: false})"
|
|
||||||
>
|
>
|
||||||
<SvgIcon
|
<SvgIcon
|
||||||
className="media-focal-point-indicator-svg"
|
className="media-focal-point-indicator-svg"
|
||||||
|
@ -193,11 +193,6 @@
|
||||||
import { intrinsicScale } from '../../../_thirdparty/intrinsic-scale/intrinsicScale'
|
import { intrinsicScale } from '../../../_thirdparty/intrinsic-scale/intrinsicScale'
|
||||||
import { resize } from '../../../_utils/events'
|
import { resize } from '../../../_utils/events'
|
||||||
import Draggable from '../../Draggable.html'
|
import Draggable from '../../Draggable.html'
|
||||||
import { throttleScheduleIdleTask } from '../../../_utils/throttleTimers'
|
|
||||||
|
|
||||||
// Updating the focal points in the store causes a lot of computations (extra JS work),
|
|
||||||
// so we really don't want to do it for every drag event.
|
|
||||||
const updateFocalPointsInStore = throttleScheduleIdleTask()
|
|
||||||
|
|
||||||
const parseAndValidateFloat = rawText => {
|
const parseAndValidateFloat = rawText => {
|
||||||
let float = parseFloat(rawText)
|
let float = parseFloat(rawText)
|
||||||
|
@ -307,7 +302,7 @@
|
||||||
observeAndSync('rawFocusY', 'focusY')
|
observeAndSync('rawFocusY', 'focusY')
|
||||||
},
|
},
|
||||||
onDraggableChange ({ x, y }) {
|
onDraggableChange ({ x, y }) {
|
||||||
updateFocalPointsInStore(() => {
|
scheduleIdleTask(() => {
|
||||||
const focusX = parseAndValidateFloat(percentToCoords(x * 100))
|
const focusX = parseAndValidateFloat(percentToCoords(x * 100))
|
||||||
const focusY = parseAndValidateFloat(percentToCoords(100 - (y * 100)))
|
const focusY = parseAndValidateFloat(percentToCoords(100 - (y * 100)))
|
||||||
const { realm, index, media } = this.get()
|
const { realm, index, media } = this.get()
|
||||||
|
@ -319,6 +314,12 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onDragStart () {
|
||||||
|
this.set({ dragging: true })
|
||||||
|
},
|
||||||
|
onDragEnd () {
|
||||||
|
this.set({ dragging: false })
|
||||||
|
},
|
||||||
measure () {
|
measure () {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!this.refs.container) {
|
if (!this.refs.container) {
|
||||||
|
|
20
src/routes/_utils/throttleTimer.js
Normal file
20
src/routes/_utils/throttleTimer.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Sometimes we want to queue multiple requestAnimationFrames but only run the last one.
|
||||||
|
// It's tedious to do this using cancelAnimationFrame, so this is a utility to throttle
|
||||||
|
// a timer such that it only runs the last callback when it fires.
|
||||||
|
|
||||||
|
export const throttleTimer = timer => {
|
||||||
|
let queuedCallback
|
||||||
|
|
||||||
|
const flush = () => {
|
||||||
|
const callback = queuedCallback
|
||||||
|
queuedCallback = null
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback => {
|
||||||
|
if (!queuedCallback) {
|
||||||
|
timer(flush)
|
||||||
|
}
|
||||||
|
queuedCallback = callback
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
// Sometimes we want to queue multiple requestAnimationFrames but only run the last one.
|
|
||||||
// It's tedious to do this using cancelAnimationFrame, so this is a utility to throttle
|
|
||||||
// a timer such that it only runs the last callback when it fires.
|
|
||||||
|
|
||||||
import { requestPostAnimationFrame } from './requestPostAnimationFrame'
|
|
||||||
import { scheduleIdleTask } from './scheduleIdleTask'
|
|
||||||
|
|
||||||
const throttle = (timer) => {
|
|
||||||
return () => {
|
|
||||||
let queuedCallback
|
|
||||||
|
|
||||||
return function throttledRaf (callback) {
|
|
||||||
const alreadyQueued = !!queuedCallback
|
|
||||||
queuedCallback = callback
|
|
||||||
if (!alreadyQueued) {
|
|
||||||
timer(() => {
|
|
||||||
const cb = queuedCallback
|
|
||||||
queuedCallback = null
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const throttleRequestAnimationFrame = throttle(requestAnimationFrame)
|
|
||||||
export const throttleRequestPostAnimationFrame = throttle(requestPostAnimationFrame)
|
|
||||||
export const throttleScheduleIdleTask = throttle(scheduleIdleTask)
|
|
Loading…
Reference in a new issue