未验证 提交 07b074ae 编写于 作者: S sushuang 提交者: GitHub

Merge pull request #14246 from apache/perf-tooltip

perf(tooltip): improve the performance of tooltip.
......@@ -17,26 +17,36 @@
* under the License.
*/
import { isString, indexOf, map, each, bind, isArray, isDom } from 'zrender/src/core/util';
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';
import { convertToColorString, toCamelCase, normalizeCssArray } from '../../util/format';
import ExtensionAPI from '../../core/ExtensionAPI';
import { ZRenderType } from 'zrender/src/zrender';
import { TooltipOption } from './TooltipModel';
import type ExtensionAPI from '../../core/ExtensionAPI';
import type { ZRenderType } from 'zrender/src/zrender';
import type { TooltipOption } from './TooltipModel';
import Model from '../../model/Model';
import { ZRRawEvent } from 'zrender/src/core/types';
import { ColorString, ZRColor } from '../../util/types';
import CanvasPainter from 'zrender/src/canvas/Painter';
import SVGPainter from 'zrender/src/svg/Painter';
import { shouldTooltipConfine } from './helper';
import type { ZRRawEvent } from 'zrender/src/core/types';
import type { ColorString, ZRColor } from '../../util/types';
import type CanvasPainter from 'zrender/src/canvas/Painter';
import type SVGPainter from 'zrender/src/svg/Painter';
import {
shouldTooltipConfine,
toCSSVendorPrefix,
getComputedStyle,
TRANSFORM_VENDOR,
TRANSITION_VENDOR
} from './helper';
import { getPaddingFromTooltipModel } from './tooltipMarkup';
const vendors = ['-ms-', '-moz-', '-o-', '-webkit-', ''];
/* global document, window */
const gCssText = 'position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;';
const CSS_TRANSITION_VENDOR = toCSSVendorPrefix(TRANSITION_VENDOR, 'transition');
const CSS_TRANSFORM_VENDOR = toCSSVendorPrefix(TRANSFORM_VENDOR, 'transform');
// eslint-disable-next-line
const gCssText = `position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;${env.transform3dSupported ? 'will-change:transform;' : ''}`;
function mirrorPos(pos: string): string {
pos = pos === 'left'
......@@ -60,44 +70,66 @@ function assembleArrow(
borderColor = convertToColorString(borderColor);
const arrowPos = mirrorPos(arrowPosition);
let positionStyle = '';
let transformStyle = '';
let positionStyle = `${arrowPos}:-6px;`;
let transformStyle = CSS_TRANSFORM_VENDOR + ':';
if (indexOf(['left', 'right'], arrowPos) > -1) {
positionStyle = `${arrowPos}:-6px;top:50%;`;
transformStyle = `translateY(-50%) rotate(${arrowPos === 'left' ? -225 : -45}deg)`;
positionStyle += 'top:50%';
transformStyle += `translateY(-50%) rotate(${arrowPos === 'left' ? -225 : -45}deg)`;
}
else {
positionStyle = `${arrowPos}:-6px;left:50%;`;
transformStyle = `translateX(-50%) rotate(${arrowPos === 'top' ? 225 : 45}deg)`;
positionStyle += 'left:50%';
transformStyle += `translateX(-50%) rotate(${arrowPos === 'top' ? 225 : 45}deg)`;
}
transformStyle = map(vendors, function (vendorPrefix) {
return vendorPrefix + 'transform:' + transformStyle;
}).join(';');
const borderStyle = `${borderColor} solid 1px;`;
const styleCss = [
'position:absolute;width:10px;height:10px;',
`${positionStyle}${transformStyle};`,
`border-bottom: ${borderColor} solid 1px;`,
`border-right: ${borderColor} solid 1px;`,
`background-color: ${backgroundColor};`,
'box-shadow: 8px 8px 16px -3px #000;'
`${positionStyle};${transformStyle};`,
`border-bottom:${borderStyle}`,
`border-right:${borderStyle}`,
`background-color:${backgroundColor};`,
'box-shadow:8px 8px 16px -3px #000;'
];
return `<div style="${styleCss.join('')}"></div>`;
}
function assembleTransition(duration: number, onlyFade?: boolean): string {
const transitionCurve = 'cubic-bezier(0.23, 1, 0.32, 1)';
let transitionText = 'opacity ' + (duration / 2) + 's ' + transitionCurve + ','
+ 'visibility ' + (duration / 2) + 's ' + transitionCurve;
const transitionCurve = 'cubic-bezier(0.23,1,0.32,1)';
let transitionOption = ` ${duration / 2}s ${transitionCurve}`;
let transitionText = `opacity${transitionOption},visibility${transitionOption}`;
if (!onlyFade) {
transitionText += ',left ' + duration + 's ' + transitionCurve
+ ',top ' + duration + 's ' + transitionCurve;
transitionOption = ` ${duration}s ${transitionCurve}`;
transitionText += env.transformSupported
? `,${TRANSFORM_VENDOR}${transitionOption}`
: `,left${transitionOption},top${transitionOption}`;
}
return map(vendors, function (vendorPrefix) {
return vendorPrefix + 'transition:' + transitionText;
}).join(';');
return CSS_TRANSITION_VENDOR + ':' + transitionText;
}
function assembleTransform(el: HTMLElement, x: number, y: number, toString?: boolean) {
// If using float on style, the final width of the dom might
// keep changing slightly while mouse move. So `toFixed(0)` them.
let x0;
let y0;
// not support transform, use `left` and `top` instead.
if (!env.transformSupported) {
x0 = x.toFixed(0);
y0 = y.toFixed(0);
return toString
? `top:${y0}px;left:${x0}px;`
: [['top', `${y0}px`], ['left', `${x0}px`]];
}
// support transform
// FIXME: the padding of parent element will affect the position of tooltip
const stl = getComputedStyle(el.parentElement);
x0 = (x - parseInt(stl.paddingLeft, 10)).toFixed(0);
y0 = (y - parseInt(stl.paddingTop, 10)).toFixed(0);
const is3d = env.transform3dSupported;
const translate = `translate${is3d ? '3d' : ''}(${x0}px,${y0}px${is3d ? ',0' : ''})`;
return toString
? CSS_TRANSFORM_VENDOR + ':' + translate + ';'
: [[TRANSFORM_VENDOR, translate]];
}
/**
......@@ -153,12 +185,12 @@ function assembleCssText(tooltipModel: Model<TooltipOption>, enableTransition?:
if (backgroundColor) {
if (env.canvasSupported) {
cssText.push('background-Color:' + backgroundColor);
cssText.push('background-color:' + backgroundColor);
}
else {
// for ie
cssText.push(
'background-Color:#' + toHex(backgroundColor)
'background-color:#' + toHex(backgroundColor)
);
cssText.push('filter:alpha(opacity=70)');
}
......@@ -272,7 +304,8 @@ class TooltipHTMLContent {
document.body.appendChild(el);
}
else {
container.appendChild(el);
// PENDING
container.prepend(el);
}
this._container = container;
......@@ -324,10 +357,9 @@ class TooltipHTMLContent {
// FIXME
// Move this logic to ec main?
const container = this._container;
const stl = (container as any).currentStyle
|| document.defaultView.getComputedStyle(container);
const position = getComputedStyle(container, 'position');
const domStyle = container.style;
if (domStyle.position !== 'absolute' && stl.position !== 'absolute') {
if (domStyle.position !== 'absolute' && position !== 'absolute') {
domStyle.position = 'relative';
}
......@@ -347,25 +379,25 @@ class TooltipHTMLContent {
clearTimeout(this._hideTimeout);
clearTimeout(this._longHideTimeout);
const el = this.el;
const style = el.style;
const styleCoord = this._styleCoord;
const offset = el.offsetHeight / 2;
nearPointColor = convertToColorString(nearPointColor);
el.style.cssText = gCssText + assembleCssText(tooltipModel, !this._firstShow, this._longHide)
// Because of the reason described in:
// http://stackoverflow.com/questions/21125587/css3-transition-not-working-in-chrome-anymore
// we should set initial value to `left` and `top`.
+ ';left:' + styleCoord[0] + 'px;top:' + (styleCoord[1] - offset) + 'px;'
+ `border-color: ${nearPointColor};`
+ (tooltipModel.get('extraCssText') || '');
el.style.display = el.innerHTML ? 'block' : 'none';
// If mouse occasionally move over the tooltip, a mouseout event will be
// triggered by canvas, and cause some unexpectable result like dragging
// stop, "unfocusAdjacency". Here `pointer-events: none` is used to solve
// it. Although it is not supported by IE8~IE10, fortunately it is a rare
// scenario.
el.style.pointerEvents = this._enterable ? 'auto' : 'none';
if (!el.innerHTML) {
style.display = 'none';
}
else {
style.cssText = gCssText
+ assembleCssText(tooltipModel, !this._firstShow, this._longHide)
// initial transform
+ assembleTransform(el, styleCoord[0], styleCoord[1], true)
+ `border-color:${convertToColorString(nearPointColor)};`
+ (tooltipModel.get('extraCssText') || '')
// If mouse occasionally move over the tooltip, a mouseout event will be
// triggered by canvas, and cause some unexpectable result like dragging
// stop, "unfocusAdjacency". Here `pointer-events: none` is used to solve
// it. Although it is not supported by IE8~IE10, fortunately it is a rare
// scenario.
+ `;pointer-event:${this._enterable ? 'auto' : 'none'}`;
}
this._show = true;
this._firstShow = false;
......@@ -421,10 +453,13 @@ class TooltipHTMLContent {
if (styleCoord[0] != null && styleCoord[1] != null) {
const style = this.el.style;
// If using float on style, the final width of the dom might
// keep changing slightly while mouse move. So `toFixed(0)` them.
style.left = styleCoord[0].toFixed(0) + 'px';
style.top = styleCoord[1].toFixed(0) + 'px';
const transforms = assembleTransform(
this.el,
styleCoord[0], styleCoord[1]
) as string[][];
each(transforms, (transform) => {
style[transform[0] as any] = transform[1];
});
}
}
......@@ -444,8 +479,10 @@ class TooltipHTMLContent {
}
hide() {
this.el.style.visibility = 'hidden';
this.el.style.opacity = '0';
const style = this.el.style;
style.visibility = 'hidden';
style.opacity = '0';
env.transform3dSupported && (style.willChange = '');
this._show = false;
this._longHideTimeout = setTimeout(() => this._longHide = true, 500) as any;
}
......@@ -478,12 +515,10 @@ class TooltipHTMLContent {
// Consider browser compatibility.
// IE8 does not support getComputedStyle.
if (document.defaultView && document.defaultView.getComputedStyle) {
const stl = document.defaultView.getComputedStyle(this.el);
if (stl) {
width += parseInt(stl.borderLeftWidth, 10) + parseInt(stl.borderRightWidth, 10);
height += parseInt(stl.borderTopWidth, 10) + parseInt(stl.borderBottomWidth, 10);
}
const stl = getComputedStyle(this.el);
if (stl) {
width += parseInt(stl.borderLeftWidth, 10) + parseInt(stl.borderRightWidth, 10);
height += parseInt(stl.borderTopWidth, 10) + parseInt(stl.borderBottomWidth, 10);
}
return {width: width, height: height};
......
......@@ -770,6 +770,7 @@ class TooltipView extends ComponentView {
tooltipModel.get('trigger'),
tooltipModel.get('borderColor')
);
const nearPointColor = nearPoint.color;
if (formatter && zrUtil.isString(formatter)) {
const useUTC = tooltipModel.ecModel.get('useUTC');
......@@ -784,7 +785,7 @@ class TooltipView extends ComponentView {
else if (zrUtil.isFunction(formatter)) {
const callback = bind(function (cbTicket: string, html: string | HTMLElement[]) {
if (cbTicket === this._ticket) {
tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPoint.color, positionExpr);
tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr);
this._updatePosition(
tooltipModel, positionExpr, x, y, tooltipContent, params, el
);
......@@ -794,8 +795,8 @@ class TooltipView extends ComponentView {
html = formatter(params, asyncTicket, callback);
}
tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPoint.color, positionExpr);
tooltipContent.show(tooltipModel, nearPoint.color);
tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr);
tooltipContent.show(tooltipModel, nearPointColor);
this._updatePosition(
tooltipModel, positionExpr, x, y, tooltipContent, params, el
);
......
......@@ -19,6 +19,10 @@
import { TooltipOption } from './TooltipModel';
import Model from '../../model/Model';
import { toCamelCase } from '../../util/format';
import env from 'zrender/src/core/env';
/* global document */
export function shouldTooltipConfine(tooltipModel: Model<TooltipOption>): boolean {
const confineOption = tooltipModel.get('confine');
......@@ -27,3 +31,43 @@ export function shouldTooltipConfine(tooltipModel: Model<TooltipOption>): boolea
// In richText mode, the outside part can not be visible.
: tooltipModel.get('renderMode') === 'richText';
}
function testStyle(styleProps: string[]): string | undefined {
if (!env.domSupported) {
return;
}
const style = document.documentElement.style;
for (let i = 0, len = styleProps.length; i < len; i++) {
if (styleProps[i] in style) {
return styleProps[i];
}
}
}
export const TRANSFORM_VENDOR = testStyle(
['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']
);
export const TRANSITION_VENDOR = testStyle(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']
);
export function toCSSVendorPrefix(styleVendor: string, styleProp: string) {
if (!styleVendor) {
return styleProp;
}
styleProp = toCamelCase(styleProp, true);
const idx = styleVendor.indexOf(styleProp);
styleVendor = idx === -1
? styleProp
: `-${styleVendor.slice(0, idx)}-${styleProp}`;
return styleVendor.toLowerCase();
}
export function getComputedStyle(el: HTMLElement, style?: string) {
const stl = (el as any).currentStyle
|| (document.defaultView && document.defaultView.getComputedStyle(el));
return stl
? style ? stl[style] : stl
: null;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册