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..bc1e3a7124 100644 --- a/src/component/tooltip/TooltipView.ts +++ b/src/component/tooltip/TooltipView.ts @@ -16,15 +16,15 @@ * 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 * as graphic from '../../util/graphic'; +import { convertToColorString, formatTpl, TooltipMarker } from '../../util/format'; +import { parsePercent } from '../../util/number'; +import { Rect } from '../../util/graphic'; import findPointFromSeries from '../axisPointer/findPointFromSeries'; -import * as layoutUtil from '../../util/layout'; +import { getLayoutRect } from '../../util/layout'; import Model from '../../model/Model'; import * as globalListener from '../axisPointer/globalListener'; import * as axisHelper from '../../coord/axisHelper'; @@ -50,20 +50,15 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import TooltipModel, { TooltipOption } from './TooltipModel'; import Element from 'zrender/src/Element'; import { AxisBaseModel } from '../../coord/AxisBaseModel'; -// import { isDimensionStacked } from '../../data/helper/dataStackHelper'; import { ECData, getECData } from '../../util/innerStore'; import { shouldTooltipConfine } from './helper'; 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'; +import { clear, createOrUpdate } from '../../util/throttle'; -const bind = zrUtil.bind; -const each = zrUtil.each; -const parsePercent = numberUtil.parsePercent; - -const proxyRect = new graphic.Rect({ +const proxyRect = new Rect({ shape: { x: -1, y: -1, width: 2, height: 2 } }); @@ -137,7 +132,7 @@ type TooltipCallbackDataParams = CallbackDataParams & { // TODO: TYPE Value type axisValue?: string | number axisValueLabel?: string - marker?: formatUtil.TooltipMarker + marker?: TooltipMarker }; class TooltipView extends ComponentView { @@ -168,18 +163,15 @@ class TooltipView extends ComponentView { private _lastDataByCoordSys: DataByCoordSys[]; private _cbParamsList: TooltipCallbackDataParams[]; - private _updatePosition: ReturnType | TooltipView['_doUpdatePosition']; - init(ecModel: GlobalModel, api: ExtensionAPI) { if (env.node || !api.getDom()) { return; } const tooltipModel = ecModel.getComponent('tooltip') as TooltipModel; - const renderMode = tooltipModel.get('renderMode'); - this._renderMode = getTooltipRenderMode(renderMode); + const renderMode = this._renderMode = getTooltipRenderMode(tooltipModel.get('renderMode')); - this._tooltipContent = this._renderMode === 'richText' + this._tooltipContent = renderMode === 'richText' ? new TooltipRichContent(api) : new TooltipHTMLContent(api.getDom(), api, { appendToBody: tooltipModel.get('appendToBody', true) @@ -220,14 +212,16 @@ 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 + if (this._renderMode !== 'richText' && tooltipModel.get('transitionDuration')) { + createOrUpdate(this, '_updatePosition', 50, 'fixRate'); + } + else { + clear(this, '_updatePosition'); + } } private _initGlobalListener() { @@ -517,7 +511,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 +553,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; @@ -585,7 +579,7 @@ class TooltipView extends ComponentView { // Pre-create marker style for makers. Users can assemble richText // text in `formatter` callback and use those markers style. cbParams.marker = markupStyleCreator.makeTooltipMarker( - 'item', formatUtil.convertToColorString(cbParams.color), renderMode + 'item', convertToColorString(cbParams.color), renderMode ); const seriesTooltipResult = normalizeTooltipFormatResult( @@ -681,7 +675,7 @@ class TooltipView extends ComponentView { // Pre-create marker style for makers. Users can assemble richText // text in `formatter` callback and use those markers style. params.marker = markupStyleCreator.makeTooltipMarker( - 'item', formatUtil.convertToColorString(params.color), renderMode + 'item', convertToColorString(params.color), renderMode ); const seriesTooltipResult = normalizeTooltipFormatResult( @@ -728,7 +722,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 +760,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,17 +808,17 @@ 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) { html = timeFormat(params0.axisValue, html, useUTC); } - html = formatUtil.formatTpl(html, params, true); + html = 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,20 +851,20 @@ 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 }; } } - private _doUpdatePosition( + _updatePosition( tooltipModel: Model, positionExpr: TooltipOption['position'], x: number, // Mouse x @@ -890,7 +884,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,15 +892,15 @@ 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]; - const layoutRect = layoutUtil.getLayoutRect( + const layoutRect = getLayoutRect( boxLayoutPosition, { width: viewWidth, height: viewHeight } ); x = layoutRect.x; @@ -917,7 +911,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 +976,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 +1010,7 @@ class TooltipView extends ComponentView { if (env.node || !api.getDom()) { return; } + clear(this, '_updatePosition'); this._tooltipContent.dispose(); globalListener.unregister('itemTooltip', api); } @@ -1057,7 +1052,7 @@ function buildTooltipModel( // value: 10, // tooltip: 'Something you need to know' // } - if (zrUtil.isString(tooltipOpt)) { + if (isString(tooltipOpt)) { tooltipOpt = { formatter: tooltipOpt }; @@ -1072,7 +1067,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 @@
+