improve perf of compose box

This commit is contained in:
Nolan Lawson 2018-02-25 20:45:11 -08:00
parent 6c71910660
commit 2ffd38383d
6 changed files with 201 additions and 17 deletions

5
package-lock.json generated
View file

@ -357,11 +357,6 @@
"postcss-value-parser": "3.3.0"
}
},
"autosize": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.0.tgz",
"integrity": "sha1-egWZsbqE1zvXWJsNnaOHAVLGkjc="
},
"aws-sign2": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",

View file

@ -25,7 +25,6 @@
"dependencies": {
"@gamestdio/websocket": "^0.2.2",
"a11y-dialog": "^4.0.1",
"autosize": "^4.0.0",
"cheerio": "^1.0.0-rc.2",
"child-process-promise": "^2.2.1",
"chokidar": "^2.0.0",
@ -95,7 +94,8 @@
"__shell__",
"__assets__",
"test",
"fixture"
"fixture",
"Element"
],
"ignore": [
"dist",

View file

@ -1,4 +1,4 @@
<div class="lite-compose-box">
<div class="lite-compose-box {{overLimit ? 'over-char-limit' : ''}}">
<div class="compose-profile-current-user">
<Avatar account="{{verifyCredentials}}" className="compose-profile-avatar" size="small"/>
<a class="compose-profile-display-name" href="/accounts/{{verifyCredentials.id}}">
@ -13,13 +13,13 @@
ref:textarea
bind:value=inputText
></textarea>
<div class="compose-profile-length-gauge {{overLimit ? 'over-char-limit' : ''}}"
style="transform: scaleX({{inputLengthAsFractionOfMax}});"
<div class="compose-profile-length-gauge"
style="transform: scaleX({{inputLengthAsFractionOfMaxAfterRaf || 0}});"
aria-hidden="true"
></div>
<span class="compose-profile-length {{overLimit ? 'over-char-limit' : ''}}"
<span class="compose-profile-length"
aria-label="{{inputLengthLabel}}">
{{inputLengthToDisplay}}
{{inputLengthToDisplayAfterRaf || '0'}}
</span>
<button class="primary compose-profile-button">
Toot!
@ -88,6 +88,7 @@
margin-top: 10px;
resize: none;
overflow: hidden;
width: calc(100% - 5px);
}
.compose-profile-button {
@ -113,11 +114,11 @@
font-size: 1.1em;
}
.compose-profile-length.over-char-limit {
.over-char-limit .compose-profile-length {
color: var(--warning-color);
}
.compose-profile-length-gauge.over-char-limit {
.over-char-limit .compose-profile-length-gauge {
background: var(--warning-color);
}
@ -137,7 +138,7 @@
<script>
import Avatar from '../Avatar.html'
import { store } from '../../_store/store'
import autosize from 'autosize'
import { autosize } from '../../_utils/autosize'
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
import debounce from 'lodash/debounce'
import { mark, stop } from '../../_utils/marks'
@ -160,6 +161,22 @@
this.store.set({inputTextInCompose: inputTextInCompose})
saveText()
}, {init: false})
// Avoid input delays by updating these values after a rAF
this.observe('inputLengthToDisplay', inputLengthToDisplay => {
requestAnimationFrame(() => {
mark('set inputLengthToDisplayAfterRaf')
this.set({inputLengthToDisplayAfterRaf: inputLengthToDisplay})
stop('set inputLengthToDisplayAfterRaf')
})
})
this.observe('inputLengthAsFractionOfMax', inputLengthAsFractionOfMax => {
requestAnimationFrame(() => {
mark('set inputLengthAsFractionOfMaxAfterRaf')
this.set({inputLengthAsFractionOfMaxAfterRaf: inputLengthAsFractionOfMax})
stop('set inputLengthAsFractionOfMaxAfterRaf')
})
})
},
ondestroy() {
mark('autosize.destroy()')
@ -177,7 +194,7 @@
currentInputTextInCompose: ($currentInputTextInCompose) => $currentInputTextInCompose,
inputLength: (inputText) => inputText ? inputText.length : 0,
inputLengthToDisplay: (inputLength) => (inputLength <= CHAR_LIMIT ? inputLength : CHAR_LIMIT - inputLength),
inputLengthAsFractionOfMax: (inputLength) => (Math.min(CHAR_LIMIT, inputLength)) / CHAR_LIMIT,
inputLengthAsFractionOfMax: (inputLength) => Math.round(100 * (Math.min(CHAR_LIMIT, inputLength)) / CHAR_LIMIT) / 100,
overLimit: (inputLength) => inputLength > CHAR_LIMIT,
inputLengthLabel: (overLimit, inputLengthToDisplay) => {
if (overLimit) {

166
routes/_utils/autosize.js Normal file
View file

@ -0,0 +1,166 @@
// Modified from https://github.com/jackmoore/autosize/commit/113f1b345868901619d4b01cda02b09aa1690ebd
// The only change is to remove IE-specific hacks,
// remove parent overflow checks, make page resizes more performant,
// add deferredUpdate, and add perf marks.
import { mark, stop } from './marks'
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
const map = new Map()
let createEvent = (name) => new Event(name, {bubbles: true})
function assign (ta) {
if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return
let heightOffset = null
let cachedHeight = null
function init () {
const style = window.getComputedStyle(ta, null)
if (style.resize === 'vertical') {
ta.style.resize = 'none'
} else if (style.resize === 'both') {
ta.style.resize = 'horizontal'
}
if (style.boxSizing === 'content-box') {
heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom))
} else {
heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth)
}
// Fix when a textarea is not on document body and heightOffset is Not a Number
if (isNaN(heightOffset)) {
heightOffset = 0
}
update()
}
function resize () {
mark('autosize:resize()')
let res = _resize()
stop('autosize:resize()')
return res
}
function _resize () {
const originalHeight = ta.style.height
ta.style.height = ''
let endHeight = ta.scrollHeight + heightOffset
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
}
ta.style.height = endHeight + 'px'
return endHeight
}
const deferredUpdate = throttle(() => requestAnimationFrame(update), 100)
function update () {
mark('autosize:update()')
_update()
stop('autosize:update()')
}
function _update () {
let 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
}
}
}
const pageResize = debounce(update, 1000)
const destroy = (style => {
window.removeEventListener('resize', pageResize, false)
ta.removeEventListener('input', deferredUpdate, false)
ta.removeEventListener('autosize:destroy', destroy, false)
ta.removeEventListener('autosize:update', update, false)
Object.keys(style).forEach(key => {
ta.style[key] = style[key]
})
map.delete(ta)
}).bind(ta, {
height: ta.style.height,
resize: ta.style.resize,
overflowY: ta.style.overflowY,
overflowX: ta.style.overflowX,
wordWrap: ta.style.wordWrap
})
ta.addEventListener('autosize:destroy', destroy, false)
window.addEventListener('resize', pageResize, false)
ta.addEventListener('input', deferredUpdate, false)
ta.addEventListener('autosize:update', update, false)
ta.style.overflowX = 'hidden'
ta.style.wordWrap = 'break-word'
map.set(ta, {
destroy,
update
})
init()
}
function destroy (ta) {
const methods = map.get(ta)
if (methods) {
methods.destroy()
}
}
function update (ta) {
const methods = map.get(ta)
if (methods) {
methods.update()
}
}
let autosize = null
// Do nothing in Node.js environment and IE8 (or lower)
if (!process.browser) {
autosize = el => el
autosize.destroy = el => el
autosize.update = el => el
} else {
autosize = (el, options) => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], x => assign(x, options))
}
return el
}
autosize.destroy = el => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], destroy)
}
return el
}
autosize.update = el => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], update)
}
return el
}
}
export { autosize }

