diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts
index 8f600934dc..85f4d8b5a1 100644
--- a/src/component/tooltip/TooltipHTMLContent.ts
+++ b/src/component/tooltip/TooltipHTMLContent.ts
@@ -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 `
`;
}
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, 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};
diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts
index cd52e2b1e5..6a6da1e74e 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -718,6 +718,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');
@@ -732,7 +733,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
);
@@ -742,8 +743,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
);
diff --git a/src/component/tooltip/helper.ts b/src/component/tooltip/helper.ts
index 352005662a..fa510b9e6c 100644
--- a/src/component/tooltip/helper.ts
+++ b/src/component/tooltip/helper.ts
@@ -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): boolean {
const confineOption = tooltipModel.get('confine');
@@ -27,3 +31,43 @@ export function shouldTooltipConfine(tooltipModel: Model): 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;
+}