diff --git a/src/Console/Console.js b/src/Console/Console.js index 2020c44..09cebe0 100644 --- a/src/Console/Console.js +++ b/src/Console/Console.js @@ -41,7 +41,7 @@ export default class Console extends Tool { } show() { super.show() - this._logger.restoreScroll() + this._logger.renderViewport() } overrideConsole() { const origConsole = (this._origConsole = {}) diff --git a/src/Console/Log.js b/src/Console/Log.js index e220b4c..04a1a98 100644 --- a/src/Console/Log.js +++ b/src/Console/Log.js @@ -31,10 +31,11 @@ import { trim, lowerCase, keys, - $ + $, + Emitter } from '../lib/util' -export default class Log { +export default class Log extends Emitter { constructor({ type = 'log', args = [], @@ -44,6 +45,8 @@ export default class Log { displayHeader = false, ignoreFilter = false }) { + super() + this.type = type this.group = group this.targetGroup = targetGroup @@ -137,8 +140,12 @@ export default class Log { isAttached() { return !!this.el.parentNode } - updateHeight() { - this.height = this.el.offsetHeight + updateHeight(silent = true) { + const height = this.el.offsetHeight + if (this.height !== height) { + this.height = this.el.offsetHeight + if (!silent) this.emit('updateHeight') + } } html() { return this.el.outerHTML @@ -193,7 +200,7 @@ export default class Log { if ($json.hasClass('eruda-hidden')) { if ($json.data('init') !== 'true') { const jsonViewer = new JsonViewer(src, $json) - jsonViewer.on('change', () => this.updateHeight()) + jsonViewer.on('change', () => this.updateHeight(false)) $json.data('init', 'true') } $json.rmClass('eruda-hidden') diff --git a/src/Console/Logger.js b/src/Console/Logger.js index 818a5d6..e89b09e 100644 --- a/src/Console/Logger.js +++ b/src/Console/Logger.js @@ -19,7 +19,7 @@ import { toArr, keys, last, - debounce + throttle } from '../lib/util' let id = 0 @@ -53,6 +53,8 @@ export default class Logger extends Emitter { this._isAtBottom = true this._groupStack = new Stack() + this.renderViewport = throttle(force => this._renderViewport(force), 16) + // https://developers.google.cn/web/tools/chrome-devtools/console/utilities this._global = { copy(value) { @@ -79,9 +81,6 @@ export default class Logger extends Emitter { this._bindEvent() } - restoreScroll() { - if (this._isAtBottom) this.scrollToBottom() - } renderAsync(flag) { this._asyncRender = flag } @@ -281,8 +280,6 @@ export default class Logger extends Emitter { this._attachLog(logs[i]) } - this.scrollToBottom() - return this } insert(type, args) { @@ -295,17 +292,6 @@ export default class Logger extends Emitter { this._handleAsyncList() } - isAtBottom() { - const { scrollTop, scrollHeight, offsetHeight } = this._container - - // invisible - if (offsetHeight !== 0) { - this._isAtBottom = scrollTop === scrollHeight - offsetHeight - return this._isAtBottom - } else { - return false - } - } insertSync(type, args) { const logs = this._logs const groupStack = this._groupStack @@ -318,8 +304,6 @@ export default class Logger extends Emitter { return this } - const isAtBottom = this.isAtBottom() - const options = isStr(type) ? { type, args } : type if (groupStack.size > 0) { options.group = groupStack.peek() @@ -342,6 +326,7 @@ export default class Logger extends Emitter { } let log = new Log(options) + log.on('updateHeight', () => this.renderViewport()) const lastLog = this._lastLog if ( @@ -371,16 +356,8 @@ export default class Logger extends Emitter { this.emit('insert', log) - if (isAtBottom) this.scrollToBottom() - return this } - scrollToBottom() { - const container = this._container - const { scrollHeight, offsetHeight } = container - - container.scrollTop = scrollHeight - offsetHeight - } toggleGroup(log) { const { targetGroup } = log targetGroup.collapsed ? this._openGroup(log) : this._collapseGroup(log) @@ -394,6 +371,7 @@ export default class Logger extends Emitter { this._$bottomSpace.css({ height }) } _updateLogHeight(log) { + if (this._fakeEl.offsetParent === null) return if (!log.isAttached()) { this._fakeEl.appendChild(log.el) log.updateHeight() @@ -408,7 +386,7 @@ export default class Logger extends Emitter { const idx = displayLogs.indexOf(log) if (idx > -1) { displayLogs.splice(idx, 1) - this._renderViewport() + this.renderViewport() } } // Binary search @@ -419,14 +397,14 @@ export default class Logger extends Emitter { if (displayLogs.length === 0) { displayLogs.push(log) - this._renderViewport() + this.renderViewport() return } const lastDisplayLog = last(displayLogs) if (log.id > lastDisplayLog.id) { displayLogs.push(log) - this._renderViewport() + this.renderViewport() return } @@ -457,7 +435,7 @@ export default class Logger extends Emitter { displayLogs.splice(middleIdx, 0, log) } - this._renderViewport() + this.renderViewport() } _handleAsyncList() { const asyncList = this._asyncList @@ -467,7 +445,7 @@ export default class Logger extends Emitter { this._asyncTimer = setTimeout(() => { this._asyncTimer = null let done = false - for (let i = 0; i < 25; i++) { + for (let i = 0; i < 20; i++) { const item = asyncList.shift() if (!item) { done = true @@ -476,7 +454,7 @@ export default class Logger extends Emitter { this.insertSync(item[0], item[1]) } if (!done) this._handleAsyncList() - }, 25) + }, 15) } _injectGlobal() { each(this._global, (val, name) => { @@ -584,35 +562,69 @@ export default class Logger extends Emitter { self._openGroup($el.get(0).log) }) - const renderViewport = debounce(() => this._renderViewport(), 15) - this._$container.on('scroll', renderViewport) + this._$container.on('scroll', () => this.renderViewport(false)) } - _renderViewport() { - const { scrollTop, offsetHeight } = this._container - const top = scrollTop - const bottom = scrollTop + offsetHeight + _renderViewport(force = true) { + const container = this._container + if (container.offsetParent === null) return + const { scrollTop, offsetHeight } = container + let top = scrollTop + let bottom = scrollTop + offsetHeight + + if (!force) { + if ( + this._topSpaceHeight < top && + this._topSpaceHeight + this._el.offsetHeight > bottom + ) { + return + } + } + const displayLogs = this._displayLogs + const tolerance = 1000 + top -= tolerance + bottom += tolerance let topSpaceHeight = 0 let bottomSpaceHeight = 0 let currentHeight = 0 this._$el.html('') + const frag = document.createDocumentFragment() for (let i = 0, len = displayLogs.length; i < len; i++) { - const { el, height } = displayLogs[i] + const log = displayLogs[i] + const { el } = log + let { height } = log + if (height === 0) { + this._updateLogHeight(log) + height = log.height + } if (currentHeight > bottom) { bottomSpaceHeight += height } else if (currentHeight + height > top) { - this._el.appendChild(el) + frag.appendChild(el) } else if (currentHeight < top) { topSpaceHeight += height } currentHeight += height } + this._el.appendChild(frag) this._updateTopSpace(topSpaceHeight) this._updateBottomSpace(bottomSpaceHeight) + + container.scrollTop = scrollTop + + const scrollHeight = container.scrollHeight + if (this._isAtBottom) { + container.scrollTop = scrollHeight - offsetHeight + this._isAtBottom = true + } else if (scrollHeight === offsetHeight) { + this._isAtBottom = true + } else if (container.scrollTop === scrollHeight - offsetHeight) { + this._isAtBottom = true + } } }