View file

@ -134,4 +134,10 @@ button::-moz-focus-inner {
Unnecessary since it gives a warning if you submit an empty field anyway. */
input:required, input:invalid {
box-shadow: none;
}
textarea {
font-family: inherit;
font-size: inherit;
box-sizing: border-box;
}

View file

@ -11,7 +11,7 @@
<style>
/* auto-generated w/ build-sass.js */
body{--button-primary-bg:#6081e6;--button-primary-text:#fff;--button-primary-border:#132c76;--button-primary-bg-active:#456ce2;--button-primary-bg-hover:#6988e7;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#4169e1;--main-bg:#fff;--body-bg:#e8edfb;--body-text-color:#333;--main-border:#dadada;--svg-fill:#4169e1;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#4169e1;--nav-border:#214cce;--nav-a-border:#4169e1;--nav-a-selected-border:#fff;--nav-a-selected-bg:#6d8ce8;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#839deb;--nav-a-bg-hover:#577ae4;--nav-a-border-hover:#4169e1;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#90a8ee;--action-button-fill-color-hover:#a2b6f0;--action-button-fill-color-active:#577ae4;--action-button-fill-color-pressed:#2351dc;--action-button-fill-color-pressed-hover:#3862e0;--action-button-fill-color-pressed-active:#1d44b8;--settings-list-item-bg:#fff;--settings-list-item-text:#4169e1;--settings-list-item-text-hover:#4169e1;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#c5d1f6;--very-deemphasized-link-color:rgba(65,105,225,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#d2dcf8;--main-theme-color:#4169e1;--warning-color:#e01f19}
body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.4;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;will-change:transform;position:absolute;top:72px;left:0;right:0;bottom:0}@media (max-width: 767px){.container{top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px;box-sizing:border-box}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}button::-moz-focus-inner{border:0}input:required,input:invalid{box-shadow:none}
body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.4;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;will-change:transform;position:absolute;top:72px;left:0;right:0;bottom:0}@media (max-width: 767px){.container{top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px;box-sizing:border-box}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}button::-moz-focus-inner{border:0}input:required,input:invalid{box-shadow:none}textarea{font-family:inherit;font-size:inherit;box-sizing:border-box}
body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-oaken.offline,body.theme-scarlet.offline,body.theme-seafoam.offline,body.theme-gecko.offline{--button-primary-bg:#ababab;--button-primary-text:#fff;--button-primary-border:#4d4d4d;--button-primary-bg-active:#9c9c9c;--button-primary-bg-hover:#b0b0b0;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#999;--main-bg:#fff;--body-bg:#fafafa;--body-text-color:#333;--main-border:#dadada;--svg-fill:#999;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#999;--nav-border:gray;--nav-a-border:#999;--nav-a-selected-border:#fff;--nav-a-selected-bg:#b3b3b3;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#bfbfbf;--nav-a-bg-hover:#a6a6a6;--nav-a-border-hover:#999;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#c7c7c7;--action-button-fill-color-hover:#d1d1d1;--action-button-fill-color-active:#a6a6a6;--action-button-fill-color-pressed:#878787;--action-button-fill-color-pressed-hover:#949494;--action-button-fill-color-pressed-active:#737373;--settings-list-item-bg:#fff;--settings-list-item-text:#999;--settings-list-item-text-hover:#999;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#bfbfbf;--very-deemphasized-link-color:rgba(153,153,153,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#ededed;--main-theme-color:#999;--warning-color:#e01f19}
</style>