Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 105 additions & 70 deletions src/component/tooltip/TooltipHTMLContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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]];
}

/**
Expand Down Expand Up @@ -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)');
}
Expand Down Expand Up @@ -272,7 +304,8 @@ class TooltipHTMLContent {
document.body.appendChild(el);
}
else {
container.appendChild(el);
// PENDING
container.prepend(el);
}

this._container = container;
Expand Down Expand Up @@ -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';
}

Expand All @@ -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;
Expand Down Expand Up @@ -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];
});
}
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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};
Expand Down
7 changes: 4 additions & 3 deletions src/component/tooltip/TooltipView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
);
Expand All @@ -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
);
Expand Down
44 changes: 44 additions & 0 deletions src/component/tooltip/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;
Comment thread
plainheart marked this conversation as resolved.
}
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;
}