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:
Nolan Lawson 2019-08-07 20:38:01 -07:00 committed by GitHub
parent a5f68aa45c
commit b2d7fad435
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 45 deletions

View file

@ -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 })
}) })
} }
}, },

View file

@ -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) {

View 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
}
}

View file

@ -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)