From e546f193dc4f0c47883a6d290c5d4b3d21c25dbc Mon Sep 17 00:00:00 2001 From: plainheart Date: Sun, 12 Dec 2021 07:40:01 +0800 Subject: [PATCH 1/2] fix(tooltip): only throttle `updatePosition` function when transition is enabled, resolves #16101. --- src/component/tooltip/TooltipHTMLContent.ts | 1 - src/component/tooltip/TooltipView.ts | 79 +++++++++++---------- test/tooltip-lag-glitch.html | 26 ++++++- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts index b324d6adab..baca229954 100644 --- a/src/component/tooltip/TooltipHTMLContent.ts +++ b/src/component/tooltip/TooltipHTMLContent.ts @@ -18,7 +18,6 @@ */ import { isString, indexOf, each, bind, isArray, isDom } from 'zrender/src/core/util'; -import { toHex } from 'zrender/src/tool/color'; import { normalizeEvent } from 'zrender/src/core/event'; import { transformLocalCoord } from 'zrender/src/core/dom'; import env from 'zrender/src/core/env'; diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts index 1523a182e6..a3173632f0 100644 --- a/src/component/tooltip/TooltipView.ts +++ b/src/component/tooltip/TooltipView.ts @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import * as zrUtil from 'zrender/src/core/util'; +import { bind, each, clone, trim, isString, isFunction, isArray, isObject } from 'zrender/src/core/util'; import env from 'zrender/src/core/env'; import TooltipHTMLContent from './TooltipHTMLContent'; import TooltipRichContent from './TooltipRichContent'; import * as formatUtil from '../../util/format'; -import * as numberUtil from '../../util/number'; +import { parsePercent } from '../../util/number'; import * as graphic from '../../util/graphic'; import findPointFromSeries from '../axisPointer/findPointFromSeries'; import * as layoutUtil from '../../util/layout'; @@ -57,11 +57,7 @@ import { DataByCoordSys, DataByAxis } from '../axisPointer/axisTrigger'; import { normalizeTooltipFormatResult } from '../../model/mixin/dataFormat'; import { createTooltipMarkup, buildTooltipMarkup, TooltipMarkupStyleCreator } from './tooltipMarkup'; import { findEventDispatcher } from '../../util/event'; -import { throttle } from '../../util/throttle'; - -const bind = zrUtil.bind; -const each = zrUtil.each; -const parsePercent = numberUtil.parsePercent; +import { throttle, ThrottleController } from '../../util/throttle'; const proxyRect = new graphic.Rect({ shape: { x: -1, y: -1, width: 2, height: 2 } @@ -168,7 +164,8 @@ class TooltipView extends ComponentView { private _lastDataByCoordSys: DataByCoordSys[]; private _cbParamsList: TooltipCallbackDataParams[]; - private _updatePosition: ReturnType | TooltipView['_doUpdatePosition']; + private _updatePosition: TooltipView['_doUpdatePosition']; + private _throttledUpdatePosition: TooltipView['_doUpdatePosition'] & ThrottleController; init(ecModel: GlobalModel, api: ExtensionAPI) { if (env.node || !api.getDom()) { @@ -176,14 +173,19 @@ class TooltipView extends ComponentView { } const tooltipModel = ecModel.getComponent('tooltip') as TooltipModel; - const renderMode = tooltipModel.get('renderMode'); - this._renderMode = getTooltipRenderMode(renderMode); + const renderMode = this._renderMode = getTooltipRenderMode(tooltipModel.get('renderMode')); + + const isRichTextMode = renderMode === 'richText'; - this._tooltipContent = this._renderMode === 'richText' + this._tooltipContent = isRichTextMode ? new TooltipRichContent(api) : new TooltipHTMLContent(api.getDom(), api, { appendToBody: tooltipModel.get('appendToBody', true) }); + + if (!isRichTextMode) { + this._throttledUpdatePosition = throttle(bind(this._doUpdatePosition, this), 50); + } } render( @@ -220,14 +222,17 @@ class TooltipView extends ComponentView { // PENDING // `mousemove` event will be triggered very frequently when the mouse moves fast, - // which causes that the updatePosition was also called very frequently. - // In Chrome with devtools open and Firefox, tooltip looks lagged and shaked around. See #14695. - // To avoid the frequent triggering, - // consider throttling it in 50ms. (the tested result may need to validate) - this._updatePosition = - this._renderMode === 'html' - ? throttle(bind(this._doUpdatePosition, this), 50) - : this._doUpdatePosition; + // which causes that the `updatePosition` function was also called frequently. + // In Chrome with devtools open and Firefox, tooltip looks laggy and shakes. See #14695 #16101 + // To avoid frequent triggering, + // consider throttling it in 50ms when transition is enabled + this._updatePosition = (...args: any[]) => { + const ctx = this; + const updateFn = tooltipModel.get('transitionDuration') + ? ctx._throttledUpdatePosition + : ctx._doUpdatePosition; + return updateFn.apply(ctx, args); + }; } private _initGlobalListener() { @@ -517,7 +522,7 @@ class TooltipView extends ComponentView { // something. `showDelay` makes it easier to enter the content // but tooltip do not move immediately. const delay = tooltipModel.get('showDelay'); - cb = zrUtil.bind(cb, this); + cb = bind(cb, this); clearTimeout(this._showTimout); delay > 0 ? (this._showTimout = setTimeout(cb, delay) as any) @@ -559,13 +564,13 @@ class TooltipView extends ComponentView { ); const axisSectionMarkup = createTooltipMarkup('section', { header: axisValueLabel, - noHeader: !zrUtil.trim(axisValueLabel), + noHeader: !trim(axisValueLabel), sortBlocks: true, blocks: [] }); articleMarkup.blocks.push(axisSectionMarkup); - zrUtil.each(axisItem.seriesDataIndices, function (idxItem) { + each(axisItem.seriesDataIndices, function (idxItem) { const series = ecModel.getSeriesByIndex(idxItem.seriesIndex); const dataIndex = idxItem.dataIndexInside; const cbParams = series.getDataParams(dataIndex) as TooltipCallbackDataParams; @@ -728,7 +733,7 @@ class TooltipView extends ComponentView { const ecData = getECData(el); const tooltipConfig = ecData.tooltipConfig; let tooltipOpt = tooltipConfig.option || {}; - if (zrUtil.isString(tooltipOpt)) { + if (isString(tooltipOpt)) { const content = tooltipOpt; tooltipOpt = { content: content, @@ -766,7 +771,7 @@ class TooltipView extends ComponentView { this._showOrMove(subTooltipModel, function (this: TooltipView) { // Use formatterParams from element defined in component // Avoid users modify it. - const formatterParams = zrUtil.clone(subTooltipModel.get('formatterParams') as any || {}); + const formatterParams = clone(subTooltipModel.get('formatterParams') as any || {}); this._showTooltipContent( subTooltipModel, defaultHtml, formatterParams, asyncTicket, e.offsetX, e.offsetY, e.position, el, markupStyleCreator @@ -814,9 +819,9 @@ class TooltipView extends ComponentView { const nearPointColor = nearPoint.color; if (formatter) { - if (zrUtil.isString(formatter)) { + if (isString(formatter)) { const useUTC = tooltipModel.ecModel.get('useUTC'); - const params0 = zrUtil.isArray(params) ? params[0] : params; + const params0 = isArray(params) ? params[0] : params; const isTimeAxis = params0 && params0.axisType && params0.axisType.indexOf('time') >= 0; html = formatter; if (isTimeAxis) { @@ -824,7 +829,7 @@ class TooltipView extends ComponentView { } html = formatUtil.formatTpl(html, params, true); } - else if (zrUtil.isFunction(formatter)) { + else if (isFunction(formatter)) { const callback = bind(function (cbTicket: string, html: string | HTMLElement | HTMLElement[]) { if (cbTicket === this._ticket) { tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr); @@ -857,13 +862,13 @@ class TooltipView extends ComponentView { ): { color: ZRColor; } { - if (trigger === 'axis' || zrUtil.isArray(tooltipDataParams)) { + if (trigger === 'axis' || isArray(tooltipDataParams)) { return { color: borderColor || (this._renderMode === 'html' ? '#fff' : 'none') }; } - if (!zrUtil.isArray(tooltipDataParams)) { + if (!isArray(tooltipDataParams)) { return { color: borderColor || tooltipDataParams.color || tooltipDataParams.borderColor }; @@ -890,7 +895,7 @@ class TooltipView extends ComponentView { const rect = el && el.getBoundingRect().clone(); el && rect.applyTransform(el.transform); - if (zrUtil.isFunction(positionExpr)) { + if (isFunction(positionExpr)) { // Callback of position can be an array or a string specify the position positionExpr = positionExpr([x, y], params, content.el, rect, { viewSize: [viewWidth, viewHeight], @@ -898,11 +903,11 @@ class TooltipView extends ComponentView { }); } - if (zrUtil.isArray(positionExpr)) { + if (isArray(positionExpr)) { x = parsePercent(positionExpr[0], viewWidth); y = parsePercent(positionExpr[1], viewHeight); } - else if (zrUtil.isObject(positionExpr)) { + else if (isObject(positionExpr)) { const boxLayoutPosition = positionExpr as BoxLayoutOptionMixin; boxLayoutPosition.width = contentSize[0]; boxLayoutPosition.height = contentSize[1]; @@ -917,7 +922,7 @@ class TooltipView extends ComponentView { vAlign = null; } // Specify tooltip position by string 'top' 'bottom' 'left' 'right' around graphic element - else if (zrUtil.isString(positionExpr) && el) { + else if (isString(positionExpr) && el) { const pos = calcTooltipPosition( positionExpr, rect, contentSize, tooltipModel.get('borderWidth') ); @@ -982,7 +987,7 @@ class TooltipView extends ComponentView { }); // check is cbParams data value changed - lastCbParamsList && zrUtil.each(lastItem.seriesDataIndices, (idxItem) => { + lastCbParamsList && each(lastItem.seriesDataIndices, (idxItem) => { const seriesIdx = idxItem.seriesIndex; const cbParams = cbParamsList[seriesIdx]; const lastCbParams = lastCbParamsList[seriesIdx]; @@ -1016,6 +1021,8 @@ class TooltipView extends ComponentView { if (env.node || !api.getDom()) { return; } + const throttledUpdatePosition = this._throttledUpdatePosition; + throttledUpdatePosition && throttledUpdatePosition.clear(); this._tooltipContent.dispose(); globalListener.unregister('itemTooltip', api); } @@ -1057,7 +1064,7 @@ function buildTooltipModel( // value: 10, // tooltip: 'Something you need to know' // } - if (zrUtil.isString(tooltipOpt)) { + if (isString(tooltipOpt)) { tooltipOpt = { formatter: tooltipOpt }; @@ -1072,7 +1079,7 @@ function buildTooltipModel( } function makeDispatchAction(payload: ShowTipPayload | HideTipPayload, api: ExtensionAPI) { - return payload.dispatchAction || zrUtil.bind(api.dispatchAction, api); + return payload.dispatchAction || bind(api.dispatchAction, api); } function refixTooltipPosition( diff --git a/test/tooltip-lag-glitch.html b/test/tooltip-lag-glitch.html index d5cf6a0874..a12578519a 100644 --- a/test/tooltip-lag-glitch.html +++ b/test/tooltip-lag-glitch.html @@ -40,6 +40,7 @@
+