From cccbfd70dafc895c0aa8c651fe5983b40dc3f565 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 19 Aug 2019 21:37:11 -0700 Subject: [PATCH] perf: improve input responsiveness in compose input (#1413) * perf: improve input responsiveness in compose input * remove some unused code from autosize.js * remove some more unused code --- .../_components/compose/ComposeBox.html | 8 +++++ .../_components/compose/ComposeInput.html | 18 +++++----- src/routes/_thirdparty/autosize/autosize.js | 36 +++++-------------- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/routes/_components/compose/ComposeBox.html b/src/routes/_components/compose/ComposeBox.html index 8e18e6d8..d1622c76 100644 --- a/src/routes/_components/compose/ComposeBox.html +++ b/src/routes/_components/compose/ComposeBox.html @@ -102,6 +102,7 @@ import { postStatus, insertHandleForReply, setReplySpoiler, setReplyVisibility } from '../../_actions/compose' import { classname } from '../../_utils/classname' import { POLL_EXPIRY_DEFAULT } from '../../_static/polls' + import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' export default { oncreate () { @@ -179,6 +180,13 @@ }, methods: { doPostStatus () { + // The reason we add a scheduleIdleTask delay here is because we also use scheduleIdleTask + // in ComposeInput.html to debounce the input events. If the user is very fast at typing + // at their keyboard and quickly presses Ctrl+Enter or the "Toot" button then there could + // be a race condition where not all of their status is posted. + scheduleIdleTask(() => this.doPostStatusAfterDelay()) + }, + doPostStatusAfterDelay () { const { text, media, diff --git a/src/routes/_components/compose/ComposeInput.html b/src/routes/_components/compose/ComposeInput.html index f3478400..c5b4693a 100644 --- a/src/routes/_components/compose/ComposeInput.html +++ b/src/routes/_components/compose/ComposeInput.html @@ -59,7 +59,6 @@ import { store } from '../../_store/store' import { autosize } from '../../_thirdparty/autosize/autosize' import { scheduleIdleTask } from '../../_utils/scheduleIdleTask' - import debounce from 'lodash-es/debounce' import { mark, stop } from '../../_utils/marks' import { selectionChange } from '../../_utils/events' import { @@ -70,6 +69,9 @@ import { get } from '../../_utils/lodash-lite' import { on } from '../../_utils/eventBus' import { requestPostAnimationFrame } from '../../_utils/requestPostAnimationFrame' + import { throttleTimer } from '../../_utils/throttleTimer' + + const updateComposeTextInStore = throttleTimer(scheduleIdleTask) export default { oncreate () { @@ -106,14 +108,14 @@ }) }, setupSyncToStore () { - const saveStore = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) - + const { realm } = this.get() this.observe('rawText', rawText => { - mark('observe rawText') - const { realm } = this.get() - this.store.setComposeData(realm, { text: rawText }) - saveStore() - stop('observe rawText') + updateComposeTextInStore(() => { + mark('updateComposeTextInStore') + this.store.setComposeData(realm, { text: rawText }) + this.store.save() + stop('updateComposeTextInStore') + }) }, { init: false }) }, setupAutosize () { diff --git a/src/routes/_thirdparty/autosize/autosize.js b/src/routes/_thirdparty/autosize/autosize.js index 7f8275ff..52c80213 100644 --- a/src/routes/_thirdparty/autosize/autosize.js +++ b/src/routes/_thirdparty/autosize/autosize.js @@ -5,11 +5,12 @@ import { mark, stop } from '../../_utils/marks' import debounce from 'lodash-es/debounce' -import throttle from 'lodash-es/throttle' import { getScrollContainer } from '../../_utils/scrollContainer' +import { throttleTimer } from '../../_utils/throttleTimer' + +const doUpdate = process.browser && throttleTimer(requestAnimationFrame) const map = new Map() -const createEvent = (name) => new Event(name, { bubbles: true }) function assign (ta) { if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) { @@ -19,7 +20,6 @@ function assign (ta) { // TODO: hack - grab our scroll container so we can maintain the scrollTop const container = getScrollContainer() let heightOffset = null - let cachedHeight = null function init () { const style = window.getComputedStyle(ta, null) @@ -35,9 +35,8 @@ function assign (ta) { function resize () { mark('autosize:resize()') - const res = _resize() + _resize() stop('autosize:resize()') - return res } function _resize () { @@ -51,15 +50,13 @@ function assign (ta) { if (ta.scrollHeight === 0) { // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. ta.style.height = originalHeight - return + } else { + ta.style.height = `${endHeight}px` + container.scrollTop = scrollTop // Firefox jiggles if we don't reset the scrollTop of the container } - - ta.style.height = endHeight + 'px' - container.scrollTop = scrollTop // Firefox jiggles if we don't reset the scrollTop of the container - return endHeight } - const deferredUpdate = throttle(() => requestAnimationFrame(update), 100) + const deferredUpdate = () => doUpdate(update) function update () { mark('autosize:update()') @@ -68,17 +65,7 @@ function assign (ta) { } function _update () { - const newHeight = resize() - if (cachedHeight !== newHeight) { - cachedHeight = newHeight - const evt = createEvent('autosize:resized') - try { - ta.dispatchEvent(evt) - } catch (err) { - // Firefox will throw an error on dispatchEvent for a detached element - // https://bugzilla.mozilla.org/show_bug.cgi?id=889376 - } - } + resize() } const pageResize = debounce(() => requestAnimationFrame(update), 1000) @@ -86,17 +73,12 @@ function assign (ta) { const destroy = () => { window.removeEventListener('resize', pageResize, false) ta.removeEventListener('input', deferredUpdate, false) - ta.removeEventListener('autosize:destroy', destroy, false) - ta.removeEventListener('autosize:update', update, false) map.delete(ta) } - ta.addEventListener('autosize:destroy', destroy, false) - window.addEventListener('resize', pageResize, false) ta.addEventListener('input', deferredUpdate, false) - ta.addEventListener('autosize:update', update, false) map.set(ta, { destroy,